diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000000..e3f65ef6a5a --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,8 @@ +housekeeping: + - "*.md" + - .github/PULL_REQUEST_TEMPLATE/*.md + - .github/pull_request_template.md + - docs/** + +outdated: + - base-branch: '!16-dev' diff --git a/.github/release.yml b/.github/release.yml index 968b64dcc2f..d15e13133a6 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -3,7 +3,7 @@ changelog: labels: - bot authors: - - renovate + - renovate[bot] - lawnchair-bot - crowdin-bot categories: @@ -18,6 +18,3 @@ changelog: - title: 🧹 Housekeeping labels: - housekeeping - - title: 🧑‍💻 Dependencies - labels: - - dependencies diff --git a/.github/workflows/build_release_apk.yml b/.github/workflows/build_release_apk.yml index 414463e03ac..51a8b1dec81 100644 --- a/.github/workflows/build_release_apk.yml +++ b/.github/workflows/build_release_apk.yml @@ -2,6 +2,12 @@ name: Build release APK on: workflow_dispatch: + push: + branches: + - '15-dev' + paths-ignore: + - '**.md' + - '.idea/**' jobs: build-release-apk: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f79a281116f..e7b397bb007 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: Lawnchair CI on: push: @@ -51,8 +51,9 @@ jobs: echo storeFile='${{ github.workspace }}/key.jks' >> keystore.properties echo ${{ secrets.KEYSTORE }} | base64 --decode > ${{ github.workspace }}/key.jks fi + # Release variant is disabled due to build conflict | assembleLawnWithQuickstepNightlyRelease - name: Build debug APK - run: ./gradlew assembleLawnWithQuickstepGithubDebug assembleLawnWithQuickstepPlayDebug assembleLawnWithQuickstepNightlyRelease --no-configuration-cache + run: ./gradlew assembleLawnWithQuickstepGithubDebug assembleLawnWithQuickstepPlayDebug --build-cache - name: Upload artifact uses: actions/upload-artifact@v5 with: @@ -118,7 +119,8 @@ jobs: nightly-release: runs-on: ubuntu-latest - if: github.repository_owner == 'LawnchairLauncher' && github.event_name == 'push' && github.ref == 'refs/heads/15-dev' + if: false + # if: github.repository_owner == 'LawnchairLauncher' && github.event_name == 'push' && github.ref == 'refs/heads/15-dev' needs: build-debug-apk permissions: contents: write diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 123e8e84350..043c6a8bdcd 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -24,7 +24,7 @@ jobs: upload_translations: false upload_sources: true download_translations: true - localization_branch_name: 15-dev-localization + localization_branch_name: 16-dev-localization create_pull_request: true base_url: 'https://lawnchair.crowdin.com' env: diff --git a/.github/workflows/crowdin_download.yml b/.github/workflows/crowdin_download.yml index 19fe5136d67..ac393db7f42 100644 --- a/.github/workflows/crowdin_download.yml +++ b/.github/workflows/crowdin_download.yml @@ -26,7 +26,7 @@ jobs: upload_translations: false upload_sources: false download_translations: true - localization_branch_name: 15-dev-localization + localization_branch_name: 16-dev-localization create_pull_request: true base_url: 'https://lawnchair.crowdin.com' env: diff --git a/.github/workflows/crowdin_upload.yml b/.github/workflows/crowdin_upload.yml index f85f0803060..83091af2a61 100644 --- a/.github/workflows/crowdin_upload.yml +++ b/.github/workflows/crowdin_upload.yml @@ -22,7 +22,7 @@ jobs: upload_translations: false upload_sources: true download_translations: false - localization_branch_name: 15-dev-localization + localization_branch_name: 16-dev-localization create_pull_request: false base_url: 'https://lawnchair.crowdin.com' env: diff --git a/.gitmodules b/.gitmodules index cf7551ac413..f2284aca652 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "platform_frameworks_libs_systemui"] path = platform_frameworks_libs_systemui url = https://github.com/LawnchairLauncher/platform_frameworks_libs_systemui + branch = 16-dev diff --git a/Android.bp b/Android.bp index eb033ee0daf..2c4fb379084 100644 --- a/Android.bp +++ b/Android.bp @@ -17,7 +17,19 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } -min_launcher3_sdk_version = "30" +min_launcher3_sdk_version = "31" + +// Targets that don't inherit framework aconfig libs (i.e., those that don't set +// `platform_apis: true`) must manually link them. +java_defaults { + name: "launcher-non-platform-apis-defaults", + static_libs: [ + "android.os.flags-aconfig-java", + "android.multiuser.flags-aconfig-java", + "android.appwidget.flags-aconfig-java", + "com.android.window.flags.window-aconfig-java", + ], +} // Common source files used to build launcher (java and kotlin) // All sources are split so they can be reused in many other libraries/apps in other folders @@ -31,12 +43,122 @@ filegroup { ], } +// Main Launcher source for compose, excluding the build config +filegroup { + name: "launcher-compose-enabled-src", + srcs: [ + "compose/facade/enabled/*.kt", + "compose/facade/core/*.kt", + "compose/features/**/*.kt", + ], +} + +filegroup { + name: "launcher-compose-disabled-src", + srcs: [ + "compose/facade/core/*.kt", + "compose/facade/disabled/*.kt", + ], +} + // Source code for quickstep build, on top of launcher-src filegroup { name: "launcher-quickstep-src", srcs: [ - "quickstep/src/**/*.java", "quickstep/src/**/*.kt", + "quickstep/src/**/*.java", + ], + device_common_srcs: [ + ":launcher-quickstep-processed-protolog-src", + ], +} + +// Launcher ProtoLog support +filegroup { + name: "launcher-quickstep-unprocessed-protolog-src", + srcs: [ + "quickstep/src_protolog/**/*.java", + ], +} + +java_library { + name: "launcher-quickstep_protolog-groups", + srcs: [ + "quickstep/src_protolog/**/*.java", + ], + static_libs: [ + "protolog-group", + "androidx.annotation_annotation", + "com_android_launcher3_flags_lib", + ], +} + +java_genrule { + name: "launcher-quickstep-processed-protolog-src", + srcs: [ + ":protolog-impl", + ":launcher-quickstep-unprocessed-protolog-src", + ":launcher-quickstep_protolog-groups", + ], + tools: ["protologtool"], + cmd: "$(location protologtool) transform-protolog-calls " + + "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " + + "--loggroups-jar $(location :launcher-quickstep_protolog-groups) " + + "--viewer-config-file-path /system_ext/etc/launcher.quickstep.protolog.pb " + + "--output-srcjar $(out) " + + "$(locations :launcher-quickstep-unprocessed-protolog-src)", + out: ["launcher.quickstep.protolog.srcjar"], +} + +java_genrule { + name: "gen-launcher.quickstep.protolog.pb", + srcs: [ + ":launcher-quickstep-unprocessed-protolog-src", + ":launcher-quickstep_protolog-groups", + ], + tools: ["protologtool"], + cmd: "$(location protologtool) generate-viewer-config " + + "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " + + "--loggroups-jar $(location :launcher-quickstep_protolog-groups) " + + "--viewer-config-type proto " + + "--viewer-config $(out) " + + "$(locations :launcher-quickstep-unprocessed-protolog-src)", + out: ["launcher.quickstep.protolog.pb"], +} + +prebuilt_etc { + name: "launcher.quickstep.protolog.pb", + system_ext_specific: true, + src: ":gen-launcher.quickstep.protolog.pb", + filename_from_src: true, +} + +// Source code for quickstep dagger +filegroup { + name: "launcher-quickstep-dagger", + srcs: [ + "quickstep/dagger/**/*.java", + "quickstep/dagger/**/*.kt", + ], +} + +// Source code for quickstep build with compose enabled, on top of launcher-src +filegroup { + name: "launcher-quickstep-compose-enabled-src", + srcs: [ + "quickstep/compose/facade/core/*.kt", + "quickstep/compose/facade/enabled/*.kt", + "quickstep/compose/features/**/*.kt", + ], +} + +filegroup { + name: "launcher-quickstep-compose-disabled-src", + srcs: [ + "quickstep/compose/facade/core/*.kt", + "quickstep/compose/facade/disabled/*.kt", ], } @@ -63,10 +185,118 @@ filegroup { srcs: ["proguard.flags"], } +// Opt-in configuration for Launcher3 code depending on Jetpack Compose. +soong_config_module_type { + name: "launcher_compose_java_defaults", + module_type: "java_defaults", + config_namespace: "ANDROID", + bool_variables: ["release_enable_compose_in_launcher"], + properties: [ + "srcs", + "static_libs", + ], +} + +// Opt-in configuration for Launcher Quickstep code depending on Jetpack Compose. +soong_config_bool_variable { + name: "release_enable_compose_in_launcher", +} + +soong_config_module_type { + name: "quickstep_compose_java_defaults", + module_type: "java_defaults", + config_namespace: "ANDROID", + bool_variables: ["release_enable_compose_in_launcher"], + properties: [ + "srcs", + "static_libs", + ], +} + +soong_config_module_type { + name: "launcher_compose_tests_java_defaults", + module_type: "java_defaults", + config_namespace: "ANDROID", + bool_variables: ["release_enable_compose_in_launcher"], + properties: [ + "static_libs", + ], +} + +launcher_compose_java_defaults { + name: "launcher_compose_defaults", + soong_config_variables: { + release_enable_compose_in_launcher: { + srcs: [ + ":launcher-compose-enabled-src", + ], + + // Compose dependencies + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + // By default, Compose is disabled and we compile the ComposeFacade + // in compose/launcher3/facade/disabled/. + conditions_default: { + srcs: [ + ":launcher-compose-disabled-src", + ], + static_libs: [], + }, + }, + }, +} + +quickstep_compose_java_defaults { + name: "quickstep_compose_defaults", + soong_config_variables: { + release_enable_compose_in_launcher: { + srcs: [ + ":launcher-quickstep-compose-enabled-src", + ], + + // Compose dependencies + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + // By default, Compose is disabled and we compile the ComposeFacade + // in compose/quickstep/facade/disabled/. + conditions_default: { + srcs: [ + ":launcher-quickstep-compose-disabled-src", + ], + static_libs: [], + }, + }, + }, +} + +launcher_compose_tests_java_defaults { + name: "launcher_compose_tests_defaults", + soong_config_variables: { + release_enable_compose_in_launcher: { + // Compose dependencies + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + conditions_default: { + static_libs: [], + }, + }, + }, +} + android_library { name: "launcher-aosp-tapl", libs: [ - "framework-statsd", + "framework-statsd.stubs.module_lib", ], static_libs: [ "androidx.annotation_annotation", @@ -76,6 +306,7 @@ android_library { "androidx.preference_preference", "SystemUISharedLib", "//frameworks/libs/systemui:animationlib", + "//frameworks/libs/systemui:contextualeducationlib", "launcher-testing-shared", ], srcs: [ @@ -136,12 +367,14 @@ java_library { // Library with all the dependencies for building Launcher3 android_library { name: "Launcher3ResLib", + defaults: [ + "launcher_compose_defaults", + ], srcs: [], resource_dirs: ["res"], static_libs: [ "LauncherPluginLib", "launcher_quickstep_log_protos_lite", - "android.os.flags-aconfig-java", "androidx-constraintlayout_constraintlayout", "androidx.recyclerview_recyclerview", "androidx.dynamicanimation_dynamicanimation", @@ -154,7 +387,10 @@ android_library { "//frameworks/libs/systemui:iconloader_base", "//frameworks/libs/systemui:view_capture", "//frameworks/libs/systemui:animationlib", + "//frameworks/libs/systemui:contextualeducationlib", + "//frameworks/libs/systemui:msdl", "SystemUI-statsd", + "WindowManager-Shell-shared-AOSP", "launcher-testing-shared", "androidx.lifecycle_lifecycle-common-java8", "androidx.lifecycle_lifecycle-extensions", @@ -163,8 +399,9 @@ android_library { "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", + "dagger2", + "jsr330", + "com_android_systemui_shared_flags_lib", ], manifest: "AndroidManifest-common.xml", sdk_version: "current", @@ -172,6 +409,9 @@ android_library { lint: { baseline_filename: "lint-baseline.xml", }, + flags_packages: [ + "com_android_launcher3_flags", + ], } // @@ -179,6 +419,7 @@ android_library { // android_app { name: "Launcher3", + defaults: ["launcher-non-platform-apis-defaults"], static_libs: [ "Launcher3ResLib", @@ -190,7 +431,7 @@ android_app { ], optimize: { - proguard_flags_files: ["proguard.pro"], + proguard_flags_files: [":launcher-proguard-rules"], // Proguard is disable for testing. Derivarive prjects to keep proguard enabled enabled: false, }, @@ -198,6 +439,7 @@ android_app { sdk_version: "current", min_sdk_version: min_launcher3_sdk_version, target_sdk_version: "current", + plugins: ["dagger2-compiler"], privileged: true, system_ext_specific: true, @@ -214,8 +456,12 @@ android_app { "AndroidManifest-common.xml", ], lint: { + extra_check_modules: ["Launcher3LintChecker"], baseline_filename: "lint-baseline.xml", }, + kotlincflags: [ + "-Xjvm-default=all", + ], } // Library with all the dependencies for building quickstep @@ -226,24 +472,34 @@ android_library { "quickstep/res", ], libs: [ - "framework-statsd", + "framework-statsd.stubs.module_lib", ], static_libs: [ "Launcher3ResLib", "lottie", "SystemUISharedLib", "SettingsLibSettingsTheme", + "dagger2", + "protolog-group", ], manifest: "quickstep/AndroidManifest.xml", min_sdk_version: "current", + lint: { + disabled_checks: ["MissingClass"], + }, } // Library with all the source code and dependencies for building Launcher Go android_library { name: "Launcher3GoLib", + defaults: [ + "launcher_compose_defaults", + "quickstep_compose_defaults", + ], srcs: [ ":launcher-src", ":launcher-quickstep-src", + ":launcher-quickstep-dagger", "go/quickstep/src/**/*.java", "go/quickstep/src/**/*.kt", ], @@ -258,7 +514,10 @@ android_library { "QuickstepResLib", "androidx.room_room-runtime", ], - plugins: ["androidx.room_room-compiler-plugin"], + plugins: [ + "androidx.room_room-compiler-plugin", + "dagger2-compiler", + ], manifest: "quickstep/AndroidManifest.xml", additional_manifests: [ "go/AndroidManifest.xml", @@ -267,19 +526,27 @@ android_library { min_sdk_version: "current", // TODO(b/319712088): re-enable use_resource_processor use_resource_processor: false, + kotlincflags: [ + "-Xjvm-default=all", + ], } // Library with all the source code and dependencies for building Quickstep android_library { name: "Launcher3QuickStepLib", + defaults: [ + "launcher_compose_defaults", + "quickstep_compose_defaults", + ], srcs: [ ":launcher-src", ":launcher-quickstep-src", + ":launcher-quickstep-dagger", ":launcher-build-config", ], resource_dirs: [], libs: [ - "framework-statsd", + "framework-statsd.stubs.module_lib", ], // Note the ordering here is important when it comes to resource // overriding. We want the most specific resource overrides defined @@ -291,18 +558,23 @@ android_library { ], manifest: "quickstep/AndroidManifest.xml", platform_apis: true, + plugins: ["dagger2-compiler"], min_sdk_version: "current", // TODO(b/319712088): re-enable use_resource_processor use_resource_processor: false, + kotlincflags: [ + "-Xjvm-default=all", + ], } // Build rule for Quickstep app. android_app { name: "Launcher3QuickStep", - static_libs: ["Launcher3QuickStepLib"], optimize: { - enabled: false, + proguard_flags_files: [":launcher-proguard-rules"], + enabled: true, + shrink_resources: true, }, platform_apis: true, @@ -316,7 +588,10 @@ android_app { "Launcher2", "Launcher3", ], - required: ["privapp_whitelist_com.android.launcher3"], + required: [ + "privapp_whitelist_com.android.launcher3", + "launcher.quickstep.protolog.pb", + ], resource_dirs: ["quickstep/res"], @@ -332,13 +607,11 @@ android_app { } - // 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: [], @@ -349,6 +622,7 @@ android_app { optimize: { proguard_flags_files: ["proguard.flags"], enabled: true, + shrink_resources: true, }, privileged: true, @@ -372,9 +646,9 @@ android_app { include_filter: ["com.android.launcher3.*"], }, } + android_app { name: "Launcher3QuickStepGo", - static_libs: ["Launcher3GoLib"], resource_dirs: [], @@ -385,6 +659,7 @@ android_app { optimize: { proguard_flags_files: ["proguard.flags"], enabled: true, + shrink_resources: true, }, privileged: true, diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index e8fa644e1fd..da43ea0bc66 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -136,13 +136,11 @@ android:writePermission="${applicationId}.permission.WRITE_SETTINGS" android:readPermission="${applicationId}.permission.READ_SETTINGS" /> - + [telegram]: https://t.me/lccommunity [discord]: https://discord.com/invite/3x8qNWxgGZ [nightly]: https://github.com/LawnchairLauncher/lawnchair/releases/tag/nightly +[security-report]: https://github.com/LawnchairLauncher/lawnchair/security/advisories/new +[security-policy]: https://github.com/LawnchairLauncher/lawnchair/security/policy [bug-reports]: https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=bug&projects=&template=bug_report.yaml&title=%5BBUG%5D+ [feature-requests]: https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=feature%2Cenhancement&projects=&template=feature_request.yaml&title=%5BFEATURE%5D+ [code-of-conduct]: CODE_OF_CONDUCT.md [crowdin]: https://lawnchair.crowdin.com [kotlin-coding-conventions]: https://kotlinlang.org/docs/coding-conventions.html -[lawnchair-package]: https://github.com/LawnchairLauncher/lawnchair/tree/15-dev/lawnchair -[src-package]: https://github.com/LawnchairLauncher/lawnchair/tree/15-dev/src +[lawnchair-package]: https://github.com/LawnchairLauncher/lawnchair/tree/16-dev/lawnchair +[src-package]: https://github.com/LawnchairLauncher/lawnchair/tree/16-dev/src [conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ [google-fonts-api-key]: https://developers.google.com/fonts/docs/developer_api#APIKey + diff --git a/GITHUB_CHANGELOG.md b/GITHUB_CHANGELOG.md index 775e6e3f0fb..fd5289fdfe1 100644 --- a/GITHUB_CHANGELOG.md +++ b/GITHUB_CHANGELOG.md @@ -1,47 +1,297 @@ -> [!TIP] -> For the story behind this release, see [the announcement](https://lawnchair.app/blog/lawnchair-15-beta-1) on our website. - -Lawnchair 15 Beta 1 is a foundational release based on Launcher3 from Android 15. This version works with QuickSwitch from Android 10 to Android 15 QPR1. Higher Android versions are not yet supported. - -### New Features -* **Android 15 Support:** Includes core platform features like Private Space and App Archiving. -* **App Drawer Folders:** A major new way to organize your app drawer. - * **Manual Folders:** Create, edit, and re-arrange your own custom folders. - * **Automatic Organization ("Caddy"):** An experimental feature to automatically categorize your entire app drawer into smart folders. -* **Dock Enhancements:** - * Add a background to the dock with options for color and corner radius. - * Place widgets directly in the dock. - * Show icon labels for apps in the dock. -* **Wallpaper Carousel:** A new pop-up menu item to quickly switch between your current and recent wallpapers, similar to the Pixel Launcher. -* **App Pausing:** For rooted users with QuickSwitch, you can now manually pause applications directly from the launcher. -* **Expanded Search Options:** - * Add custom search engines for web suggestions in the app drawer. - * New web search providers added, including Ecosia, Kagi, Firefox, Iceraven, and Mull. -* **"Deck" (Experimental):** An initial implementation of a "no app drawer" mode. *Please create a launcher backup before trying this feature to prevent data loss.* - -### Improvements -* **UI:** Updated many UI components to better align with Material 3 design principles. -* **Gestures:** Added "Open Recents Screen" and "Open Assistant" as new gesture actions. -* **Pop-Up Menu:** The long-press menu options can now be reordered. -* **Settings:** Reorganized many settings for a more intuitive experience and centralized all search-related settings into a single screen. - -### Core & Under-the-Hood -* **Type-Safe Navigation:** The settings infrastructure has been rewritten using modern Jetpack Compose Navigation for enhanced stability. -* **Build & Dependency Updates:** Major updates to dependencies and build scripts improve performance and maintainability. -* **New Translations:** Translations have been updated from Crowdin. -* **Nightly Builds:** A formal nightly build system is now in place for easier access to development versions. -* **Crash & Bug Fixes:** Implemented numerous fixes for various OEM skins (Lenovo, Motorola), custom ROMs, and older Android versions. - -### Regressions & Known Issues -* **Icon Badges:** Icon badges for work profile apps are temporarily non-functional due to core changes in the A15 rebase. This is a high-priority item for a future update. -* **'Customize Icon' State:** The bottom sheet for customizing an icon may not update its state immediately. Restarting the launcher will apply the change. -* **App Drawer folders:** As of now, you can't edit app drawer folders from the app drawer. Please visit the settings screen to change the contents of each folder. - -Other issues that you may encounter can be found at [our FAQ](https://lawnchair.app/faq/#common-issues). - -### Community & Thanks -This release marks a new chapter in how we engage with our community. We recently formed the **Lawnchair Triage Team**, a group of dedicated volunteers who have already begun the massive task of organizing our issue tracker. Their early efforts have been invaluable in helping us focus development. - -Thanks as well to all the people who have [donated to our Open Collective](https://opencollective.com/lawnchair) and [submitted translations on Crowdin](https://lawnchair.crowdin.com/). - -And, as always, a huge thanks to all our code contributors for this cycle: @validcube, @Morty0Smith, @benjaminkitt, and @tgex0 +Lawnchair 16 pE Development 2 is here! Contributors are encouraged to target this branch instead of +older (i.e., Lawnchair `15-dev`). + +### 🥞 Development 3 Release 2 + +Build: BD3.2211 + +Compatibility list: + +| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | +|-------------|---------------------|--------------------| +| Android 8.1 | | Android 12.0 | +| Android 9 | | Android 12.1 | +| Android 10 | | Android 13 | +| Android 11 | | Android 14 | +| | | Android 15 | +| | | Android 16 | + +#### Features +* [Lawnchair] Updated screenshots compressions and fastlane screenshot +* [Lawnchair] Features from Lawnchair 15-dev + +#### Fixes +* [Lawnchair] Conflict from Lawnchair 15-dev + +### Development 3 Release 1 + +Build: BD3.1711 + +The biggest change log ever, this marked the end of Bubble Tea [r2] branch as future development +switched to Bubble Tea [QPR1]. See you at Snapshot 7 or Development 4! + +(Again) Originally going to launch D3 if most of the issue on tracker have been resolved, but hit a +stability milestone instead. + +This release includes 4 new features, and 33 bug fixes, +Reimplemented some of Lawnchair features, better sizing of home screen, updated README.md screenshot +and the inclusion of Bubble Tea project into the official Lawnchair repository as 16-dev! + +This release have been tested with: +* ☁️ Pixel 6 (Android 12.0) +* 📱 Nothing (3a)-series (Android 15, Android 16.0) +* 📱 Vivo Y21 (Android 12.0) +* 📱 HTC Wildfire E3 lite (Android 12.0) +* Many more! Unfortunately I only count build from pE Open testing! + +Compatibility list: + +| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | +|-------------|---------------------|--------------------| +| Android 8.1 | | Android 12.0 | +| Android 9 | | Android 12.1 | +| Android 10 | | Android 13 | +| Android 11 | | Android 14 | +| | | Android 15 | +| | | Android 16 | + +> [!NOTE] +> QuickSwitch compatibility have not been tested at any time during the development of Bubble Tea! + +#### Features +* [Lawnchair] Complex Clover icon shape +* [Lawnchair] Very Sunny icon shape +* [Lawnchair/Font] Update Google Fonts listing to 25102025 +* [Lawnchair/Gesture] Allow Open Quick Settings* + +#### Fixes +* Disable OEM override on launcher settings, (reimplement `ENABLE_AUTO_INSTALLS_LAYOUT` | c51b2a221838aefb610b7146fc4ef7cb34e5e495) +* [Lawnchair/Iconloaderlib] Reimplement custom app name +* [Lawnchair] Reimplement Launcher3 debug page +* [Lawnchair] Reimplement Caddy and App drawer folder +* [Lawnchair] Reimplement Hotseat toggle +* [Lawnchair] Reimplement Favorite application label +* [Lawnchair] Hotseat positioning with favorite icon label enabled placed the same even if label is disabled +* [Lawnchair] Hotseat background now have a reasonably sized margin compared to D2 +* [Lawnchair] Qsb sizing now correctly estimate the width based on width of the app/widget layout or DeviceProfile on device with inlined Qsb +* [Lawnchair] Reimplement Allapps opacity configuration +* [DeviceProfile] Crash from createWindowContext on less than Android 12.0 +* [QuickstepLauncher] Ignore trying to set SystemUiProxy icon sizes on less than Android 12.1 +* [Lawnchair/BlankActivity] Apply Material 3 Expressive button animations +* [Launcher] Disable add widget button if home screen is locked +* [Lawnchair/Iconloaderlib] Crash when trying to set `null` monochrome icon on less than Android 12.1 +* [SystemUI/Unfold] Crash when getting configuration for foldable-specific resources +* [Lawnchair/Iconloaderlib] Don't parse monochrome drawable in Android 12.1 or less +* [Launcher3/AllApps] Allow theming of Expressive allapps +* ~~[Lawnchair] Lawnchair can now be compiled in release mode~~ + * [Lawnchair] Fix crashes with WM-Shell +* [Lawnchair] Bottom sheet blur will only trigger when your device supported blur* +* [Lawnchair/Lazy] Corner radii of lazy component now matched radius of non-lazy* +* [Lawnchair/Debug] Cleanup the debug menu* +* [Lawnchair/Docs] Warn off danger using 16-dev branch* +* [Launcher3] Crash with predictive back on some device using Android 13/14 +* [Launcher3] WindowInsets crash in Android 11 +* [Launcher3] Widgets crash on some device using Android 12 +* [Launcher3/PrivateSpace] Use custom icons of Private Space lock* +* [Launcher3/Iconloaderlib] App badges for work profile* +* [Lawnchair] Update spacing for dock search settings* +* [Launcher3] Quickstep dispatcher crash on Android 13 +* [Launcher3] Crash due to missing resources for Android 8.0 +* [Lawnchair/Docs] Update screenshot to 16-dev + +### 🥞 Development 2 + +Originally going to launch D2 if most of the comestic bug fixes have been resolved, but hit a +stability milestone instead. + +This release includes 15 new features, and 20 bug fixes, +Lawnchair settings now takes shape of initial material 3 expressive redesign, [(but by no mean finish!)][Lawnget] +launcher should now render icons better than D1 milestone, with auto-adaptive icons feature reimplemented. + +This release have been tested with: +* ☁️ Pixel 6 (Android 12.0) - Build: Ad-hoc +* ☁️ Pixel 6a (Android 12.1) - Build: Ad-hoc +* ☁️ Pixel 7 (Android 13) - Build: Ad-hoc +* ☁️ Pixel 9 (Android 15, Android 16.0) - Build: Ad-hoc +* ☁️ Pixel 9 Pro Fold (Android 14, Android 15) - Build: Ad-hoc +* ☁️ Vivo V40 (Android 15) - Build: Ad-hoc +* ☁️ Xiaomi MIX (Android 15) - Build: Ad-hoc +* 📱 Nothing (3a)-series (Android 15) - Build: pE-`15102025` +* 📱 Pixel 9 Pro XL (Android 16.0 QPR2 Beta 2) - Build: pE-`02102025` +* 📱 BLU View 5 Pro (Android 14) - Build: pE-`02102025` +* 📱🔥 Vivo Y21 (Android 12.0) - Build: pE-`08102025` + +> [!NOTE] +> QuickSwitch compatibility have not been tested at any time during the development of Bubble Tea! + +[Lawnget]: https://www.google.com/teapot + +Compatibility list: + +| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | +|-------------|---------------------|--------------------| +| Android 8.1 | Android 12.0 | Android 12.1 | +| Android 9 | | Android 13 | +| Android 10 | | Android 14 | +| Android 11 | | Android 15 | +| | | Android 16 | + +#### Features + +* Enable All Apps Blur Flags on Phone (oops, forgot about the allAppsSheetForHandheld flag) +* Make Safe Mode check more reliable +* Smartspace Battery now reports battery charging status of Fast (more than 90% of 20 W) and Slow (less than 90% of 5 W) charging +* Show pseudonym version to Settings +* Resizing workspace calculate items position more accurately +* Update Lawnchair default grid size to 4×7 (or 4×6 with smartspace widget) +* Reimplement Hotseat background customisation +* Make haptic on a locked workspace use Google MSDL vibration +* Make Launcher3 colour more accurate to upstream Android 16 +* ProvideComposeSheetHandler now have expressive blur +* Lawnchair Settings now uses Material 3 Expressive +* Animate keyboard on/off state on app drawer search (Try enabling automatically show keyboard in app drawer settings and swipe up and down or directly tap “Apps list” in popup menu) -> (Backport not possible) +* Add LeakCanary check to all debug variant of the application +* [DEBUG] Launcher3 feature status diagnostic check in debug menu +* [Documentation] Add more visibility into both app certificate and SLSA verification for app authenticity check [VERIFICATION.md](VERIFICATION.md) +* [Documentation] Initial drafting of Improve documentation v6 (pave-path) +* [Launcher] Widget animations during resize +* [Iconloaderlib] Enable second hand for the clock app + +#### Fixes + +* Fix unable to access preview for icon style +* Popup's Arrow Theme now has the correct theme +* Widget should open normally after a workaround (C7evQZDJ) +* Fix (1) Search bar and Dock, (2) Folders and App Drawer settings didn't open due to init problems +* Lawnchair should hopefully remember what grid they should be using +* Most if not all of Lawnchair settings should be usable without crashes +* Correct Baseline Profile from old `market` to `play` variant, and now should calculate profile for `nightly` +* Fix Private Space crash when Lawnchair is set as Launcher due to flags only available on A16 +* Fix crash on a device with strict export receiver requirements on A14 +* Interactable widget crashing due to App Transition Manager being null (C7evQZDJ) +* Icon not responding to mouse cursor -> (Backported to Lawnchair 15) +* Rare NoSuchMethodError crash on IMS canImeRenderGesturalNavButtons +* [Lawnchair] Reimplement Bulk icons toggle +* SettingsCache crashing with SecurityException with unreadable keys (@hide) in Android 12 and newer (assume false) +* Assume flags `enableMovingContentIntoPrivateSpace` is false when ClassNotFoundException on Android 16 devices +* Rare NoSuchMethodError crash on SurfaceControl setEarlyWakeupStart and setEarlyWakeupEnd +* Properly align built-in smartspace in workspace +* Use WM Proxy from Lawnchair instead of System, fix Android 8.1/9/10/11/12.0/12.1 regarding SE, NSME like SystemBarUtils -> (dWkyIGw9), (reworked CllOXHJv) + * LawnchairWindowManagerProxy have been migrated to Dagger + * SystemWindowManagerProxy have been left unused +* [Lawnchair/Iconloaderlib] Update CustomAdaptiveIconDrawable to latest AOSP 13 +* [Iconloaderlib] Reset most of the changes to favour more AOSP 16_r02 code then Lawnchair (need rewrite) + * fix icon loaded in monochrome and always monochrome when it is not supposed to + * fix notification dots being twice the size with notification count +* [Lawnchair/Iconloaderlib] Reimplement Lawnchair Iconloaderlib (adaptive icons, monochrome, regular icon) + +#### Known Bugs +* Preview can't show device wallpaper -> (lIxkAYGg) +* IDP Preview doesn't refresh on settings change -> workaround is to hit apply and re-open the preview -> (ZbLX3438) +* Workspace theme doesn't refresh until restart -> (ZbLX3438) -> Fixed as part of (31lLEflf, 1MevNrzp) +* Lawnchair Colour can't handle restart causing default colour to be used instead -> Fixed? -> Properly fixed as part of (31lLEflf, 1MevNrzp) +* (Investigating) Work profile switch on widget selector *may* have reverted to Lawnchair 15 style +* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair + +### Development 1 + +First development milestone! Basic launcher functionality should be stable enough. + +* Make Lawnchair Launcher launchable in Android 12.1, 13, 14, 15, 16 +* Remove two deprecated features (Use Material U Popup, and Use dot pagination) +* Add pseudonym version in debug settings +* Adapt Lawnchair code to Launcher3 16 +* Make basic features of Launcher work (App Drawer, Home Screen, Search, Folders, Widgets) +* Enable Material Expressive Flags (Try swiping through launcher page) +* Enable All Apps Blur Flags (Try opening All Apps on supported devices) +* Enable MSDL Haptics Feedback Flags (Try gliding widget or icons across the homescreen) +* Make Predictive Back Gesture work on Android 13, 14, 15, 16 (Try swiping left or right on gesture-based navigational) +* Programmatically set Safe Mode status + +#### Known Bugs + +* App Icon may sometimes render with less than 0 in height/width causing blank icon to be rendered and crashing ISE on customising icons -> (31lLEflf) +* Any Lawnchair settings using IDP will crash the launcher -> Fixed in Lawnchair 16 pE Development 2 +* Icon pack isn't usable -> (DXo69Qzd) +* Dynamic icons will not be themed by launcher +* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair + +### Snapshot 6 + +This is a developer-focused change log: + +This snapshot marks the first time Lawnchair 16 is able to compile and build an APK! + +* Fix all issues with Java files in both `lawn` and `src` +* Make Lawnchair compilable (with instant crash) +* Move to KSP for Dagger code generation + +### Snapshot 5 + +This is a developer-focused change log: + +This snapshot now able to compile all sources (Kotlin files only) + +* Fix MORE MORE MORE `lawn` issues +* Use Gradle Version Catalog for consistent dependency version across all modules (Full implementation @ LawnchairLauncher/Lawnchair#5753) +* Magically fix ASM Instrumentation issues (I didn't do anything, it just works now) +* Fix ALL the issues in kotlin stage (`compileLawnWithQuickstepNightlyDebugKotlin`) +* Reintroduce some features from Lawnchair +* Add compatibility checks and workarounds for them +* Fix most issues with Java files in both `lawn` and `src` + +### Snapshot 4 + +This is a developer-focused change log: + +This snapshot marks the first time Lawnchair 16 is able to compile all Launcher3 sources! + +* Add `MSDLLib` to `platform_frameworks_libs_systemui` +* Add `contextualeducationlib` to `platform_frameworks_libs_systemui` +* Fix issues in both `lawn` and `src` modules +* Fix AIDL sources +* Resolve Lawnchair/LC-TODO lists +* Merge `wmshell.shared` res with res from `wmshell` +* Consistent build reproducibility by specifying dependencies in `build.gradle` +* Some ASM Instrumentation issues (and re-add some…) +* Update documentations + +### Snapshot 3 + +This is a developer-focused change log: + +Not a lot of errors left to go! + +* Finish correctly implementing all Dagger functions (?) +* Merge Lawnchair 15 Beta 1 into Bubble Tea + * Support for 16-kb page size devices +* Repository rebased and dropped commit + * Switch back from turbine-combined variant to javac variant for prebuilt SystemUI-core-16 because issues with LFS + * MORE MORE fixes regarding turbine-combined to javac +* Publish `platform_frameworks_libs_systemui` to pe 16-dev branch +* ATLEAST check to almost every launcher3 source file +* `Utils` module (stripped) +* Fix Dagger duplicated classes (because of Dagger dependency ksp/kapt mixing) +* Build reproducibility improvements by specifying dependencies in `build.gradle` files +* Fix some of the issues in both `lawn` and `src` modules + +### Snapshot 2 + +This is a developer-focused change log: + +This snapshot milestone marked the first time Lawnchair now able to compile all supplementary +modules, `src` + `lawn` will be in Snapshot 5 or Development 1 milestone. + +* Merge flags +* Fix some issues with launcher3 sources. +* A temporary workaround with framworks.jar not adding in anim module. +* Shared not having access to animationlib. +* **Switch from javac variant to turbine-combined variant for prebuilt SystemUI-core-16**. + +### From Initial snapshot 0 and 1 + +This is a developer-focused change log: +* Prebuilt updated to Android 16-0.0_r2 (Android 16.0.0 Release 2) +* Submodule have also been refreshed to A16r2 +* Baklava Compatlib (QuickSwitch compatibility not guaranteed) +* Refreshed internal documentation like prebuilt, systemUI diff --git a/OWNERS b/OWNERS index a66bf54b58f..3f7a780edc8 100644 --- a/OWNERS +++ b/OWNERS @@ -6,11 +6,8 @@ adamcohen@google.com hyunyoungs@google.com -twickham@google.com vadimt@google.com winsonc@google.com -jonmiranda@google.com -awickham@google.com agvard@google.com # Launcher workspace eng team @@ -23,19 +20,22 @@ fransebas@google.com pinyaoting@google.com andonian@google.com sihua@google.com +abegovic@google.com # Multitasking eng team tracyzhou@google.com peanutbutter@google.com jeremysim@google.com atsjenk@google.com -brianji@google.com +hwwang@google.com # Overview eng team alexchau@google.com samcackett@google.com silvajordan@google.com uwaisashraf@google.com +vinayjoglekar@google.com +willosborn@google.com # Physical Keyboard & Trackpad eng team patmanning@google.com @@ -45,11 +45,35 @@ helencheuk@google.com shamalip@google.com zakcohen@google.com +# System Navigation team +brianji@google.com +jonmiranda@google.com +jagrutdesai@google.com +randypfohl@google.com +saumyaprakash@google.com +sukeshram@google.com +twickham@google.com +victortulias@google.com + +## Note: some of the below overlap and also work on other integrations like Circle to Search. + +# All Apps / QSB team +awickham@google.com +brdayauon@google.com +ganjam@google.com +kylim@google.com + +# Smartspace team +xilei@google.com +davidct@google.com +iamiam@google.com +jiuyu@google.com + per-file FeatureFlags.java, globs = set noparent -per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com +per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com, abegovic@google.com per-file DeviceConfigWrapper.java, globs = set noparent -per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com +per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, abegovic@google.com # Predictive Back -per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com \ No newline at end of file +per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 9051ca8562b..768ba652de5 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,8 +1,10 @@ [Builtin Hooks] ktfmt = true +bpfmt = true [Builtin Hooks Options] ktfmt = --kotlinlang-style +bpfmt = -d [Tool Paths] ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh @@ -10,4 +12,3 @@ 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} -flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH} diff --git a/README.md b/README.md index 694336fea52..fc22c12ccd8 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,143 @@ -# Lawnchair 15 - -[![Build debug APK](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/ci.yml/badge.svg)](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/ci.yml) -[![Build release APK](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/release_update.yml/badge.svg)](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/release_update.yml) -[![Crowdin](https://badges.crowdin.net/e/188ba69d884418987f0b7f1dd55e3a4e/localized.svg)](https://lawnchair.crowdin.com/lawnchair) -[![OpenCollective](https://img.shields.io/opencollective/all/lawnchair?label=financial%20contributors&logo=open-collective)](https://opencollective.com/lawnchair) -[![Telegram](https://img.shields.io/endpoint?url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Flccommunity)](https://t.me/lccommunity) -[![Discord](https://img.shields.io/discord/803299970169700402?label=server&logo=discord)](https://discord.gg/3x8qNWxgGZ) -[![GitHub Downloads](https://img.shields.io/github/downloads/LawnchairLauncher/lawnchair/total.svg?label=GitHub%20Downloads&logo=github)](https://github.com/LawnchairLauncher/lawnchair/releases) -[![Play Store Installs](https://img.shields.io/endpoint?color=green&logo=googleplay&logoColor=green&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Dapp.lawnchair.play%26l%3DPlay%2520Store%2520Installs%26m%3D%24shortinstalls)](https://play.google.com/store/apps/details?id=app.lawnchair.play) - - - - - - - Google Pixel running Lawnchair Launcher with green wallpaper - - -Lawnchair is a free, open-source home app for Android. Taking Launcher3—Android’s default home app—as a starting point, it ports Pixel Launcher features and introduces rich customization options. - -This branch houses the codebase of Lawnchair 15, which is currently in beta and is based on Launcher3 from Android 15. For Lawnchair 9 to 14, see the branches with the `9-` to `14-` prefixes, respectively. - -## Features - -- **Material You Theming:** Adapts to your wallpaper and system theme. -- **At a Glance Widget:** Displays information *at a glance* with support for [Smartspacer](https://github.com/KieronQuinn/Smartspacer). -- **QuickSwitch Support:** Integrates with Android Recents on Android 10 and newer. (requires root) -- **Global Search:** Allows quick access to apps, contacts, and web results from the home screen. -- **Customization Options:** Provides options to tweak icons, fonts, and colors to your liking. -- And more! - -## Download - -

- - - - - Get it on Google Play - - - - - - Get it on IzzyOnDroid - - - - - - Get it on Obtainium - - - - - - Get it on GitHub - - -

- -Lawnchair on Play Store will install as a different app from other sources. Some features may be restricted to comply with Google Play's publishing rules. - -### Development builds - -Interested in keeping yourself up-to-date with every Lawnchair development? Try our development builds! - -These builds offer the latest features and bug fixes at a cost of being slower and introducing new bugs. Ensure that you make backups before installing. - -**Download:** [Obtainium][Obtainium link] • [GitHub][GitHub link] • [nightly.link][Nightly link] - -### Verification - -Verify the integrity of your Lawnchair download using these SHA-256 hashes: - -###### Google Play +# AutoCat + +> **Fork of [Lawnchair 15](https://github.com/LawnchairLauncher/lawnchair)** with intelligent app auto-categorization + +[![Build debug APK](https://github.com/thejaustin/AutoCat/actions/workflows/ci.yml/badge.svg)](https://github.com/thejaustin/AutoCat/actions/workflows/ci.yml) + +## About AutoCat + +**AutoCat** is a personal fork of Lawnchair Launcher that adds **AI-powered automatic app categorization** to the app drawer. It uses a multi-stage intelligent pipeline with LLM integration and automatic folder sync to keep your apps organized. + +### ✨ Key Features + +#### 🤖 **Multi-Provider LLM Categorization** +- **4 LLM Providers**: Google AI (Gemini 2.0), Claude (Anthropic), OpenAI (GPT-4o), Perplexity (Llama 3.1) +- **Batch Processing**: 20x faster than sequential (100 apps in 20 seconds vs 6.7 minutes) +- **Auto Batch Sizing**: Optimizes batch size based on model context windows (16K-1M tokens) +- **Provider Fallback**: Automatic failover between providers for reliability +- **Model Selection**: Choose specific models per provider with deprecation handling +- **Accuracy Tracking**: Tracks model performance based on your correction history +- **Auto-Select Best Model**: Automatically uses the most accurate provider for your apps + +#### 📁 **Dual Folder Sync** +- **App Drawer Folders**: Automatically creates folders in caddy-style app drawer +- **Home Screen Folders**: Optional sync to workspace folders (Android Launcher3) +- **Three Sync Modes**: DRAWER (default), HOME_SCREEN, or BOTH +- **10x Performance**: Optimized folder sync (recent improvement) + +#### 🎯 **Smart Categorization Pipeline** +1. **Built-in Categories** - Android system categories (70% coverage) +2. **LLM Categorization** - AI-powered custom categories (85% accuracy) +3. **User Overrides** - Manual corrections always respected +4. **Learning System** - Learns from user corrections to improve over time + +#### 🎨 **Rich User Experience** +- **Category Tabs**: Organized drawer with customizable category tabs +- **Manual Override UI**: Long-press to change app categories +- **Smart Launcher Import**: Import categories from Smart Launcher backups (.slbk files) +- **Developer Diagnostics**: Comprehensive error logging and diagnostics +- **Progress Tracking**: Real-time progress with batch information +- **App Descriptions**: Display LLM reasoning for categorization decisions + +### 📊 Development Status + +🚀 **Beta** - Core features complete, actively testing and refining. + +**Completed Features:** +- ✅ Room database foundation +- ✅ Built-in system categorization +- ✅ LLM integration (4 providers) +- ✅ Batch API processing +- ✅ Dual folder sync (drawer + home) +- ✅ Category management UI +- ✅ User correction learning +- ✅ Smart Launcher import +- ✅ Developer diagnostics +- ✅ Comprehensive error handling +- ✅ Model accuracy tracking & analytics +- ✅ Adaptive model auto-selection + +**In Progress:** +- 🔄 Compilation & integration testing +- 🔄 Performance verification + +**Upcoming:** +- 🎯 Retry logic with exponential backoff +- 🎯 Parallel batch processing +- 🎯 Folder sync mode UI preference +- 🎯 Multi-language support + +### 🏗️ Technical Architecture + +**Tech Stack:** +- **Language**: Kotlin with Coroutines +- **Database**: Room (SQLite) for categories and folders +- **LLM Integration**: 4 providers with REST APIs +- **UI**: Android Jetpack Compose + Material Design 3 +- **Base**: Lawnchair 15 (Android 15 Launcher3) + +**Performance Metrics:** +- **Categorization Speed**: 20x faster with batch processing (20s vs 6.7min for 100 apps) +- **Token Efficiency**: 68% token savings with batching +- **Accuracy**: ~90% combined (70% built-in + 85% LLM for remaining) +- **Folder Sync**: 10x performance optimization + +**Key Components:** ``` -47:AC:92:63:1C:60:35:13:CC:8D:26:DD:9C:FF:E0:71:9A:8B:36:55:44:DC:CE:C2:09:58:24:EC:25:61:20:A7 +CategorizationManager → LLMCategorizer → [4 Providers] + ↓ + CategoryFolderSyncService → [Drawer/Home Folders] + ↓ + UserCorrectionLearner → [Improve over time] ``` -###### Elsewhere -``` -74:7C:36:45:B3:57:25:8B:2E:23:E8:51:E5:3C:96:74:7F:E0:AD:D0:07:E5:BA:2C:D9:7E:8C:85:57:2E:4D:C5 -``` +### Download & Testing -## Contributing +**Latest Development Build**: [dev-latest release](https://github.com/thejaustin/AutoCat/releases/tag/dev-latest) -Please visit the [Lawnchair Contributing Guidelines](CONTRIBUTING.md) for information and tips on contributing to Lawnchair. +AutoCat uses a versioning scheme: `15.0.b1-autocat.{BUILD_NUMBER}` +- `15.0.b1` = Lawnchair 15.0 Beta 1 (base version) +- `autocat.X` = AutoCat build number -## Supporting Lawnchair +Every push creates TWO releases: +- **`dev-latest`** (rolling) - Always points to newest build +- **`v15.0.b1-autocat.X`** (versioned) - Permanent release for each build -If you love what we do, consider [supporting us on Open Collective](https://opencollective.com/lawnchair)! Your contributions help keep Lawnchair independent and enable us to develop faster. +**Quick Install**: +1. **Download**: [dev-latest release](https://github.com/thejaustin/AutoCat/releases/tag/dev-latest) → Download `AutoCat-dev-latest.apk` +2. **Install**: Enable "Install from Unknown Sources" in Android settings +3. **Verify**: Check the release notes for the SHA-256 hash to verify integrity. +4. **Test**: See [WHAT_TO_TEST.md](WHAT_TO_TEST.md) for testing checklist -A huge thank you to our **Core Backers ($5+)**: -*(These backers directly fund our Project Velocity Fund)* +**All Versions**: [Releases page](https://github.com/thejaustin/AutoCat/releases) | **Testing Guide**: [TESTING.md](TESTING.md) | **Changelog**: [CHANGELOG.md](CHANGELOG.md) -[![Core Backers](https://opencollective.com/lawnchair/tiers/backer.svg?avatarHeight=64&width=890&button=false)](https://opencollective.com/lawnchair) +### 🔐 Security & Privacy -[Become a supporter](https://opencollective.com/lawnchair) to help us cover our operational costs, or become a Core Backer to be featured here! +- **API Keys**: Users must provide their own LLM API keys (not included) +- **Local Processing**: All categorization happens on-device after fetching from LLM +- **No Telemetry**: No data collection or tracking +- **Open Source**: All code is publicly available for audit -## Quick links +**Supported LLM Providers:** +- Google AI (Gemini) - Free tier available +- OpenAI (GPT) - Pay-per-use +- Anthropic (Claude) - Pay-per-use +- Perplexity (Llama) - Free tier available -- [Website](https://lawnchair.app) -- [News on Telegram](https://t.me/lawnchairci) -- [Discord](https://discord.com/invite/3x8qNWxgGZ) -- [Lawnchair on X (formerly Twitter)](https://x.com/lawnchairapp) -- [_XDA_ thread](https://xdaforums.com/t/lawnchair-customizable-pixel-launcher.3627137/) +### Development Logs -You can view all our links in the [Lawnchair Wiki](https://github.com/LawnchairLauncher/lawnchair/wiki). +This project includes detailed development session logs in the [`dev-logs/`](dev-logs/) directory. Each log contains: +- Full conversation transcripts between developer and AI assistant +- Technical decisions and rationale +- Code changes and commits +- Next steps and open questions + +See [`dev-logs/README.md`](dev-logs/README.md) for more information. + +--- + +## About Lawnchair + +Lawnchair is a free, open-source home app for Android. Taking Launcher3—Android’s default home app—as a starting point, it ports Pixel Launcher features and introduces rich customization options. - -[Nightly link]: https://nightly.link/LawnchairLauncher/lawnchair/workflows/ci/15-dev -[Obtainium link]: https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22app.lawnchair.nightly%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Flawnchairlauncher%2Flawnchair%22%2C%22author%22%3A%22Lawnchair%20Launcher%22%2C%22name%22%3A%22Lawnchair%20(Debug)%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Atrue%2C%5C%22fallbackToOlderReleases%5C%22%3Afalse%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22Lawnchair%20Nightly%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Afalse%2C%5C%22dontSortReleasesList%5C%22%3Afalse%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Afalse%2C%5C%22releaseDateAsVersion%5C%22%3Atrue%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22Lawnchair%20is%20a%20free%2C%20open-source%20home%20app%20for%20Android.%20(NOTE%3A%20This%20is%20the%20debug%20version%20of%20Lawnchair%2C%20for%20the%20beta%2Fstable%20versions%20see%20%5C%5C%5C%22Lawnchair%5C%5C%5C%22)%5C%22%7D%22%7D -[GitHub link]: https://github.com/LawnchairLauncher/lawnchair/releases/tag/nightly +This project is a fork of [Lawnchair](https://github.com/LawnchairLauncher/lawnchair). All credit for the base launcher goes to the Lawnchair team. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md index 9aa0b671f60..430d3a7682a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,8 +9,9 @@ The latest version of Lawnchair is the only supported version. | Version | Supported | -| -------------- | ------------------ | +|----------------|--------------------| | Nightly build | :white_check_mark: | +| 16 | :white_check_mark: | | 15 | :white_check_mark: | | 14 | :x: | | 13 | :x: | diff --git a/TELEGRAM_CHANGELOG.txt b/TELEGRAM_CHANGELOG.txt index 47db6009765..104b91b32e7 100644 --- a/TELEGRAM_CHANGELOG.txt +++ b/TELEGRAM_CHANGELOG.txt @@ -1,10 +1,197 @@ -*Lawnchair 15 Beta 1 is here!* +Lawnchair 16 pE Development 2 is here! Contributors are encouraged to target this branch instead of +older (i.e., Lawnchair `15-dev`). -We're excited to release the first beta for Lawnchair 15, a foundational update based on Android 15. +### Development 2 -The biggest new feature is one of our most requested ever: **App Drawer Folders**. You can now create and reorder custom folders to finally organize your app drawer. +Originally going to launch D2 if comestic bug fixes have been resolved, but hit a +stability milestone instead. -This release is packed with many other improvements, from a more powerful dock to dozens of UI refinements. +This release includes 15 new features, and 20 bug fixes, +Lawnchair settings now takes shape of initial material 3 expressive redesign, [(but by no mean finish!)](https://www.google.com/teapot) +launcher should now render icons better than D1 milestone, with auto-adaptive icons feature reimplemented. -**Read the full announcement on our blog:** -https://lawnchair.app/blog/lawnchair-15-beta-1 +This release have been tested with: +* ☁️ Pixel 6 (Android 12.0) - Build: Ad-hoc +* ☁️ Pixel 6a (Android 12.1) - Build: Ad-hoc +* ☁️ Pixel 7 (Android 13) - Build: Ad-hoc +* ☁️ Pixel 9 (Android 15, Android 16.0) - Build: Ad-hoc +* ☁️ Pixel 9 Pro Fold (Android 14, Android 15) - Build: Ad-hoc +* ☁️ Vivo V40 (Android 15) - Build: Ad-hoc +* ☁️ Xiaomi MIX (Android 15) - Build: Ad-hoc +* 📱 Nothing (3a)-series (Android 15) - Build: pE-`15102025` +* 📱 Pixel 9 Pro XL (Android 16.0 QPR2 Beta 2) - Build: pE-`02102025` +* 📱 BLU View 5 Pro (Android 14) - Build: pE-`02102025` +* 📱🔥 Vivo Y21 (Android 12.0) - Build: pE-`08102025` + +> [!NOTE] +> QuickSwitch compatibility have not been tested at any time during the development of Bubble Tea! + +Compatibility list: + +| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | +|-------------|---------------------|--------------------| +| Android 8.1 | Android 12.0 | Android 12.1 | +| Android 9 | | Android 13 | +| Android 10 | | Android 14 | +| Android 11 | | Android 15 | +| | | Android 16 | + +#### Features + +* Enable All Apps Blur Flags on Phone (oops, forgot about the allAppsSheetForHandheld flag) +* Make Safe Mode check more reliable +* Smartspace Battery now reports battery charging status of Fast (more than 90% of 20 W) and Slow (less than 90% of 5 W) charging +* Show pseudonym version to Settings +* Resizing workspace calculate items position more accurately +* Update Lawnchair default grid size to 4×7 (or 4×6 with smartspace widget) +* Reimplement Hotseat background customisation +* Make haptic on a locked workspace use Google MSDL vibration +* Make Launcher3 colour more accurate to upstream Android 16 +* ProvideComposeSheetHandler now have expressive blur +* Lawnchair Settings now uses Material 3 Expressive +* Animate keyboard on/off state on app drawer search (Try enabling automatically show keyboard in app drawer settings and swipe up and down or directly tap “Apps list” in popup menu) -> (Backport not possible) +* Add LeakCanary check to all debug variant of the application +* [DEBUG] Launcher3 feature status diagnostic check in debug menu +* [Documentation] Add more visibility into both app certificate and SLSA verification for app authenticity check [VERIFICATION.md](VERIFICATION.md) +* [Documentation] Initial drafting of Improve documentation v6 (pave-path) +* [Launcher] Widget animations during resize +* [Iconloaderlib] Enable second hand for the clock app + +#### Fixes + +* Fix unable to access preview for icon style +* Popup's Arrow Theme now has the correct theme +* Widget should open normally after a workaround (C7evQZDJ) +* Fix (1) Search bar and Dock, (2) Folders and App Drawer settings didn't open due to init problems +* Lawnchair should hopefully remember what grid they should be using +* Most if not all of Lawnchair settings should be usable without crashes +* Correct Baseline Profile from old `market` to `play` variant, and now should calculate profile for `nightly` +* Fix Private Space crash when Lawnchair is set as Launcher due to flags only available on A16 +* Fix crash on a device with strict export receiver requirements on A14 +* Interactable widget crashing due to App Transition Manager being null (C7evQZDJ) +* Icon not responding to mouse cursor -> (Backported to Lawnchair 15) +* Rare NoSuchMethodError crash on IMS canImeRenderGesturalNavButtons +* [Lawnchair] Reimplement Bulk icons toggle +* SettingsCache crashing with SecurityException with unreadable keys (@hide) in Android 12 and newer (assume false) +* Assume flags `enableMovingContentIntoPrivateSpace` is false when ClassNotFoundException on Android 16 devices +* Rare NoSuchMethodError crash on SurfaceControl setEarlyWakeupStart and setEarlyWakeupEnd +* Properly align built-in smartspace in workspace +* Use WM Proxy from Lawnchair instead of System, fix Android 8.1/9/10/11/12.0/12.1 regarding SE, NSME like SystemBarUtils -> (dWkyIGw9), (reworked CllOXHJv) + * LawnchairWindowManagerProxy have been migrated to Dagger + * SystemWindowManagerProxy have been left unused +* [Lawnchair/Iconloaderlib] Update CustomAdaptiveIconDrawable to latest AOSP 13 +* [Iconloaderlib] Reset most of the changes to favour more AOSP 16_r02 code then Lawnchair (need rewrite) + * fix icon loaded in monochrome and always monochrome when it is not supposed to + * fix notification dots being twice the size with notification count +* [Lawnchair/Iconloaderlib] Reimplement Lawnchair Iconloaderlib (adaptive icons, monochrome, regular icon) + +#### Known Bugs +* Preview can't show device wallpaper -> (lIxkAYGg) +* IDP Preview doesn't refresh on settings change -> workaround is to hit apply and re-open the preview -> (ZbLX3438) +* Workspace theme doesn't refresh until restart -> (ZbLX3438) -> Fixed as part of (31lLEflf, 1MevNrzp) +* Lawnchair Colour can't handle restart causing default colour to be used instead -> Fixed? -> Properly fixed as part of (31lLEflf, 1MevNrzp) +* (Investigating) Work profile switch on widget selector *may* have reverted to Lawnchair 15 style +* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair + +### 🥞 Development 1 + +First development milestone! Basic launcher functionality should be stable enough. + +* Make Lawnchair Launcher launchable in Android 12.1, 13, 14, 15, 16 +* Remove two deprecated features (Use Material U Popup, and Use dot pagination) +* Add pseudonym version in debug settings +* Adapt Lawnchair code to Launcher3 16 +* Make basic features of Launcher work (App Drawer, Home Screen, Search, Folders, Widgets) +* Enable Material Expressive Flags (Try swiping through launcher page) +* Enable All Apps Blur Flags (Try opening All Apps on supported devices) +* Enable MSDL Haptics Feedback Flags (Try gliding widget or icons across the homescreen) +* Make Predictive Back Gesture work on Android 13, 14, 15, 16 (Try swiping left or right on gesture-based navigational) +* Programmatically set Safe Mode status + +#### Known Bugs + +* App Icon may sometimes render with less than 0 in height/width causing blank icon to be rendered and crashing ISE on customising icons -> (31lLEflf) +* Any Lawnchair settings using IDP will crash the launcher -> Fixed in Lawnchair 16 pE Development 2 +* Icon pack isn't usable -> (DXo69Qzd) +* Dynamic icons will not be themed by launcher +* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair + +### Snapshot 6 + +This is a developer-focused change log: + +This snapshot marks the first time Lawnchair 16 is able to compile and build an APK! + +* Fix all issues with Java files in both `lawn` and `src` +* Make Lawnchair compilable (with instant crash) +* Move to KSP for Dagger code generation + +### Snapshot 5 + +This is a developer-focused change log: + +This snapshot now able to compile all sources (Kotlin files only) + +* Fix MORE MORE MORE `lawn` issues +* Use Gradle Version Catalog for consistent dependency version across all modules (Full implementation @ LawnchairLauncher/Lawnchair#5753) +* Magically fix ASM Instrumentation issues (I didn't do anything, it just works now) +* Fix ALL the issues in kotlin stage (`compileLawnWithQuickstepNightlyDebugKotlin`) +* Reintroduce some features from Lawnchair +* Add compatibility checks and workarounds for them +* Fix most issues with Java files in both `lawn` and `src` + +### Snapshot 4 + +This is a developer-focused change log: + +This snapshot marks the first time Lawnchair 16 is able to compile all Launcher3 sources! + +* Add `MSDLLib` to `platform_frameworks_libs_systemui` +* Add `contextualeducationlib` to `platform_frameworks_libs_systemui` +* Fix issues in both `lawn` and `src` modules +* Fix AIDL sources +* Resolve Lawnchair/LC-TODO lists +* Merge `wmshell.shared` res with res from `wmshell` +* Consistent build reproducibility by specifying dependencies in `build.gradle` +* Some ASM Instrumentation issues (and re-add some…) +* Update documentations + +### Snapshot 3 + +This is a developer-focused change log: + +Not a lot of errors left to go! + +* Finish correctly implementing all Dagger functions (?) +* Merge Lawnchair 15 Beta 1 into Bubble Tea + * Support for 16-kb page size devices +* Repository rebased and dropped commit + * Switch back from turbine-combined variant to javac variant for prebuilt SystemUI-core-16 because issues with LFS + * MORE MORE fixes regarding turbine-combined to javac +* Publish `platform_frameworks_libs_systemui` to pe 16-dev branch +* ATLEAST check to almost every launcher3 source file +* `Utils` module (stripped) +* Fix Dagger duplicated classes (because of Dagger dependency ksp/kapt mixing) +* Build reproducibility improvements by specifying dependencies in `build.gradle` files +* Fix some of the issues in both `lawn` and `src` modules + +### Snapshot 2 + +This is a developer-focused change log: + +This snapshot milestone marked the first time Lawnchair now able to compile all supplementary +modules, `src` + `lawn` will be in Snapshot 5 or Development 1 milestone. + +* Merge flags +* Fix some issues with launcher3 sources. +* A temporary workaround with framworks.jar not adding in anim module. +* Shared not having access to animationlib. +* **Switch from javac variant to turbine-combined variant for prebuilt SystemUI-core-16**. + +### From Initial snapshot 0 and 1 + +This is a developer-focused change log: +* Prebuilt updated to Android 16-0.0_r2 (Android 16.0.0 Release 2) +* Submodule have also been refreshed to A16r2 +* Baklava Compatlib (QuickSwitch compatibility not guaranteed) +* Refreshed internal documentation like prebuilt, systemUI diff --git a/VERIFICATION.md b/VERIFICATION.md new file mode 100644 index 00000000000..90f65d56f8a --- /dev/null +++ b/VERIFICATION.md @@ -0,0 +1,33 @@ +# Lawnchair verification + +Lawnchair apk are cryptographically signed and can be verified using two verifications system. +1. GitHub or SLSA attestations +2. SHA256 of android app certificate + +## SLSA Attestation + +Lawnchair repository is SLSA-Level 2 compliance and can be verified using a provenance. + +> [!NOTE] +> It is possible to verify without GitHub CLI by cross-referencing check from +> [GitHub Attestation][github-attestation] with [Sigstore Rekor][sigstore-rekor] + +1. Install GitHub CLI +2. Download the APK and attestation from [GitHub Attestation][github-attestation] +3. Run `gh attestation verify APK -R LawnchairLauncher/lawnchair`, replace {APK} with the + actual APK file +4. Done + +## Android App Certificate + +Lawnchair have two app certificates: +* Google Play: + `47:AC:92:63:1C:60:35:13:CC:8D:26:DD:9C:FF:E0:71:9A:8B:36:55:44:DC:CE:C2:09:58:24:EC:25:61:20:A7` +* Elsewhere: + `74:7C:36:45:B3:57:25:8B:2E:23:E8:51:E5:3C:96:74:7F:E0:AD:D0:07:E5:BA:2C:D9:7E:8C:85:57:2E:4D:C5` + +On Android, using a verification app like [AppVerifier][3p-appverifier] can ease up the verifying process. + +[github-attestation]: https://github.com/LawnchairLauncher/lawnchair/attestations +[sigstore-rekor]: https://search.sigstore.dev/ +[3p-appverifier]: https://github.com/soupslurpr/AppVerifier diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index f1f9966f783..b083390b062 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -22,13 +22,6 @@ flag { bug: "316027081" } -flag { - name: "enable_grid_only_overview" - namespace: "launcher" - description: "Enable a grid-only overview without a focused task." - bug: "257950105" -} - flag { name: "enable_cursor_hover_states" namespace: "launcher" @@ -43,13 +36,6 @@ flag { bug: "302189128" } -flag { - name: "enable_overview_icon_menu" - namespace: "launcher" - description: "Enable updated overview icon and menu within task." - bug: "257950105" -} - flag { name: "enable_focus_outline" namespace: "launcher" @@ -237,13 +223,6 @@ flag { 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" @@ -256,7 +235,7 @@ flag { flag { name: "enable_fallback_overview_in_window" - namespace: "launcher" + namespace: "lse_desktop_experience" description: "Enables fallback recents opening inside of a window instead of an activity." bug: "292269949" } @@ -310,9 +289,418 @@ flag { } } +flag { + name: "enable_container_return_animations" + namespace: "launcher" + description: "Enables the container return animation mirroring launches." + bug: "341017746" +} + flag { name: "floating_search_bar" namespace: "launcher" description: "Search bar persists at the bottom of the screen across Launcher states" bug: "346408388" } + +flag { + name: "all_apps_sheet_for_handheld" + namespace: "launcher" + description: "All Apps will be presented on a bottom sheet in handheld mode" + bug: "374186088" +} + +flag { + name: "all_apps_blur" + namespace: "launcher" + description: "Content behind the all apps panel in Launcher will be blurred." + bug: "400827727" +} + +flag { + name: "multiline_search_bar" + namespace: "launcher" + description: "Search bar can wrap to multi-line" + bug: "341795751" +} + +flag { + name: "enable_multi_instance_menu_taskbar" + namespace: "launcher" + description: "Menu in Taskbar with options to launch and manage multiple instances of the same app" + bug: "355237285" +} + +flag { + name: "navigate_to_child_preference" + namespace: "launcher" + description: "Settings screen supports navigating to child preference if the key is not on the screen" + bug: "293390881" +} + +flag { + name: "use_new_icon_for_archived_apps" + namespace: "launcher" + description: "Archived apps will use new cloud icon in app title instead of overlay" + bug: "350758155" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "letter_fast_scroller" + namespace: "launcher" + description: "Change fast scroller to a lettered list" + bug: "358673724" +} + +flag { + name: "enable_desktop_task_alpha_animation" + namespace: "launcher" + description: "Enables the animation of the desktop task's background view" + bug: "320307666" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "ignore_three_finger_trackpad_for_nav_handle_long_press" + namespace: "launcher" + description: "Ignore three finger trackpad event for nav handle long press" + bug: "342143522" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "work_scheduler_in_work_profile" + namespace: "launcher" + description: "Enables work scheduler view above the work pause button in work profile." + bug: "361589193" +} + +flag { + name: "one_grid_specs" + namespace: "launcher" + description: "Defines the new specs for grids based on OneGrid" + bug: "364711064" +} + +flag { + name: "one_grid_mounted_mode" + namespace: "launcher" + description: "Support a fixed landscape mode for handheld devices" + bug: "364711735" +} + +flag { + name: "one_grid_rotation_handling" + namespace: "launcher" + description: "New landscape approach for the workspace using different rows and columns in landscape and portrait" + bug: "364711814" +} + +flag { + name: "grid_migration_refactor" + namespace: "launcher" + description: "Refactor grid migration such that the code is simpler to understand and update" + bug: "358399271" +} + +flag { + name: "accessibility_scroll_on_allapps" + namespace: "launcher" + description: "Scroll to item position if accessibility focused" + bug: "265392261" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_dismiss_prediction_undo" + namespace: "launcher" + description: "Show an 'Undo' snackbar when users dismiss a predicted hotseat item" + bug: "270394476" +} + +flag { + name: "enable_all_apps_button_in_hotseat" + namespace: "launcher" + description: "Enables displaying the all apps button in the hotseat." + bug: "270393897" +} + +flag { + name: "taskbar_quiet_mode_change_support" + namespace: "launcher" + description: "Support changing quiet mode for user profiles in taskbar." + bug: "345760034" +} + +flag { + name: "taskbar_overflow" + namespace: "launcher" + description: "Show recent apps in the taskbar overflow." + bug: "368119679" +} + +flag { + name: "enable_active_gesture_proto_log" + namespace: "launcher" + description: "Enables tracking active gesture logs in ProtoLog" + bug: "293182501" +} + +flag { + name: "enable_recents_window_proto_log" + namespace: "lse_desktop_experience" + description: "Enables tracking recents window logs in ProtoLog" + bug: "292269949" +} + +flag { + name: "enable_state_manager_proto_log" + namespace: "lse_desktop_experience" + description: "Enables tracking state manager logs in ProtoLog" + bug: "292269949" +} + +flag { + name: "coordinate_workspace_scale" + namespace: "launcher" + description: "Ensure that the workspace and hotseat scale doesn't conflict and transitions smoothly between launching and closing apps" + bug: "366403487" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_tiered_widgets_by_default_in_picker" + namespace: "launcher" + description: "Shows filtered set of widgets by default and an option to show all widgets in the widget picker" + bug: "356127021" +} + +flag { + name: "show_taskbar_pinning_popup_from_anywhere" + namespace: "launcher" + description: "Shows the pinning popup view after long-pressing or right-clicking anywhere on the pinned taskbar" + bug: "297325541" +} + +flag { + name: "enable_launcher_overview_in_window" + namespace: "lse_desktop_experience" + description: "Enables launcher recents opening inside of a window instead of being hosted in launcher activity." + bug: "292269949" +} + +flag { + name: "use_system_radius_for_app_widgets" + namespace: "launcher" + description: "Use system radius for enforced widget corners instead of a separate 16.dp value" + bug: "373351337" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_contrast_tiles" + namespace: "launcher" + description: "Enable launcher app contrast tiles." + bug: "341217082" +} + +flag { + name: "msdl_feedback" + namespace: "launcher" + description: "Enable MSDL feedback for Launcher interactions" + bug: "377496684" +} + +flag { + name: "enable_pinning_app_with_context_menu" + namespace: "launcher" + description: "Add options to pin/unpin to taskbar to app context menus." + bug: "375648361" +} + +flag { + name: "enable_launcher_icon_shapes" + namespace: "launcher" + description: "Enable launcher icon shape customizations" + bug: "348708061" +} + +flag { + name: "predictive_back_to_home_polish" + namespace: "launcher" + description: "Enables workspace reveal animation for predictive back-to-home" + bug: "382453424" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "predictive_back_to_home_blur" + namespace: "launcher" + description: "Adds blur for predictive back-to-home" + bug: "342178850" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_launcher_visual_refresh" + namespace: "launcher" + description: "Adds refresh for font family, app longpress menu icons, and pagination dots" + bug: "395145453" +} + +flag { + name: "gsf_res" + namespace: "launcher" + description: "Adds refresh for font family. Needs to be fixed to be used in resources." + bug: "395145453" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "restore_archived_shortcuts" + namespace: "launcher" + description: "Makes sure pre-archived pinned shortcuts also get restored" + bug: "375414891" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "restore_archived_app_icons_from_db" + namespace: "launcher" + description: "Restores pre-archived icons from db when available, mimicing promise icons" + bug: "391913214" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_mouse_interaction_changes" + namespace: "launcher" + description: "Changes mouse interaction behavior" + bug: "388897603" +} + +flag { + name: "enable_alt_tab_kqs_on_connected_displays" + namespace: "lse_desktop_experience" + description: "Enable Alt + Tab KQS support on connected displays" + bug: "394007677" +} + +flag { + name: "expressive_theme_in_taskbar_and_navigation" + namespace: "launcher" + description: "Enables the expressive theme and GSF font styles for Taskbar and Gesture Navigation" + bug: "394613212" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_strict_mode" + namespace: "launcher" + description: "Enable Strict Mode for the Launcher app" + bug: "394651876" +} + +flag { + name: "extendible_theme_manager" + namespace: "launcher" + description: "Enables custom theme manager in Launcher" + bug: "381897614" +} + +flag { + name: "enable_alt_tab_kqs_flatenning" + namespace: "lse_desktop_experience" + description: "Enable Alt + Tab KQS view to show apps in flattened structure" + bug: "382769617" +} + +flag { + name: "enable_gesture_nav_on_connected_displays" + namespace: "lse_desktop_experience" + description: "Enables gesture navigation handling on connected displays" + bug: "382130680" +} + +flag { + name: "enable_taskbar_behind_shade" + namespace: "lse_desktop_experience" + description: "Keeps taskbar behind notification shade when its pulled down" + bug: "343194358" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_scalability_for_desktop_experience" + namespace: "launcher" + description: "Enable more grid scale options on the launcher for desktop experience" + bug: "375491272" +} + +flag { + name: "enable_gesture_nav_horizontal_touch_slop" + namespace: "launcher" + description: "Enables horizontal touch slop checking in non-vertical fling navigation gestures" + bug: "394364217" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "sync_app_launch_with_taskbar_stash" + namespace: "launcher" + description: "Syncs the two animations (app launch, taskbar stash) so they play at the same time." + bug: "319162553" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "remove_apps_refresh_on_right_click" + namespace: "launcher" + description: "Remove predicted apps refresh on right click" + bug: "343650193" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_taskbar_for_direct_boot" + namespace: "launcher" + description: "Initializes parts of Taskbar before onUserUnlocked" + bug: "324485921" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/aconfig/launcher_accessibility.aconfig b/aconfig/launcher_accessibility.aconfig new file mode 100644 index 00000000000..13e1127efb8 --- /dev/null +++ b/aconfig/launcher_accessibility.aconfig @@ -0,0 +1,12 @@ +package: "com.android.launcher3" +container: "system_ext" + +flag { + name: "remove_exclude_from_screen_magnification_flag_usage" + namespace: "accessibility" + description: "Remove the WindowManager PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION flag usage in Launcher" + bug: "369019568" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/aconfig/launcher_growth.aconfig b/aconfig/launcher_growth.aconfig new file mode 100644 index 00000000000..a880538dbff --- /dev/null +++ b/aconfig/launcher_growth.aconfig @@ -0,0 +1,9 @@ +package: "com.android.launcher3" +container: "system_ext" + +flag { + name: "enable_growth_nudge" + namespace: "desktop_oobe" + description: "Add growth nudge in launcher" + bug: "396165728" +} diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig new file mode 100644 index 00000000000..d27a2143e11 --- /dev/null +++ b/aconfig/launcher_overview.aconfig @@ -0,0 +1,122 @@ +package: "com.android.launcher3" +container: "system_ext" + +flag { + name: "enable_grid_only_overview" + namespace: "launcher_overview" + description: "Enable a grid-only overview without a focused task." + bug: "360204325" +} + +flag { + name: "enable_overview_icon_menu" + namespace: "launcher_overview" + description: "Enable updated overview icon and menu within task." + bug: "360205084" +} + +flag { + name: "enable_refactor_task_thumbnail" + namespace: "launcher_overview" + description: "Enables rewritten version of TaskThumbnailViews in Overview" + bug: "331753115" +} + +flag { + name: "enable_hover_of_child_elements_in_taskview" + namespace: "launcher_overview" + description: "Enables child elements to receive hover events in TaskView and respond visually to those hover events." + bug: "342594235" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_large_desktop_windowing_tile" + namespace: "launcher_overview" + description: "Makes the desktop tiles larger and moves them to the front of the list in Overview." + bug: "357860832" +} + +flag { + name: "enable_overview_command_helper_timeout" + namespace: "launcher_overview" + description: "Enables OverviewCommandHelper new version with a timeout to prevent the queue to be unresponsive." + bug: "351122926" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_desktop_windowing_carousel_detach" + namespace: "launcher_overview" + description: "Makes the desktop windowing task carousel detaches from fullscreen task carousel during quickswitch." + bug: "353947917" +} + +flag { + name: "enable_desktop_exploded_view" + namespace: "launcher_overview" + description: "Enables the non-overlapping layout for desktop windows in Overview mode." + bug: "378011776" +} + +flag { + name: "enable_use_top_visible_activity_for_exclude_from_recent_task" + namespace: "launcher_overview" + description: "Enables using the top visible activity for exclude from recent task instead of the activity indicies." + bug: "342627272" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_expressive_dismiss_task_motion" + namespace: "launcher_overview" + description: "Enables expressive motion and animations for dismissing a task in Overview." + bug: "381239462" +} + +flag { + name: "enable_separate_external_display_tasks" + namespace: "launcher_overview" + description: "Enables separating external display tasks in Overview." + bug: "391311008" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_overview_on_connected_displays" + namespace: "launcher_overview" + description: "Enable overview on connected displays." + bug: "363251602" +} + +flag { + name: "enable_overview_background_wallpaper_blur" + namespace: "launcher_overview" + description: "Enable wallpaper blur in overview." + bug: "369975912" +} + +flag { + name: "enable_overview_desktop_tile_wallpaper_background" + namespace: "launcher_overview" + description: "Enable wallpaper background for desktop tasks in overview." + bug: "363257721" +} + +flag { + name: "enable_show_enabled_shortcuts_in_accessibility_menu" + namespace: "launcher_overview" + description: "Enables showing the same shortcuts in the Task menu as well as the accessibility actions menu" + bug: "383662632" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/androidx-lib/build.gradle b/androidx-lib/build.gradle index ec0c42af04c..29969470f5c 100644 --- a/androidx-lib/build.gradle +++ b/androidx-lib/build.gradle @@ -4,7 +4,7 @@ plugins { } android { - namespace "androidx.dynamicanimation.animation" + namespace = "androidx.dynamicanimation.animation" sourceSets { main { java.srcDirs = ['src'] @@ -12,5 +12,5 @@ android { } } -addFrameworkJar('framework-15.jar') +addFrameworkJar('framework-16.jar') compileOnlyCommonJars() diff --git a/baseline-profile/build.gradle b/baseline-profile/build.gradle index a39b8e290b7..f8bd7200eb5 100644 --- a/baseline-profile/build.gradle +++ b/baseline-profile/build.gradle @@ -7,7 +7,7 @@ plugins { } android { - namespace 'app.lawnchair.baseline' + namespace = 'app.lawnchair.baseline' defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -20,10 +20,16 @@ android { lawn { dimension = "app" } withQuickstep { dimension = "recents" } github { dimension = "channel" } - market { dimension = "channel" } + nightly { dimension = "channel" } + play { dimension = "channel" } } testOptions.managedDevices.devices { + pixel7Api36(ManagedVirtualDevice) { + device = "Pixel 7" + apiLevel = 36 + systemImageSource = "google" + } pixel6Api33(ManagedVirtualDevice) { device = "Pixel 6" apiLevel = 33 @@ -35,7 +41,7 @@ android { // This is the configuration block for the Baseline Profile plugin. // You can specify to run the generators on a managed devices or connected devices. baselineProfile { - managedDevices += "pixel6Api33" + managedDevices += ["pixel6Api33", "pixel7Api36"] useConnectedDevices = false } diff --git a/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt b/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt index a55a86c1bd3..c4fdc13af7a 100644 --- a/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt +++ b/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt @@ -1,5 +1,7 @@ package app.lawnchair.baseline +import android.os.Build +import androidx.annotation.RequiresApi import androidx.benchmark.macro.junit4.BaselineProfileRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest @@ -33,9 +35,11 @@ import org.junit.runner.RunWith class BaselineProfileGenerator { @get:Rule + @RequiresApi(Build.VERSION_CODES.P) val rule = BaselineProfileRule() @Test + @RequiresApi(Build.VERSION_CODES.P) fun generate() { rule.collect(Constants.PACKAGE_NAME) { // This block defines the app's critical user journey. Here we are interested in diff --git a/build.gradle b/build.gradle index 1dcdea8f4dc..4d5729447fa 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,6 @@ plugins { alias(libs.plugins.diffplug.spotless) } - allprojects { plugins.withType(AndroidBasePlugin).configureEach { android { @@ -32,12 +31,12 @@ allprojects { } defaultConfig { minSdk 26 - targetSdk 35 + targetSdk 36 vectorDrawables.useSupportLibrary = true } lint { - abortOnError true - checkReleaseBuilds false + abortOnError = true + checkReleaseBuilds = false } compileOptions { sourceCompatibility = libs.versions.jdkRelease.get() @@ -46,6 +45,7 @@ allprojects { } dependencies { implementation libs.androidx.core.ktx + implementation libs.androidx.core.animation } } @@ -102,7 +102,8 @@ allprojects { if (!frameworkJar.exists()) { throw new IllegalArgumentException("Framework jar path ${frameworkJar.path} doesn't exist") } - gradle.projectsEvaluated { + + project.afterEvaluate { tasks.withType(JavaCompile).configureEach { // Make sure the frameworkJar is always first in the classpath. classpath = files(frameworkJar, classpath) @@ -112,6 +113,7 @@ allprojects { libraries.setFrom files(frameworkJar, libraries) } } + dependencies { compileOnly files(frameworkJar) } @@ -119,9 +121,9 @@ allprojects { compileOnlyCommonJars = { dependencies { - compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-core.jar') - compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd.jar') - compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-15.jar') + compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-core-16.jar') + compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd-16.jar') + compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-16.jar') compileOnly projects.compatLib compileOnly projects.compatLib.compatLibVQ @@ -130,6 +132,7 @@ allprojects { compileOnly projects.compatLib.compatLibVT compileOnly projects.compatLib.compatLibVU compileOnly projects.compatLib.compatLibVV + compileOnly projects.compatLib.compatLibVBaklava } } } @@ -144,20 +147,22 @@ final def ciRef = System.getenv("GITHUB_REF") ?: "" final def ciRunNumber = System.getenv("GITHUB_RUN_NUMBER") ?: "" final def isReleaseBuild = ciBuild && ciRef.contains("beta") final def devReleaseName = ciBuild ? "Dev.(#${ciRunNumber})" : "Dev.(${buildCommit})" -final def version = "15" -final def releaseName = "Beta 1" +final def version = "16" +final def releaseName = "Development 3 Release 2" final def versionDisplayName = "${version}.${isReleaseBuild ? releaseName : devReleaseName}" final def majorVersion = versionDisplayName.split("\\.")[0] final def quickstepMinSdk = "29" -final def quickstepMaxSdk = "35" +final def quickstepMaxSdk = "36" android { - namespace "com.android.launcher3" + namespace = "com.android.launcher3" defaultConfig { - // Lawnchair Launcher 15.0 Beta 1 - // See CONTRIBUTING.md#versioning-scheme - versionCode 15_00_02_01 + /* + * Lawnchair Launcher 16.0 Development 3 Release 2 + * see CONTRIBUTING.md#versioning-scheme + */ + versionCode 16_00_01_03_02 versionName "${versionDisplayName}" buildConfigField "String", "VERSION_DISPLAY_NAME", "\"${versionDisplayName}\"" buildConfigField "String", "MAJOR_VERSION", "\"${majorVersion}\"" @@ -178,13 +183,13 @@ android { } androidResources { - generateLocaleConfig true + generateLocaleConfig = true } buildFeatures { - aidl true - buildConfig true - resValues true + aidl = true + buildConfig = true + resValues = true } packagingOptions.resources.excludes += [ @@ -228,7 +233,7 @@ android { buildTypes { all { - signingConfig releaseSigning + signingConfig = releaseSigning applicationVariants.configureEach { variant -> variant.outputs.configureEach { @@ -239,24 +244,30 @@ android { } } } - pseudoLocalesEnabled true + pseudoLocalesEnabled = true } debug { applicationIdSuffix ".debug" resValue("string", "derived_app_name", "Lawnchair (Debug)") + + manifestPlaceholders.quickstepMinSdk = "0" + manifestPlaceholders.quickstepMaxSdk = "100000" + buildConfigField "int", "QUICKSTEP_MIN_SDK", "0" + buildConfigField "int", "QUICKSTEP_MAX_SDK", "100000" } release { resValue("string", "derived_app_name", "Lawnchair") minifyEnabled true - shrinkResources true + shrinkResources = true + pseudoLocalesEnabled = false proguardFiles proguardFilesFromAosp + "proguard.pro" } } compileOptions { - coreLibraryDesugaringEnabled true + coreLibraryDesugaringEnabled = true } dependenciesInfo { @@ -290,7 +301,7 @@ android { play { applicationId "app.lawnchair.play" dimension "channel" - isDefault true + isDefault = true } configureEach { @@ -343,7 +354,7 @@ android { withQuickstep { res.srcDirs = ['quickstep/res', 'quickstep/recents_ui_overrides/res'] - java.srcDirs = ['quickstep/src', 'quickstep/recents_ui_overrides/src'] + java.srcDirs = ['quickstep/src', 'quickstep/dagger', 'quickstep/recents_ui_overrides/src', 'quickstep/src_protolog'] manifest.srcFile "quickstep/AndroidManifest.xml" } } @@ -356,12 +367,14 @@ composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_build_reports") } -addFrameworkJar('framework-15.jar') +addFrameworkJar('framework-16.jar') dependencies { implementation projects.iconloaderlib implementation projects.searchuilib implementation projects.animationlib + implementation projects.msdllib + implementation projects.contextualeducationlib // Recents lib dependency withQuickstepCompileOnly projects.hiddenApi @@ -373,6 +386,7 @@ dependencies { withQuickstepCompileOnly projects.plugin withQuickstepImplementation projects.plugincore withQuickstepCompileOnly projects.common + withQuickstepCompileOnly projects.utils // QuickSwitch Compat withQuickstepImplementation projects.compatLib @@ -382,13 +396,15 @@ dependencies { withQuickstepImplementation projects.compatLib.compatLibVT withQuickstepImplementation projects.compatLib.compatLibVU withQuickstepImplementation projects.compatLib.compatLibVV + withQuickstepImplementation projects.compatLib.compatLibVBaklava withQuickstepImplementation projects.wmshell withQuickstepImplementation projects.flags implementation libs.androidx.dynamicanimation - implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd-15.jar') - implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-15.jar') - withQuickstepCompileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'framework-15.jar') + implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd-16.jar') + implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-16.jar') + withQuickstepCompileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'framework-16.jar') + baselineProfile projects.baselineProfile coreLibraryDesugaring libs.android.desugarJdkLibs @@ -398,14 +414,21 @@ dependencies { implementation libs.androidx.recyclerview implementation libs.androidx.preference.ktx + implementation libs.javax.inject implementation libs.kotlinx.coroutines.android implementation libs.kotlinx.serialization.json implementation libs.chickenhook.restrictionbypass implementation libs.rikka.refine.runtime + implementation libs.androidx.activity.compose + implementation libs.androidx.constraintlayout + implementation libs.androidx.datastore.preferences + implementation platform(libs.compose.bom) implementation libs.compose.ui implementation libs.compose.ui.util + implementation libs.compose.ui.graphics + implementation libs.bundles.graphics debugImplementation libs.compose.ui.tooling implementation libs.compose.ui.tooling.preview implementation libs.compose.ui.google.fonts @@ -454,10 +477,17 @@ dependencies { implementation libs.hoko.blur implementation libs.androidx.window + + ksp libs.dagger.compiler + implementation libs.dagger.hilt.android + ksp libs.dagger.hilt.compiler + + debugImplementation libs.leakcanary.android } ksp { arg("room.schemaLocation", "$projectDir/schemas") + arg("dagger.hilt.disableModulesHaveInstallInCheck", "true") } diff --git a/checks/Android.bp b/checks/Android.bp new file mode 100644 index 00000000000..dfd701efe05 --- /dev/null +++ b/checks/Android.bp @@ -0,0 +1,46 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library_host { + name: "Launcher3LintChecker", + srcs: ["src/**/*.kt"], + plugins: ["auto_service_plugin"], + libs: [ + "auto_service_annotations", + "lint_api", + ], + kotlincflags: ["-Xjvm-default=all"], +} + +java_test_host { + name: "Launcher3LintCheckerTest", + defaults: ["AndroidLintCheckerTestDefaults"], + srcs: ["tests/**/*.kt"], + data: [ + ":androidx.annotation_annotation", + ":dagger2", + ":kotlinx-coroutines-core", + ], + device_common_data: [ + ":framework", + ], + static_libs: [ + "Launcher3LintChecker", + ], +} diff --git a/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt new file mode 100644 index 00000000000..37358bbc0b2 --- /dev/null +++ b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import org.jetbrains.uast.UClass + +/** Detector to identify custom usage of Android's Dialog within the Launcher3 codebase. */ +class CustomDialogDetector : Detector(), SourceCodeScanner { + + override fun applicableSuperClasses(): List { + return listOf(DIALOG_CLASS_NAME) + } + + override fun visitClass(context: JavaContext, declaration: UClass) { + val superTypeClassNames = declaration.superTypes.mapNotNull { it.resolve()?.qualifiedName } + if (superTypeClassNames.contains(DIALOG_CLASS_NAME)) { + context.report( + ISSUE, + declaration, + context.getNameLocation(declaration), + "Class implements Dialog", + ) + } + } + + companion object { + private const val DIALOG_CLASS_NAME = "android.app.Dialog" + + @JvmField + val ISSUE = + Issue.create( + id = "IllegalUseOfCustomDialog", + briefDescription = "dialogs should not be used in Launcher", + explanation = + """ + Don't use custom Dialogs within the launcher code base, instead consider utilizing + AbstractFloatingView to display content that should float above the launcher where + it can be correctly managed for dismissal. + """ + .trimIndent(), + category = Category.CORRECTNESS, + priority = 10, + severity = Severity.ERROR, + implementation = + Implementation(CustomDialogDetector::class.java, Scope.JAVA_FILE_SCOPE), + ) + } +} diff --git a/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt new file mode 100644 index 00000000000..c77c42bf5d1 --- /dev/null +++ b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.launcher3.lint + +import CustomDialogDetector +import com.android.tools.lint.client.api.IssueRegistry +import com.android.tools.lint.client.api.Vendor +import com.android.tools.lint.detector.api.CURRENT_API +import com.android.tools.lint.detector.api.Issue +import com.google.auto.service.AutoService + +@AutoService(IssueRegistry::class) +@Suppress("UnstableApiUsage") +class Launcher3IssueRegistry : IssueRegistry() { + override val issues: List + get() = listOf(CustomDialogDetector.ISSUE) + + override val api: Int + get() = CURRENT_API + + override val minApi: Int + get() = 8 + + override val vendor: Vendor = + Vendor( + vendorName = "Android", + feedbackUrl = "http://b/issues/new?component=78010", + contact = "abegovic@google.com", + ) +} diff --git a/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt new file mode 100644 index 00000000000..2a379530387 --- /dev/null +++ b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.launcher3.lint + +import CustomDialogDetector +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +/** Test for [CustomDialogDetector]. */ +class CustomDialogDetectorTest : Launcher3LintDetectorTest() { + override fun getDetector(): Detector = CustomDialogDetector() + + override fun getIssues(): List = listOf(CustomDialogDetector.ISSUE) + + @Test + fun classDoesNotExtendDialog_noViolation() { + lint() + .files( + TestFiles.kotlin( + """ + package test.pkg + + class SomeClass + """ + .trimIndent() + ), + *androidStubs, + ) + .issues(CustomDialogDetector.ISSUE) + .run() + .expectClean() + } + + @Test + fun classDoesExtendDialog_violation() { + lint() + .files( + TestFiles.kotlin( + """ + package test.pkg + + import android.app.Dialog + + class SomeClass(context: Context) : Dialog(context) + """ + .trimIndent() + ), + *androidStubs, + ) + .issues(CustomDialogDetector.ISSUE) + .run() + .expect( + (""" + src/test/pkg/SomeClass.kt:5: Error: Class implements Dialog [IllegalUseOfCustomDialog] + class SomeClass(context: Context) : Dialog(context) + ~~~~~~~~~ + 1 errors, 0 warnings + """) + .trimIndent() + ) + } +} diff --git a/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt new file mode 100644 index 00000000000..09085c7c95e --- /dev/null +++ b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.launcher3.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.checks.infrastructure.TestLintTask +import java.io.File +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** + * Abstract class that should be used by any test for launcher 3 lint detectors. + * + * When you write your test, ensure that you pass [androidStubs] as part of your [TestFiles] + * definition. + */ +@RunWith(JUnit4::class) +abstract class Launcher3LintDetectorTest : LintDetectorTest() { + + /** + * Customize the lint task to disable SDK usage completely. This ensures that running the tests + * in Android Studio has the same result as running the tests in atest + */ + override fun lint(): TestLintTask = + super.lint().allowMissingSdk(true).sdkHome(File("/dev/null")) + + companion object { + private val libraryNames = + arrayOf( + "androidx.annotation_annotation.jar", + "dagger2.jar", + "framework.jar", + "kotlinx-coroutines-core.jar", + ) + + /** + * This file contains stubs of framework APIs and System UI classes for testing purposes + * only. The stubs are not used in the lint detectors themselves. + */ + val androidStubs = + libraryNames + .map { TestFiles.LibraryReferenceTestFile(File(it).canonicalFile) } + .toTypedArray() + } +} diff --git a/compatLib/README.md b/compatLib/README.md index 2698ccba017..82a31bfe9a6 100644 --- a/compatLib/README.md +++ b/compatLib/README.md @@ -1,15 +1,24 @@ # Lawnchair Quickstep Compat Library -The `compatLib` library helps integrate Lawnchair with QuickSwitch while ensuring backward-compatibility with older Android versions. +The `compatLib` library helps integrate Lawnchair with Recents +(also known as QuickSwitch, Quickstep, or sometimes, Lawnstep) +while ensuring backward-compatibility with older Android versions. Each subdirectory of the `compatLib`, denoted by a letter (e.g., `compatLibVQ` for Android 10), refers to the compatibility code for that specific Android version. -| Library | Android version | -|-------------|-----------------| -| compatLibVQ | 10 | -| compatLibVR | 11 | -| compatLibVS | 12 | -| compatLibVT | 13 | -| compatLibVU | 14 | -| compatLibVV | 15 | +Starting with Android 16 and above, the `compatLib` will denoted by the codename of the Android version +(e.g., `compatLibVBaklava` for Android 16). + +| Library | Android version | +|-------------------|-----------------| +| compatLibVQ | 10 | +| compatLibVR | 11 | +| compatLibVS | 12 | +| compatLibVT | 13 | +| compatLibVU | 14 | +| compatLibVV | 15 | +| compatLibVBaklava | 16 | + +Keep in mind that this list does not guarantee Recents compatibility with your Android versions, +as the implementation may still be in progress or not fully functional. diff --git a/compatLib/build.gradle b/compatLib/build.gradle index 7813d645c47..0504b6bc790 100644 --- a/compatLib/build.gradle +++ b/compatLib/build.gradle @@ -3,10 +3,11 @@ plugins { } android { - namespace "app.lawnchair.compatlib" + buildToolsVersion "36.1.0" + namespace = "app.lawnchair.compatlib" buildFeatures { - aidl true + aidl = true } sourceSets { diff --git a/compatLib/compatLibVBaklava/build.gradle b/compatLib/compatLibVBaklava/build.gradle new file mode 100644 index 00000000000..1d11c426f55 --- /dev/null +++ b/compatLib/compatLibVBaklava/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'com.android.library' +} + +android { + namespace = 'app.lawnchair.compatlib.sixteen' +} + +addFrameworkJar('framework-16.jar') + +dependencies { + api projects.compatLib.compatLibVV +} diff --git a/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityManagerCompatVBaklava.java b/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityManagerCompatVBaklava.java new file mode 100644 index 00000000000..736200c667f --- /dev/null +++ b/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityManagerCompatVBaklava.java @@ -0,0 +1,7 @@ +package app.lawnchair.compatlib.sixteen; + +import androidx.annotation.RequiresApi; +import app.lawnchair.compatlib.fifteen.ActivityManagerCompatVV; + +@RequiresApi(36) +public class ActivityManagerCompatVBaklava extends ActivityManagerCompatVV {} diff --git a/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityOptionsCompatVBaklava.java b/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityOptionsCompatVBaklava.java new file mode 100644 index 00000000000..83da4a7ed6a --- /dev/null +++ b/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/ActivityOptionsCompatVBaklava.java @@ -0,0 +1,45 @@ +package app.lawnchair.compatlib.sixteen; + +import android.app.ActivityOptions; +import android.content.Context; +import android.os.Handler; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import app.lawnchair.compatlib.fifteen.ActivityOptionsCompatVV; + +@RequiresApi(36) +public class ActivityOptionsCompatVBaklava extends ActivityOptionsCompatVV { + @NonNull + @Override + public ActivityOptions makeCustomAnimation( + @NonNull Context context, + int enterResId, + int exitResId, + @NonNull final Handler callbackHandler, + @Nullable final Runnable startedListener, + @Nullable final Runnable finishedListener) { + return ActivityOptions.makeCustomAnimation( + context, + enterResId, + exitResId, + 0, + callbackHandler, + new ActivityOptions.OnAnimationStartedListener() { + @Override + public void onAnimationStarted(long elapsedRealTime) { + if (startedListener != null) { + startedListener.run(); + } + } + }, + new ActivityOptions.OnAnimationFinishedListener() { + @Override + public void onAnimationFinished(long elapsedRealTime) { + if (finishedListener != null) { + finishedListener.run(); + } + } + }); + } +} diff --git a/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java b/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java new file mode 100644 index 00000000000..223828f1fe9 --- /dev/null +++ b/compatLib/compatLibVBaklava/src/main/java/app/lawnchair/compatlib/sixteen/QuickstepCompatFactoryVBaklava.java @@ -0,0 +1,31 @@ +package app.lawnchair.compatlib.sixteen; + +import android.window.RemoteTransition; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import app.lawnchair.compatlib.ActivityManagerCompat; +import app.lawnchair.compatlib.ActivityOptionsCompat; +import app.lawnchair.compatlib.RemoteTransitionCompat; +import app.lawnchair.compatlib.fifteen.QuickstepCompatFactoryVV; + +@RequiresApi(36) +public class QuickstepCompatFactoryVBaklava extends QuickstepCompatFactoryVV { + + @NonNull + @Override + public ActivityManagerCompat getActivityManagerCompat() { + return new ActivityManagerCompatVBaklava(); + } + + @NonNull + @Override + public ActivityOptionsCompat getActivityOptionsCompat() { + return new ActivityOptionsCompatVBaklava(); + } + + @NonNull + @Override + public RemoteTransitionCompat getRemoteTransitionCompat() { + return RemoteTransition::new; + } +} diff --git a/compatLib/compatLibVQ/build.gradle b/compatLib/compatLibVQ/build.gradle index ef00fa6dd1d..5c5b2e58275 100644 --- a/compatLib/compatLibVQ/build.gradle +++ b/compatLib/compatLibVQ/build.gradle @@ -3,7 +3,7 @@ plugins { } android { - namespace "app.lawnchair.compatlib.ten" + namespace = "app.lawnchair.compatlib.ten" } addFrameworkJar('framework-10.jar') diff --git a/compatLib/compatLibVR/build.gradle b/compatLib/compatLibVR/build.gradle index a7fc5a3dca6..8d3ee700c85 100644 --- a/compatLib/compatLibVR/build.gradle +++ b/compatLib/compatLibVR/build.gradle @@ -3,7 +3,7 @@ plugins { } android { - namespace "app.lawnchair.compatlib.eleven" + namespace = "app.lawnchair.compatlib.eleven" } addFrameworkJar('framework-11.jar') diff --git a/compatLib/compatLibVS/build.gradle b/compatLib/compatLibVS/build.gradle index edc8d7699dd..11e80b17706 100644 --- a/compatLib/compatLibVS/build.gradle +++ b/compatLib/compatLibVS/build.gradle @@ -3,7 +3,7 @@ plugins { } android { - namespace "app.lawnchair.compatlib.twelve" + namespace = "app.lawnchair.compatlib.twelve" } addFrameworkJar('framework-12.jar') diff --git a/compatLib/compatLibVT/build.gradle b/compatLib/compatLibVT/build.gradle index 6ded6f55a8e..c7a466e8c33 100644 --- a/compatLib/compatLibVT/build.gradle +++ b/compatLib/compatLibVT/build.gradle @@ -3,7 +3,7 @@ plugins { } android { - namespace 'app.lawnchair.compatlib.thirteen' + namespace = 'app.lawnchair.compatlib.thirteen' } addFrameworkJar('framework-13.jar') diff --git a/compatLib/compatLibVU/build.gradle b/compatLib/compatLibVU/build.gradle index 33bfad7094a..5763ead16d6 100644 --- a/compatLib/compatLibVU/build.gradle +++ b/compatLib/compatLibVU/build.gradle @@ -3,7 +3,7 @@ plugins { } android { - namespace 'app.lawnchair.compatlib.fourteen' + namespace = 'app.lawnchair.compatlib.fourteen' } addFrameworkJar('framework-14.jar') diff --git a/compatLib/compatLibVV/build.gradle b/compatLib/compatLibVV/build.gradle index cd31b1a8056..16d2bf139f0 100644 --- a/compatLib/compatLibVV/build.gradle +++ b/compatLib/compatLibVV/build.gradle @@ -3,7 +3,7 @@ plugins { } android { - namespace 'app.lawnchair.compatlib.fifteen' + namespace = 'app.lawnchair.compatlib.fifteen' } addFrameworkJar('framework-15.jar') diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/compose/facade/core/BaseComposeFacade.kt similarity index 71% rename from quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt rename to compose/facade/core/BaseComposeFacade.kt index a8b5112860d..bc7ba4700e5 100644 --- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt +++ b/compose/facade/core/BaseComposeFacade.kt @@ -14,11 +14,13 @@ * limitations under the License. */ -package com.android.quickstep.task.viewmodel +package com.android.launcher3.compose.core -import kotlinx.coroutines.flow.MutableStateFlow +import android.content.Context +import android.view.View -class TaskViewData { - // This is typically a View concern but it is used to invalidate rendering in other Views - val scale = MutableStateFlow(1f) +interface BaseComposeFacade { + fun isComposeAvailable(): Boolean + + fun initComposeView(appContext: Context): View } diff --git a/compose/facade/disabled/ComposeFacade.kt b/compose/facade/disabled/ComposeFacade.kt new file mode 100644 index 00000000000..c1cbfff0c3c --- /dev/null +++ b/compose/facade/disabled/ComposeFacade.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.compose + +import android.content.Context +import android.view.View +import com.android.launcher3.compose.core.BaseComposeFacade + +object ComposeFacade : BaseComposeFacade { + override fun isComposeAvailable(): Boolean = false + + override fun initComposeView(appContext: Context): View { + error( + "Compose is not available. Make sure to check isComposeAvailable() before calling any" + + " other function on ComposeFacade." + ) + } +} diff --git a/compose/facade/enabled/ComposeFacade.kt b/compose/facade/enabled/ComposeFacade.kt new file mode 100644 index 00000000000..d98a979f292 --- /dev/null +++ b/compose/facade/enabled/ComposeFacade.kt @@ -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. + */ + +package com.android.launcher3.compose + +import android.content.Context +import android.view.View +import androidx.compose.ui.platform.ComposeView +import com.android.launcher3.compose.core.BaseComposeFacade + +object ComposeFacade : BaseComposeFacade { + override fun isComposeAvailable(): Boolean = true + + override fun initComposeView(appContext: Context): View = ComposeView(appContext) +} diff --git a/docs/assets/README.md b/docs/assets/README.md index 51ef527d050..30e6cb75683 100644 --- a/docs/assets/README.md +++ b/docs/assets/README.md @@ -1,21 +1,19 @@ -# Lawnchair Visual Guidelines +# Lawnchair Assets Guidelines This directory lists all the decoration & visual explainers used in the Lawnchair Documentation. -All assets created should use Material 3 design with `#47B84F` as source color and +All assets created should use Material 3 design with `#47B84F` as source color, and the [Inter](https://fonts.google.com/specimen/Inter) ([OFL v1.1](https://github.com/rsms/inter/?tab=OFL-1.1-1-ov-file#readme)) -typography. Visit the [Material 3 theme builder][material-theme-builder] for more information. +typography. -When creating device mockups for Lawnchair, make sure that you're using the latest commits of -Lawnchair or use Lawnchair Nightly as base. +Visit the [Material 3 theme builder][material-theme-builder] for more information on color. -## Device Mockup +## Device mockup -Use in: [README](/README.md) - -* Icon pack: [Lawnicons](https://github.com/LawnchairLauncher/lawnicons) -* Wallpaper: https://unsplash.com/photos/photography-of-green-leaves-ZVKr8wADhpc -* Color Extraction Technique: Tonal Spot from Lawnchair -* License: https://unsplash.com/license +When creating device mockups for Lawnchair, make sure the latest stable commits of Lawnchair is use +as base. All wallpapers, fonts and icon packs are allowed as long as they're free-to-use +under a permissive license like the licenses from Creative-Common. Using vanilla out-of-the-box +experience is recommended because they show the user what to expect from Lawnchair Launcher when +they first use it. [material-theme-builder]: https://material-foundation.github.io/material-theme-builder/?primary=%2347B84F&bodyFont=Inter&displayFont=Inter&colorMatch=false diff --git a/docs/assets/badge-github.png b/docs/assets/badge-github.png deleted file mode 100644 index 5129ff38b71..00000000000 Binary files a/docs/assets/badge-github.png and /dev/null differ diff --git a/docs/assets/badge-github.webp b/docs/assets/badge-github.webp new file mode 100644 index 00000000000..591b6b5a51a Binary files /dev/null and b/docs/assets/badge-github.webp differ diff --git a/docs/assets/badge-google-play.png b/docs/assets/badge-google-play.png deleted file mode 100644 index 1d17b6aa53a..00000000000 Binary files a/docs/assets/badge-google-play.png and /dev/null differ diff --git a/docs/assets/badge-google-play.webp b/docs/assets/badge-google-play.webp new file mode 100644 index 00000000000..0f0a32ac035 Binary files /dev/null and b/docs/assets/badge-google-play.webp differ diff --git a/docs/assets/badge-izzyondroid.png b/docs/assets/badge-izzyondroid.png deleted file mode 100644 index c97854de0b0..00000000000 Binary files a/docs/assets/badge-izzyondroid.png and /dev/null differ diff --git a/docs/assets/badge-izzyondroid.webp b/docs/assets/badge-izzyondroid.webp new file mode 100644 index 00000000000..e9ff9482088 Binary files /dev/null and b/docs/assets/badge-izzyondroid.webp differ diff --git a/docs/assets/badge-obtainium.png b/docs/assets/badge-obtainium.png deleted file mode 100644 index c5cee26fca8..00000000000 Binary files a/docs/assets/badge-obtainium.png and /dev/null differ diff --git a/docs/assets/badge-obtainium.webp b/docs/assets/badge-obtainium.webp new file mode 100644 index 00000000000..2629c812c2e Binary files /dev/null and b/docs/assets/badge-obtainium.webp differ diff --git a/docs/assets/device-frame.png b/docs/assets/device-frame.png deleted file mode 100644 index 46a4d3fa126..00000000000 Binary files a/docs/assets/device-frame.png and /dev/null differ diff --git a/docs/assets/device-frame.webp b/docs/assets/device-frame.webp new file mode 100644 index 00000000000..c90e3539341 Binary files /dev/null and b/docs/assets/device-frame.webp differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/device-frame.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/device-frame.jpg index 70a926b3c8d..e67c6542414 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/device-frame.jpg and b/fastlane/metadata/android/en-US/images/phoneScreenshots/device-frame.jpg differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 7af858c97b3..7a8ca7d1d36 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Homescreen based on on Launcher3 from AOSP \ No newline at end of file +Homescreen based on Launcher3 from AOSP diff --git a/flags/README.md b/flags/README.md new file mode 100644 index 00000000000..96d0d9e0ee8 --- /dev/null +++ b/flags/README.md @@ -0,0 +1,9 @@ +# Launcher3 Flags + +Run + +m Launcher3 + +out\soong\.intermediates\packages\apps\Launcher3\aconfig\com_android_launcher3_flags_lib\android_common\gen\com_android_launcher3_flags_lib.srcjar + +Turn it into JAR and paste the contents into the current Launcher3 flags. diff --git a/flags/build.gradle b/flags/build.gradle index 0dd01c027a8..2b00f3f5b17 100644 --- a/flags/build.gradle +++ b/flags/build.gradle @@ -4,7 +4,7 @@ plugins { } android { - namespace "com.android.launcher3.flags" + namespace = "com.android.launcher3.flags" sourceSets { main { java.srcDirs = ['src'] @@ -12,5 +12,5 @@ android { } } -addFrameworkJar('framework-15.jar') +addFrameworkJar('framework-16.jar') compileOnlyCommonJars() diff --git a/flags/src/com/android/launcher3/CustomFeatureFlags.java b/flags/src/com/android/launcher3/CustomFeatureFlags.java index e3b74b8dbc3..2db6820c48a 100644 --- a/flags/src/com/android/launcher3/CustomFeatureFlags.java +++ b/flags/src/com/android/launcher3/CustomFeatureFlags.java @@ -1,12 +1,12 @@ package com.android.launcher3; +// TODO(b/303773055): Remove the annotation after access issue is resolved. import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; - /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -16,303 +16,807 @@ public CustomFeatureFlags(BiPredicate> getValueI mGetValueImpl = getValueImpl; } @Override + + public boolean accessibilityScrollOnAllapps() { + return getValue(Flags.FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS, + FeatureFlags::accessibilityScrollOnAllapps); + } + + @Override + + public boolean allAppsBlur() { + return getValue(Flags.FLAG_ALL_APPS_BLUR, + FeatureFlags::allAppsBlur); + } + + @Override + + public boolean allAppsSheetForHandheld() { + return getValue(Flags.FLAG_ALL_APPS_SHEET_FOR_HANDHELD, + FeatureFlags::allAppsSheetForHandheld); + } + + @Override + + public boolean coordinateWorkspaceScale() { + return getValue(Flags.FLAG_COORDINATE_WORKSPACE_SCALE, + FeatureFlags::coordinateWorkspaceScale); + } + + @Override + + public boolean enableActiveGestureProtoLog() { + return getValue(Flags.FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG, + FeatureFlags::enableActiveGestureProtoLog); + } + + @Override + public boolean enableAddAppWidgetViaConfigActivityV2() { return getValue(Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, - FeatureFlags::enableAddAppWidgetViaConfigActivityV2); + FeatureFlags::enableAddAppWidgetViaConfigActivityV2); } @Override + public boolean enableAdditionalHomeAnimations() { return getValue(Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, - FeatureFlags::enableAdditionalHomeAnimations); + FeatureFlags::enableAdditionalHomeAnimations); + } + + @Override + + public boolean enableAllAppsButtonInHotseat() { + return getValue(Flags.FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT, + FeatureFlags::enableAllAppsButtonInHotseat); } @Override + + public boolean enableAltTabKqsFlatenning() { + return getValue(Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING, + FeatureFlags::enableAltTabKqsFlatenning); + } + + @Override + + public boolean enableAltTabKqsOnConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS, + FeatureFlags::enableAltTabKqsOnConnectedDisplays); + } + + @Override + public boolean enableCategorizedWidgetSuggestions() { return getValue(Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, - FeatureFlags::enableCategorizedWidgetSuggestions); + FeatureFlags::enableCategorizedWidgetSuggestions); + } + + @Override + + public boolean enableContainerReturnAnimations() { + return getValue(Flags.FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS, + FeatureFlags::enableContainerReturnAnimations); + } + + @Override + + public boolean enableContrastTiles() { + return getValue(Flags.FLAG_ENABLE_CONTRAST_TILES, + FeatureFlags::enableContrastTiles); } @Override + public boolean enableCursorHoverStates() { return getValue(Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, - FeatureFlags::enableCursorHoverStates); + FeatureFlags::enableCursorHoverStates); + } + + @Override + + public boolean enableDesktopExplodedView() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW, + FeatureFlags::enableDesktopExplodedView); } @Override + + public boolean enableDesktopTaskAlphaAnimation() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION, + FeatureFlags::enableDesktopTaskAlphaAnimation); + } + + @Override + + public boolean enableDesktopWindowingCarouselDetach() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH, + FeatureFlags::enableDesktopWindowingCarouselDetach); + } + + @Override + + public boolean enableDismissPredictionUndo() { + return getValue(Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO, + FeatureFlags::enableDismissPredictionUndo); + } + + @Override + public boolean enableExpandingPauseWorkButton() { return getValue(Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, - FeatureFlags::enableExpandingPauseWorkButton); + FeatureFlags::enableExpandingPauseWorkButton); + } + + @Override + + public boolean enableExpressiveDismissTaskMotion() { + return getValue(Flags.FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION, + FeatureFlags::enableExpressiveDismissTaskMotion); } @Override + public boolean enableFallbackOverviewInWindow() { return getValue(Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, - FeatureFlags::enableFallbackOverviewInWindow); + FeatureFlags::enableFallbackOverviewInWindow); } @Override + public boolean enableFirstScreenBroadcastArchivingExtras() { return getValue(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, - FeatureFlags::enableFirstScreenBroadcastArchivingExtras); + FeatureFlags::enableFirstScreenBroadcastArchivingExtras); } @Override + public boolean enableFocusOutline() { return getValue(Flags.FLAG_ENABLE_FOCUS_OUTLINE, - FeatureFlags::enableFocusOutline); + FeatureFlags::enableFocusOutline); } @Override + public boolean enableGeneratedPreviews() { return getValue(Flags.FLAG_ENABLE_GENERATED_PREVIEWS, - FeatureFlags::enableGeneratedPreviews); + FeatureFlags::enableGeneratedPreviews); + } + + @Override + + public boolean enableGestureNavHorizontalTouchSlop() { + return getValue(Flags.FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP, + FeatureFlags::enableGestureNavHorizontalTouchSlop); + } + + @Override + + public boolean enableGestureNavOnConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS, + FeatureFlags::enableGestureNavOnConnectedDisplays); } @Override + public boolean enableGridMigrationFix() { return getValue(Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, - FeatureFlags::enableGridMigrationFix); + FeatureFlags::enableGridMigrationFix); } @Override + public boolean enableGridOnlyOverview() { return getValue(Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - FeatureFlags::enableGridOnlyOverview); + FeatureFlags::enableGridOnlyOverview); + } + + @Override + + public boolean enableGrowthNudge() { + return getValue(Flags.FLAG_ENABLE_GROWTH_NUDGE, + FeatureFlags::enableGrowthNudge); } @Override + public boolean enableHandleDelayedGestureCallbacks() { return getValue(Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, - FeatureFlags::enableHandleDelayedGestureCallbacks); + FeatureFlags::enableHandleDelayedGestureCallbacks); } @Override + public boolean enableHomeTransitionListener() { return getValue(Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, - FeatureFlags::enableHomeTransitionListener); + FeatureFlags::enableHomeTransitionListener); + } + + @Override + + public boolean enableHoverOfChildElementsInTaskview() { + return getValue(Flags.FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW, + FeatureFlags::enableHoverOfChildElementsInTaskview); + } + + @Override + + public boolean enableLargeDesktopWindowingTile() { + return getValue(Flags.FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE, + FeatureFlags::enableLargeDesktopWindowingTile); } @Override + public boolean enableLauncherBrMetricsFixed() { return getValue(Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, - FeatureFlags::enableLauncherBrMetricsFixed); + FeatureFlags::enableLauncherBrMetricsFixed); } @Override + + public boolean enableLauncherIconShapes() { + return getValue(Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES, + FeatureFlags::enableLauncherIconShapes); + } + + @Override + + public boolean enableLauncherOverviewInWindow() { + return getValue(Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, + FeatureFlags::enableLauncherOverviewInWindow); + } + + @Override + + public boolean enableLauncherVisualRefresh() { + return getValue(Flags.FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH, + FeatureFlags::enableLauncherVisualRefresh); + } + + @Override + + public boolean enableMouseInteractionChanges() { + return getValue(Flags.FLAG_ENABLE_MOUSE_INTERACTION_CHANGES, + FeatureFlags::enableMouseInteractionChanges); + } + + @Override + + public boolean enableMultiInstanceMenuTaskbar() { + return getValue(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, + FeatureFlags::enableMultiInstanceMenuTaskbar); + } + + @Override + public boolean enableNarrowGridRestore() { return getValue(Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, - FeatureFlags::enableNarrowGridRestore); + FeatureFlags::enableNarrowGridRestore); } @Override + + public boolean enableOverviewBackgroundWallpaperBlur() { + return getValue(Flags.FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR, + FeatureFlags::enableOverviewBackgroundWallpaperBlur); + } + + @Override + + public boolean enableOverviewCommandHelperTimeout() { + return getValue(Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT, + FeatureFlags::enableOverviewCommandHelperTimeout); + } + + @Override + + public boolean enableOverviewDesktopTileWallpaperBackground() { + return getValue(Flags.FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND, + FeatureFlags::enableOverviewDesktopTileWallpaperBackground); + } + + @Override + public boolean enableOverviewIconMenu() { return getValue(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, - FeatureFlags::enableOverviewIconMenu); + FeatureFlags::enableOverviewIconMenu); + } + + @Override + + public boolean enableOverviewOnConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS, + FeatureFlags::enableOverviewOnConnectedDisplays); + } + + @Override + + public boolean enablePinningAppWithContextMenu() { + return getValue(Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU, + FeatureFlags::enablePinningAppWithContextMenu); } @Override + public boolean enablePredictiveBackGesture() { return getValue(Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, - FeatureFlags::enablePredictiveBackGesture); + FeatureFlags::enablePredictiveBackGesture); } @Override + public boolean enablePrivateSpace() { return getValue(Flags.FLAG_ENABLE_PRIVATE_SPACE, - FeatureFlags::enablePrivateSpace); + FeatureFlags::enablePrivateSpace); } @Override + public boolean enablePrivateSpaceInstallShortcut() { return getValue(Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, - FeatureFlags::enablePrivateSpaceInstallShortcut); + FeatureFlags::enablePrivateSpaceInstallShortcut); } @Override + public boolean enableRebootUnlockAnimation() { return getValue(Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, - FeatureFlags::enableRebootUnlockAnimation); + FeatureFlags::enableRebootUnlockAnimation); } @Override + public boolean enableRecentsInTaskbar() { return getValue(Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, - FeatureFlags::enableRecentsInTaskbar); + FeatureFlags::enableRecentsInTaskbar); + } + + @Override + + public boolean enableRecentsWindowProtoLog() { + return getValue(Flags.FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG, + FeatureFlags::enableRecentsWindowProtoLog); } @Override + public boolean enableRefactorTaskThumbnail() { return getValue(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, - FeatureFlags::enableRefactorTaskThumbnail); + FeatureFlags::enableRefactorTaskThumbnail); } @Override + public boolean enableResponsiveWorkspace() { return getValue(Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, - FeatureFlags::enableResponsiveWorkspace); + FeatureFlags::enableResponsiveWorkspace); } @Override + + public boolean enableScalabilityForDesktopExperience() { + return getValue(Flags.FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE, + FeatureFlags::enableScalabilityForDesktopExperience); + } + + @Override + public boolean enableScalingRevealHomeAnimation() { return getValue(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, - FeatureFlags::enableScalingRevealHomeAnimation); + FeatureFlags::enableScalingRevealHomeAnimation); + } + + @Override + + public boolean enableSeparateExternalDisplayTasks() { + return getValue(Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS, + FeatureFlags::enableSeparateExternalDisplayTasks); } @Override + public boolean enableShortcutDontSuggestApp() { return getValue(Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, - FeatureFlags::enableShortcutDontSuggestApp); + FeatureFlags::enableShortcutDontSuggestApp); + } + + @Override + + public boolean enableShowEnabledShortcutsInAccessibilityMenu() { + return getValue(Flags.FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU, + FeatureFlags::enableShowEnabledShortcutsInAccessibilityMenu); } @Override + public boolean enableSmartspaceAsAWidget() { return getValue(Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, - FeatureFlags::enableSmartspaceAsAWidget); + FeatureFlags::enableSmartspaceAsAWidget); } @Override + public boolean enableSmartspaceRemovalToggle() { return getValue(Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, - FeatureFlags::enableSmartspaceRemovalToggle); + FeatureFlags::enableSmartspaceRemovalToggle); + } + + @Override + + public boolean enableStateManagerProtoLog() { + return getValue(Flags.FLAG_ENABLE_STATE_MANAGER_PROTO_LOG, + FeatureFlags::enableStateManagerProtoLog); + } + + @Override + + public boolean enableStrictMode() { + return getValue(Flags.FLAG_ENABLE_STRICT_MODE, + FeatureFlags::enableStrictMode); } @Override + public boolean enableSupportForArchiving() { return getValue(Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, - FeatureFlags::enableSupportForArchiving); + FeatureFlags::enableSupportForArchiving); } @Override + public boolean enableTabletTwoPanePickerV2() { return getValue(Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, - FeatureFlags::enableTabletTwoPanePickerV2); + FeatureFlags::enableTabletTwoPanePickerV2); } @Override + + public boolean enableTaskbarBehindShade() { + return getValue(Flags.FLAG_ENABLE_TASKBAR_BEHIND_SHADE, + FeatureFlags::enableTaskbarBehindShade); + } + + @Override + public boolean enableTaskbarCustomization() { return getValue(Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, - FeatureFlags::enableTaskbarCustomization); + FeatureFlags::enableTaskbarCustomization); + } + + @Override + + public boolean enableTaskbarForDirectBoot() { + return getValue(Flags.FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT, + FeatureFlags::enableTaskbarForDirectBoot); } @Override + public boolean enableTaskbarNoRecreate() { return getValue(Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, - FeatureFlags::enableTaskbarNoRecreate); + FeatureFlags::enableTaskbarNoRecreate); } @Override + public boolean enableTaskbarPinning() { return getValue(Flags.FLAG_ENABLE_TASKBAR_PINNING, - FeatureFlags::enableTaskbarPinning); + FeatureFlags::enableTaskbarPinning); } @Override + + public boolean enableTieredWidgetsByDefaultInPicker() { + return getValue(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER, + FeatureFlags::enableTieredWidgetsByDefaultInPicker); + } + + @Override + public boolean enableTwoPaneLauncherSettings() { return getValue(Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, - FeatureFlags::enableTwoPaneLauncherSettings); + FeatureFlags::enableTwoPaneLauncherSettings); } @Override + public boolean enableTwolineAllapps() { return getValue(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, - FeatureFlags::enableTwolineAllapps); + FeatureFlags::enableTwolineAllapps); } @Override + public boolean enableTwolineToggle() { return getValue(Flags.FLAG_ENABLE_TWOLINE_TOGGLE, - FeatureFlags::enableTwolineToggle); + FeatureFlags::enableTwolineToggle); } @Override + public boolean enableUnfoldStateAnimation() { return getValue(Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, - FeatureFlags::enableUnfoldStateAnimation); + FeatureFlags::enableUnfoldStateAnimation); } @Override + public boolean enableUnfoldedTwoPanePicker() { return getValue(Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, - FeatureFlags::enableUnfoldedTwoPanePicker); + FeatureFlags::enableUnfoldedTwoPanePicker); } @Override + + public boolean enableUseTopVisibleActivityForExcludeFromRecentTask() { + return getValue(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK, + FeatureFlags::enableUseTopVisibleActivityForExcludeFromRecentTask); + } + + @Override + public boolean enableWidgetTapToAdd() { return getValue(Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, - FeatureFlags::enableWidgetTapToAdd); + FeatureFlags::enableWidgetTapToAdd); } @Override + public boolean enableWorkspaceInflation() { return getValue(Flags.FLAG_ENABLE_WORKSPACE_INFLATION, - FeatureFlags::enableWorkspaceInflation); + FeatureFlags::enableWorkspaceInflation); } @Override + public boolean enabledFoldersInAllApps() { return getValue(Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, - FeatureFlags::enabledFoldersInAllApps); + FeatureFlags::enabledFoldersInAllApps); + } + + @Override + + public boolean expressiveThemeInTaskbarAndNavigation() { + return getValue(Flags.FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION, + FeatureFlags::expressiveThemeInTaskbarAndNavigation); + } + + @Override + + public boolean extendibleThemeManager() { + return getValue(Flags.FLAG_EXTENDIBLE_THEME_MANAGER, + FeatureFlags::extendibleThemeManager); } @Override + public boolean floatingSearchBar() { return getValue(Flags.FLAG_FLOATING_SEARCH_BAR, - FeatureFlags::floatingSearchBar); + FeatureFlags::floatingSearchBar); } @Override + public boolean forceMonochromeAppIcons() { return getValue(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, - FeatureFlags::forceMonochromeAppIcons); + FeatureFlags::forceMonochromeAppIcons); } @Override + + public boolean gridMigrationRefactor() { + return getValue(Flags.FLAG_GRID_MIGRATION_REFACTOR, + FeatureFlags::gridMigrationRefactor); + } + + @Override + + public boolean gsfRes() { + return getValue(Flags.FLAG_GSF_RES, + FeatureFlags::gsfRes); + } + + @Override + + public boolean ignoreThreeFingerTrackpadForNavHandleLongPress() { + return getValue(Flags.FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS, + FeatureFlags::ignoreThreeFingerTrackpadForNavHandleLongPress); + } + + @Override + + public boolean letterFastScroller() { + return getValue(Flags.FLAG_LETTER_FAST_SCROLLER, + FeatureFlags::letterFastScroller); + } + + @Override + + public boolean msdlFeedback() { + return getValue(Flags.FLAG_MSDL_FEEDBACK, + FeatureFlags::msdlFeedback); + } + + @Override + + public boolean multilineSearchBar() { + return getValue(Flags.FLAG_MULTILINE_SEARCH_BAR, + FeatureFlags::multilineSearchBar); + } + + @Override + + public boolean navigateToChildPreference() { + return getValue(Flags.FLAG_NAVIGATE_TO_CHILD_PREFERENCE, + FeatureFlags::navigateToChildPreference); + } + + @Override + + public boolean oneGridMountedMode() { + return getValue(Flags.FLAG_ONE_GRID_MOUNTED_MODE, + FeatureFlags::oneGridMountedMode); + } + + @Override + + public boolean oneGridRotationHandling() { + return getValue(Flags.FLAG_ONE_GRID_ROTATION_HANDLING, + FeatureFlags::oneGridRotationHandling); + } + + @Override + + public boolean oneGridSpecs() { + return getValue(Flags.FLAG_ONE_GRID_SPECS, + FeatureFlags::oneGridSpecs); + } + + @Override + + public boolean predictiveBackToHomeBlur() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_TO_HOME_BLUR, + FeatureFlags::predictiveBackToHomeBlur); + } + + @Override + + public boolean predictiveBackToHomePolish() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_TO_HOME_POLISH, + FeatureFlags::predictiveBackToHomePolish); + } + + @Override + public boolean privateSpaceAddFloatingMaskView() { return getValue(Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, - FeatureFlags::privateSpaceAddFloatingMaskView); + FeatureFlags::privateSpaceAddFloatingMaskView); } @Override + public boolean privateSpaceAnimation() { return getValue(Flags.FLAG_PRIVATE_SPACE_ANIMATION, - FeatureFlags::privateSpaceAnimation); + FeatureFlags::privateSpaceAnimation); } @Override + public boolean privateSpaceAppInstallerButton() { return getValue(Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, - FeatureFlags::privateSpaceAppInstallerButton); + FeatureFlags::privateSpaceAppInstallerButton); } @Override + public boolean privateSpaceRestrictAccessibilityDrag() { return getValue(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, - FeatureFlags::privateSpaceRestrictAccessibilityDrag); + FeatureFlags::privateSpaceRestrictAccessibilityDrag); } @Override + public boolean privateSpaceRestrictItemDrag() { return getValue(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, - FeatureFlags::privateSpaceRestrictItemDrag); + FeatureFlags::privateSpaceRestrictItemDrag); } @Override + public boolean privateSpaceSysAppsSeparation() { return getValue(Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, - FeatureFlags::privateSpaceSysAppsSeparation); + FeatureFlags::privateSpaceSysAppsSeparation); } @Override + + public boolean removeAppsRefreshOnRightClick() { + return getValue(Flags.FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK, + FeatureFlags::removeAppsRefreshOnRightClick); + } + + @Override + + public boolean removeExcludeFromScreenMagnificationFlagUsage() { + return getValue(Flags.FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE, + FeatureFlags::removeExcludeFromScreenMagnificationFlagUsage); + } + + @Override + + public boolean restoreArchivedAppIconsFromDb() { + return getValue(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB, + FeatureFlags::restoreArchivedAppIconsFromDb); + } + + @Override + + public boolean restoreArchivedShortcuts() { + return getValue(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS, + FeatureFlags::restoreArchivedShortcuts); + } + + @Override + + public boolean showTaskbarPinningPopupFromAnywhere() { + return getValue(Flags.FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE, + FeatureFlags::showTaskbarPinningPopupFromAnywhere); + } + + @Override + + public boolean syncAppLaunchWithTaskbarStash() { + return getValue(Flags.FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH, + FeatureFlags::syncAppLaunchWithTaskbarStash); + } + + @Override + + public boolean taskbarOverflow() { + return getValue(Flags.FLAG_TASKBAR_OVERFLOW, + FeatureFlags::taskbarOverflow); + } + + @Override + + public boolean taskbarQuietModeChangeSupport() { + return getValue(Flags.FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT, + FeatureFlags::taskbarQuietModeChangeSupport); + } + + @Override + public boolean useActivityOverlay() { return getValue(Flags.FLAG_USE_ACTIVITY_OVERLAY, - FeatureFlags::useActivityOverlay); + FeatureFlags::useActivityOverlay); + } + + @Override + + public boolean useNewIconForArchivedApps() { + return getValue(Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS, + FeatureFlags::useNewIconForArchivedApps); + } + + @Override + + public boolean useSystemRadiusForAppWidgets() { + return getValue(Flags.FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS, + FeatureFlags::useSystemRadiusForAppWidgets); + } + + @Override + + public boolean workSchedulerInWorkProfile() { + return getValue(Flags.FLAG_WORK_SCHEDULER_IN_WORK_PROFILE, + FeatureFlags::workSchedulerInWorkProfile); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } @@ -327,65 +831,240 @@ protected boolean getValue(String flagName, Predicate getter) { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, - Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, - Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, - Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, - Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, - Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, - Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, - Flags.FLAG_ENABLE_FOCUS_OUTLINE, - Flags.FLAG_ENABLE_GENERATED_PREVIEWS, - Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, - Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, - Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, - Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, - Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, - Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, - Flags.FLAG_ENABLE_PRIVATE_SPACE, - Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, - Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, - Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, - Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, - Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, - Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, - Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, - Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, - Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, - Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, - Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, - Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, - Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, - Flags.FLAG_ENABLE_TASKBAR_PINNING, - Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, - Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, - Flags.FLAG_ENABLE_TWOLINE_TOGGLE, - Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, - Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, - Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, - Flags.FLAG_ENABLE_WORKSPACE_INFLATION, - Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, - Flags.FLAG_FLOATING_SEARCH_BAR, - Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, - Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, - Flags.FLAG_PRIVATE_SPACE_ANIMATION, - Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, - Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, - Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, - Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, - Flags.FLAG_USE_ACTIVITY_OVERLAY + Flags.FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS, + Flags.FLAG_ALL_APPS_BLUR, + Flags.FLAG_ALL_APPS_SHEET_FOR_HANDHELD, + Flags.FLAG_COORDINATE_WORKSPACE_SCALE, + Flags.FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG, + Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, + Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, + Flags.FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT, + Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING, + Flags.FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, + Flags.FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS, + Flags.FLAG_ENABLE_CONTRAST_TILES, + Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, + Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW, + Flags.FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH, + Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO, + Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, + Flags.FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION, + Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, + Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, + Flags.FLAG_ENABLE_FOCUS_OUTLINE, + Flags.FLAG_ENABLE_GENERATED_PREVIEWS, + Flags.FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP, + Flags.FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, + Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, + Flags.FLAG_ENABLE_GROWTH_NUDGE, + Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, + Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, + Flags.FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW, + Flags.FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE, + Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, + Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES, + Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, + Flags.FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH, + Flags.FLAG_ENABLE_MOUSE_INTERACTION_CHANGES, + Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, + Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, + Flags.FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR, + Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT, + Flags.FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, + Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU, + Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, + Flags.FLAG_ENABLE_PRIVATE_SPACE, + Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, + Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, + Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, + Flags.FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG, + Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, + Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, + Flags.FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE, + Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, + Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS, + Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, + Flags.FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU, + Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, + Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, + Flags.FLAG_ENABLE_STATE_MANAGER_PROTO_LOG, + Flags.FLAG_ENABLE_STRICT_MODE, + Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, + Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, + Flags.FLAG_ENABLE_TASKBAR_BEHIND_SHADE, + Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, + Flags.FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT, + Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, + Flags.FLAG_ENABLE_TASKBAR_PINNING, + Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER, + Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, + Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, + Flags.FLAG_ENABLE_TWOLINE_TOGGLE, + Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, + Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, + Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK, + Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, + Flags.FLAG_ENABLE_WORKSPACE_INFLATION, + Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, + Flags.FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION, + Flags.FLAG_EXTENDIBLE_THEME_MANAGER, + Flags.FLAG_FLOATING_SEARCH_BAR, + Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, + Flags.FLAG_GRID_MIGRATION_REFACTOR, + Flags.FLAG_GSF_RES, + Flags.FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS, + Flags.FLAG_LETTER_FAST_SCROLLER, + Flags.FLAG_MSDL_FEEDBACK, + Flags.FLAG_MULTILINE_SEARCH_BAR, + Flags.FLAG_NAVIGATE_TO_CHILD_PREFERENCE, + Flags.FLAG_ONE_GRID_MOUNTED_MODE, + Flags.FLAG_ONE_GRID_ROTATION_HANDLING, + Flags.FLAG_ONE_GRID_SPECS, + Flags.FLAG_PREDICTIVE_BACK_TO_HOME_BLUR, + Flags.FLAG_PREDICTIVE_BACK_TO_HOME_POLISH, + Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, + Flags.FLAG_PRIVATE_SPACE_ANIMATION, + Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, + Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, + Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, + Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, + Flags.FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK, + Flags.FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE, + Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB, + Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS, + Flags.FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE, + Flags.FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH, + Flags.FLAG_TASKBAR_OVERFLOW, + Flags.FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT, + Flags.FLAG_USE_ACTIVITY_OVERLAY, + Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS, + Flags.FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS, + Flags.FLAG_WORK_SCHEDULER_IN_WORK_PROFILE ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, - Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, - Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, - Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, - "" - ) + Arrays.asList( + Flags.FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS, + Flags.FLAG_ALL_APPS_BLUR, + Flags.FLAG_ALL_APPS_SHEET_FOR_HANDHELD, + Flags.FLAG_COORDINATE_WORKSPACE_SCALE, + Flags.FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG, + Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, + Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, + Flags.FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT, + Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING, + Flags.FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, + Flags.FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS, + Flags.FLAG_ENABLE_CONTRAST_TILES, + Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, + Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW, + Flags.FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH, + Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO, + Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, + Flags.FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION, + Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, + Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, + Flags.FLAG_ENABLE_FOCUS_OUTLINE, + Flags.FLAG_ENABLE_GENERATED_PREVIEWS, + Flags.FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP, + Flags.FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, + Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, + Flags.FLAG_ENABLE_GROWTH_NUDGE, + Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, + Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, + Flags.FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW, + Flags.FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE, + Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, + Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES, + Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, + Flags.FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH, + Flags.FLAG_ENABLE_MOUSE_INTERACTION_CHANGES, + Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, + Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, + Flags.FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR, + Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT, + Flags.FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, + Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU, + Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, + Flags.FLAG_ENABLE_PRIVATE_SPACE, + Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, + Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, + Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, + Flags.FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG, + Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, + Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, + Flags.FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE, + Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, + Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS, + Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, + Flags.FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU, + Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, + Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, + Flags.FLAG_ENABLE_STATE_MANAGER_PROTO_LOG, + Flags.FLAG_ENABLE_STRICT_MODE, + Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, + Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, + Flags.FLAG_ENABLE_TASKBAR_BEHIND_SHADE, + Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, + Flags.FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT, + Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, + Flags.FLAG_ENABLE_TASKBAR_PINNING, + Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER, + Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, + Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, + Flags.FLAG_ENABLE_TWOLINE_TOGGLE, + Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, + Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, + Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK, + Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, + Flags.FLAG_ENABLE_WORKSPACE_INFLATION, + Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, + Flags.FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION, + Flags.FLAG_EXTENDIBLE_THEME_MANAGER, + Flags.FLAG_FLOATING_SEARCH_BAR, + Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, + Flags.FLAG_GRID_MIGRATION_REFACTOR, + Flags.FLAG_GSF_RES, + Flags.FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS, + Flags.FLAG_LETTER_FAST_SCROLLER, + Flags.FLAG_MSDL_FEEDBACK, + Flags.FLAG_MULTILINE_SEARCH_BAR, + Flags.FLAG_NAVIGATE_TO_CHILD_PREFERENCE, + Flags.FLAG_ONE_GRID_MOUNTED_MODE, + Flags.FLAG_ONE_GRID_ROTATION_HANDLING, + Flags.FLAG_ONE_GRID_SPECS, + Flags.FLAG_PREDICTIVE_BACK_TO_HOME_BLUR, + Flags.FLAG_PREDICTIVE_BACK_TO_HOME_POLISH, + Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, + Flags.FLAG_PRIVATE_SPACE_ANIMATION, + Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, + Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, + Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, + Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, + Flags.FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK, + Flags.FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE, + Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB, + Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS, + Flags.FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE, + Flags.FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH, + Flags.FLAG_TASKBAR_OVERFLOW, + Flags.FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT, + Flags.FLAG_USE_ACTIVITY_OVERLAY, + Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS, + Flags.FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS, + Flags.FLAG_WORK_SCHEDULER_IN_WORK_PROFILE, + "" + ) ); } diff --git a/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java b/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java index 1dc6e623ae1..1416ee20072 100644 --- a/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; - /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/launcher3/FeatureFlags.java b/flags/src/com/android/launcher3/FeatureFlags.java index 7cd0b034f28..6c7a84557f6 100644 --- a/flags/src/com/android/launcher3/FeatureFlags.java +++ b/flags/src/com/android/launcher3/FeatureFlags.java @@ -1,103 +1,348 @@ package com.android.launcher3; +// TODO(b/303773055): Remove the annotation after access issue is resolved. /** @hide */ public interface FeatureFlags { + + boolean accessibilityScrollOnAllapps(); + + + boolean allAppsBlur(); + + + boolean allAppsSheetForHandheld(); + + + boolean coordinateWorkspaceScale(); + + + boolean enableActiveGestureProtoLog(); + + boolean enableAddAppWidgetViaConfigActivityV2(); + boolean enableAdditionalHomeAnimations(); + + boolean enableAllAppsButtonInHotseat(); + + + boolean enableAltTabKqsFlatenning(); + + + boolean enableAltTabKqsOnConnectedDisplays(); + + boolean enableCategorizedWidgetSuggestions(); + + boolean enableContainerReturnAnimations(); + + + boolean enableContrastTiles(); + + boolean enableCursorHoverStates(); + + boolean enableDesktopExplodedView(); + + + boolean enableDesktopTaskAlphaAnimation(); + + + boolean enableDesktopWindowingCarouselDetach(); + + + boolean enableDismissPredictionUndo(); + + boolean enableExpandingPauseWorkButton(); + + boolean enableExpressiveDismissTaskMotion(); + + boolean enableFallbackOverviewInWindow(); + boolean enableFirstScreenBroadcastArchivingExtras(); + boolean enableFocusOutline(); + boolean enableGeneratedPreviews(); + + boolean enableGestureNavHorizontalTouchSlop(); + + + boolean enableGestureNavOnConnectedDisplays(); + + boolean enableGridMigrationFix(); + boolean enableGridOnlyOverview(); + + boolean enableGrowthNudge(); + + boolean enableHandleDelayedGestureCallbacks(); + boolean enableHomeTransitionListener(); + + boolean enableHoverOfChildElementsInTaskview(); + + + boolean enableLargeDesktopWindowingTile(); + + boolean enableLauncherBrMetricsFixed(); + + boolean enableLauncherIconShapes(); + + + boolean enableLauncherOverviewInWindow(); + + + boolean enableLauncherVisualRefresh(); + + + boolean enableMouseInteractionChanges(); + + + boolean enableMultiInstanceMenuTaskbar(); + + boolean enableNarrowGridRestore(); + + boolean enableOverviewBackgroundWallpaperBlur(); + + + boolean enableOverviewCommandHelperTimeout(); + + + boolean enableOverviewDesktopTileWallpaperBackground(); + + boolean enableOverviewIconMenu(); + + boolean enableOverviewOnConnectedDisplays(); + + + boolean enablePinningAppWithContextMenu(); + + boolean enablePredictiveBackGesture(); + boolean enablePrivateSpace(); + boolean enablePrivateSpaceInstallShortcut(); + boolean enableRebootUnlockAnimation(); + boolean enableRecentsInTaskbar(); + + boolean enableRecentsWindowProtoLog(); + + boolean enableRefactorTaskThumbnail(); + boolean enableResponsiveWorkspace(); + + boolean enableScalabilityForDesktopExperience(); + + boolean enableScalingRevealHomeAnimation(); + + boolean enableSeparateExternalDisplayTasks(); + + boolean enableShortcutDontSuggestApp(); + + boolean enableShowEnabledShortcutsInAccessibilityMenu(); + + boolean enableSmartspaceAsAWidget(); + boolean enableSmartspaceRemovalToggle(); + + boolean enableStateManagerProtoLog(); + + + boolean enableStrictMode(); + + boolean enableSupportForArchiving(); + boolean enableTabletTwoPanePickerV2(); + + boolean enableTaskbarBehindShade(); + + boolean enableTaskbarCustomization(); + + boolean enableTaskbarForDirectBoot(); + + boolean enableTaskbarNoRecreate(); + boolean enableTaskbarPinning(); + + boolean enableTieredWidgetsByDefaultInPicker(); + + boolean enableTwoPaneLauncherSettings(); + boolean enableTwolineAllapps(); + boolean enableTwolineToggle(); + boolean enableUnfoldStateAnimation(); + boolean enableUnfoldedTwoPanePicker(); + + boolean enableUseTopVisibleActivityForExcludeFromRecentTask(); + + boolean enableWidgetTapToAdd(); + boolean enableWorkspaceInflation(); + boolean enabledFoldersInAllApps(); + + boolean expressiveThemeInTaskbarAndNavigation(); + + + boolean extendibleThemeManager(); + + boolean floatingSearchBar(); + boolean forceMonochromeAppIcons(); + + boolean gridMigrationRefactor(); + + + boolean gsfRes(); + + + boolean ignoreThreeFingerTrackpadForNavHandleLongPress(); + + + boolean letterFastScroller(); + + + boolean msdlFeedback(); + + + boolean multilineSearchBar(); + + + boolean navigateToChildPreference(); + + + boolean oneGridMountedMode(); + + + boolean oneGridRotationHandling(); + + + boolean oneGridSpecs(); + + + boolean predictiveBackToHomeBlur(); + + + boolean predictiveBackToHomePolish(); + + boolean privateSpaceAddFloatingMaskView(); + boolean privateSpaceAnimation(); + boolean privateSpaceAppInstallerButton(); + boolean privateSpaceRestrictAccessibilityDrag(); + boolean privateSpaceRestrictItemDrag(); + boolean privateSpaceSysAppsSeparation(); + + boolean removeAppsRefreshOnRightClick(); + + + boolean removeExcludeFromScreenMagnificationFlagUsage(); + + + boolean restoreArchivedAppIconsFromDb(); + + + boolean restoreArchivedShortcuts(); + + + boolean showTaskbarPinningPopupFromAnywhere(); + + + boolean syncAppLaunchWithTaskbarStash(); + + + boolean taskbarOverflow(); + + + boolean taskbarQuietModeChangeSupport(); + + boolean useActivityOverlay(); -} \ No newline at end of file + + + boolean useNewIconForArchivedApps(); + + + boolean useSystemRadiusForAppWidgets(); + + + boolean workSchedulerInWorkProfile(); +} diff --git a/flags/src/com/android/launcher3/FeatureFlagsImpl.java b/flags/src/com/android/launcher3/FeatureFlagsImpl.java index d0aad19aaa5..1fd17137d9d 100644 --- a/flags/src/com/android/launcher3/FeatureFlagsImpl.java +++ b/flags/src/com/android/launcher3/FeatureFlagsImpl.java @@ -1,880 +1,803 @@ package com.android.launcher3; // TODO(b/303773055): Remove the annotation after access issue is resolved. -import androidx.core.os.BuildCompat; +/** @hide */ +public final class FeatureFlagsImpl implements FeatureFlags { + @Override + + + public boolean accessibilityScrollOnAllapps() { + return true; + } + + @Override + + + public boolean allAppsBlur() { + return true; + } + + @Override + + + public boolean allAppsSheetForHandheld() { + return true; + } + + @Override + + + public boolean coordinateWorkspaceScale() { + return true; + } + + @Override + + + public boolean enableActiveGestureProtoLog() { + return true; + } + + @Override + + + public boolean enableAddAppWidgetViaConfigActivityV2() { + return true; + } + + @Override + + + public boolean enableAdditionalHomeAnimations() { + return true; + } + + @Override + + + public boolean enableAllAppsButtonInHotseat() { + return false; + } + + @Override + + + public boolean enableAltTabKqsFlatenning() { + return false; + } + + @Override + + + public boolean enableAltTabKqsOnConnectedDisplays() { + return false; + } + + @Override + + + public boolean enableCategorizedWidgetSuggestions() { + return true; + } + + @Override + + + public boolean enableContainerReturnAnimations() { + return true; + } + + @Override + + + public boolean enableContrastTiles() { + return false; + } + + @Override + + + public boolean enableCursorHoverStates() { + return true; + } + + @Override + + + public boolean enableDesktopExplodedView() { + return false; + } + + @Override + + + public boolean enableDesktopTaskAlphaAnimation() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingCarouselDetach() { + return false; + } + + @Override + + + public boolean enableDismissPredictionUndo() { + return true; + } + + @Override + + + public boolean enableExpandingPauseWorkButton() { + return true; + } + + @Override + + + public boolean enableExpressiveDismissTaskMotion() { + return true; + } + + @Override + + + public boolean enableFallbackOverviewInWindow() { + return false; + } + + @Override + + + public boolean enableFirstScreenBroadcastArchivingExtras() { + return true; + } + + @Override + + + public boolean enableFocusOutline() { + return true; + } + + @Override + + + public boolean enableGeneratedPreviews() { + return true; + } + + @Override + + + public boolean enableGestureNavHorizontalTouchSlop() { + return false; + } + + @Override + + + public boolean enableGestureNavOnConnectedDisplays() { + return false; + } + + @Override + + + public boolean enableGridMigrationFix() { + return true; + } + + @Override + + + public boolean enableGridOnlyOverview() { + return false; + } + + @Override + + + public boolean enableGrowthNudge() { + return false; + } + + @Override + + + public boolean enableHandleDelayedGestureCallbacks() { + return true; + } + + @Override + + + public boolean enableHomeTransitionListener() { + return true; + } + + @Override + + + public boolean enableHoverOfChildElementsInTaskview() { + return true; + } + + @Override + + + public boolean enableLargeDesktopWindowingTile() { + return true; + } + + @Override + + + public boolean enableLauncherBrMetricsFixed() { + return true; + } + + @Override + + + public boolean enableLauncherIconShapes() { + return true; + } + + @Override + + + public boolean enableLauncherOverviewInWindow() { + return false; + } + + @Override + + + public boolean enableLauncherVisualRefresh() { + return true; + } + + @Override + + + public boolean enableMouseInteractionChanges() { + return true; + } + + @Override + + + public boolean enableMultiInstanceMenuTaskbar() { + return true; + } + + @Override + + + public boolean enableNarrowGridRestore() { + return true; + } + + @Override + + + public boolean enableOverviewBackgroundWallpaperBlur() { + return true; + } + + @Override + + + public boolean enableOverviewCommandHelperTimeout() { + return true; + } + + @Override + + + public boolean enableOverviewDesktopTileWallpaperBackground() { + return false; + } + + @Override + + + public boolean enableOverviewIconMenu() { + return false; + } + + @Override + + + public boolean enableOverviewOnConnectedDisplays() { + return false; + } + + @Override + + + public boolean enablePinningAppWithContextMenu() { + return false; + } + + @Override + + + public boolean enablePredictiveBackGesture() { + return true; + } + + @Override + + + public boolean enablePrivateSpace() { + return true; + } + + @Override + + + public boolean enablePrivateSpaceInstallShortcut() { + return true; + } + + @Override + + + public boolean enableRebootUnlockAnimation() { + return false; + } + + @Override + + + public boolean enableRecentsInTaskbar() { + return false; + } -import com.android.quickstep.util.DeviceConfigHelper; + @Override -import java.nio.file.Files; -import java.nio.file.Paths; -/** @hide */ -public final class FeatureFlagsImpl implements FeatureFlags { - private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); - private static volatile boolean isCached = false; - private static volatile boolean launcher_is_cached = false; - private static volatile boolean launcher_search_is_cached = false; - private static boolean enableAddAppWidgetViaConfigActivityV2 = true; - private static boolean enableAdditionalHomeAnimations = true; - private static boolean enableCategorizedWidgetSuggestions = true; - private static boolean enableCursorHoverStates = true; - private static boolean enableExpandingPauseWorkButton = true; - private static boolean enableFallbackOverviewInWindow = false; - private static boolean enableFocusOutline = true; - private static boolean enableGeneratedPreviews = true; - private static boolean enableGridOnlyOverview = false; - private static boolean enableHandleDelayedGestureCallbacks = true; - private static boolean enableHomeTransitionListener = true; - private static boolean enableOverviewIconMenu = false; - private static boolean enablePredictiveBackGesture = true; - private static boolean enablePrivateSpace = true; - private static boolean enablePrivateSpaceInstallShortcut = true; - private static boolean enableRebootUnlockAnimation = false; - private static boolean enableRecentsInTaskbar = false; - private static boolean enableRefactorTaskThumbnail = false; - private static boolean enableResponsiveWorkspace = true; - private static boolean enableScalingRevealHomeAnimation = true; - private static boolean enableShortcutDontSuggestApp = true; - private static boolean enableSmartspaceAsAWidget = false; - private static boolean enableSmartspaceRemovalToggle = false; - private static boolean enableSupportForArchiving = false; - private static boolean enableTabletTwoPanePickerV2 = false; - private static boolean enableTaskbarCustomization = false; - private static boolean enableTaskbarNoRecreate = false; - private static boolean enableTaskbarPinning = true; - private static boolean enableTwoPaneLauncherSettings = false; - private static boolean enableTwolineAllapps = false; - private static boolean enableTwolineToggle = true; - private static boolean enableUnfoldStateAnimation = false; - private static boolean enableUnfoldedTwoPanePicker = true; - private static boolean enableWidgetTapToAdd = true; - private static boolean enableWorkspaceInflation = true; - private static boolean enabledFoldersInAllApps = false; - private static boolean floatingSearchBar = false; - private static boolean forceMonochromeAppIcons = false; - private static boolean privateSpaceAddFloatingMaskView = false; - private static boolean privateSpaceAnimation = true; - private static boolean privateSpaceAppInstallerButton = true; - private static boolean privateSpaceRestrictAccessibilityDrag = true; - private static boolean privateSpaceRestrictItemDrag = true; - private static boolean privateSpaceSysAppsSeparation = true; - private static boolean useActivityOverlay = true; - - - private void init() { - isCached = true; - } - - private void load_overrides_launcher() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - enableAddAppWidgetViaConfigActivityV2 = - properties.getBoolean(Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, true); - enableAdditionalHomeAnimations = - properties.getBoolean(Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, true); - enableCategorizedWidgetSuggestions = - properties.getBoolean(Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, true); - enableCursorHoverStates = - properties.getBoolean(Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, true); - enableExpandingPauseWorkButton = - properties.getBoolean(Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, true); - enableFallbackOverviewInWindow = - properties.getBoolean(Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, false); - enableFocusOutline = - properties.getBoolean(Flags.FLAG_ENABLE_FOCUS_OUTLINE, true); - enableGeneratedPreviews = - properties.getBoolean(Flags.FLAG_ENABLE_GENERATED_PREVIEWS, true); - enableGridOnlyOverview = - properties.getBoolean(Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, false); - enableHandleDelayedGestureCallbacks = - properties.getBoolean(Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, true); - enableHomeTransitionListener = - properties.getBoolean(Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, true); - enableOverviewIconMenu = - properties.getBoolean(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, false); - enablePredictiveBackGesture = - properties.getBoolean(Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, true); - enablePrivateSpaceInstallShortcut = - properties.getBoolean(Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, true); - enableRebootUnlockAnimation = - properties.getBoolean(Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, false); - enableRecentsInTaskbar = - properties.getBoolean(Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, false); - enableRefactorTaskThumbnail = - properties.getBoolean(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, false); - enableResponsiveWorkspace = - properties.getBoolean(Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, true); - enableScalingRevealHomeAnimation = - properties.getBoolean(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, true); - enableShortcutDontSuggestApp = - properties.getBoolean(Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, true); - enableSmartspaceAsAWidget = - properties.getBoolean(Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, false); - enableSmartspaceRemovalToggle = - properties.getBoolean(Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, true); - enableSupportForArchiving = - properties.getBoolean(Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, BuildCompat.isAtLeastU()); - enableTabletTwoPanePickerV2 = - properties.getBoolean(Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, false); - enableTaskbarCustomization = - properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, false); - enableTaskbarNoRecreate = - properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, false); - enableTaskbarPinning = - properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_PINNING, true); - enableTwoPaneLauncherSettings = - properties.getBoolean(Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, true); - enableTwolineAllapps = - properties.getBoolean(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, false); - enableTwolineToggle = - properties.getBoolean(Flags.FLAG_ENABLE_TWOLINE_TOGGLE, true); - enableUnfoldStateAnimation = - properties.getBoolean(Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, false); - enableUnfoldedTwoPanePicker = - properties.getBoolean(Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, true); - enableWidgetTapToAdd = - properties.getBoolean(Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, true); - enableWorkspaceInflation = - properties.getBoolean(Flags.FLAG_ENABLE_WORKSPACE_INFLATION, true); - enabledFoldersInAllApps = - properties.getBoolean(Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, false); - floatingSearchBar = - properties.getBoolean(Flags.FLAG_FLOATING_SEARCH_BAR, false); - forceMonochromeAppIcons = - properties.getBoolean(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, true); - useActivityOverlay = - properties.getBoolean(Flags.FLAG_USE_ACTIVITY_OVERLAY, true); - } catch (NullPointerException e) { - // Ignored - } - launcher_is_cached = true; - } - - private void load_overrides_launcher_search() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - enablePrivateSpace = - properties.getBoolean(Flags.FLAG_ENABLE_PRIVATE_SPACE, BuildCompat.isAtLeastU()); - privateSpaceAddFloatingMaskView = - properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, BuildCompat.isAtLeastU()); - privateSpaceAnimation = - properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_ANIMATION, BuildCompat.isAtLeastU()); - privateSpaceAppInstallerButton = - properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, BuildCompat.isAtLeastU()); - privateSpaceRestrictAccessibilityDrag = - properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, BuildCompat.isAtLeastU()); - privateSpaceRestrictItemDrag = - properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, BuildCompat.isAtLeastU()); - privateSpaceSysAppsSeparation = - properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, BuildCompat.isAtLeastU()); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace launcher_search " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - launcher_search_is_cached = true; + + public boolean enableRecentsWindowProtoLog() { + return false; } @Override - public boolean enableAddAppWidgetViaConfigActivityV2() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableAddAppWidgetViaConfigActivityV2; + + public boolean enableRefactorTaskThumbnail() { + return false; } - public boolean enableAdditionalHomeAnimations() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableAdditionalHomeAnimations; + @Override + + public boolean enableResponsiveWorkspace() { + return true; } @Override - public boolean enableCategorizedWidgetSuggestions() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableCategorizedWidgetSuggestions; + + public boolean enableScalabilityForDesktopExperience() { + return false; } @Override - public boolean enableCursorHoverStates() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableCursorHoverStates; + + public boolean enableScalingRevealHomeAnimation() { + return true; } @Override - public boolean enableExpandingPauseWorkButton() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableExpandingPauseWorkButton; + + public boolean enableSeparateExternalDisplayTasks() { + return true; } @Override - public boolean enableFallbackOverviewInWindow() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableFallbackOverviewInWindow; + + public boolean enableShortcutDontSuggestApp() { + return true; } @Override - public boolean enableFirstScreenBroadcastArchivingExtras() { + + + public boolean enableShowEnabledShortcutsInAccessibilityMenu() { + return true; + } + + @Override + + + public boolean enableSmartspaceAsAWidget() { return false; + } + + @Override + + public boolean enableSmartspaceRemovalToggle() { + return false; } @Override - public boolean enableFocusOutline() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableFocusOutline; + + public boolean enableStateManagerProtoLog() { + return false; } @Override - public boolean enableGeneratedPreviews() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableGeneratedPreviews; + + public boolean enableStrictMode() { + return false; } @Override - public boolean enableGridMigrationFix() { + + + public boolean enableSupportForArchiving() { + return true; + } + + @Override + + + public boolean enableTabletTwoPanePickerV2() { return false; + } + @Override + + + public boolean enableTaskbarBehindShade() { + return false; } @Override - public boolean enableGridOnlyOverview() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableGridOnlyOverview; + + public boolean enableTaskbarCustomization() { + return false; } @Override - public boolean enableHandleDelayedGestureCallbacks() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableHandleDelayedGestureCallbacks; + + public boolean enableTaskbarForDirectBoot() { + return false; } @Override - public boolean enableHomeTransitionListener() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableHomeTransitionListener; + + public boolean enableTaskbarNoRecreate() { + return false; } @Override - public boolean enableLauncherBrMetricsFixed() { + + + public boolean enableTaskbarPinning() { return true; + } + + @Override + + + public boolean enableTieredWidgetsByDefaultInPicker() { + return false; + } + + @Override + + public boolean enableTwoPaneLauncherSettings() { + return true; } @Override - public boolean enableNarrowGridRestore() { + + + public boolean enableTwolineAllapps() { return false; + } + @Override + + + public boolean enableTwolineToggle() { + return true; } @Override - public boolean enableOverviewIconMenu() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableOverviewIconMenu; + + public boolean enableUnfoldStateAnimation() { + return true; } @Override - public boolean enablePredictiveBackGesture() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enablePredictiveBackGesture; + + public boolean enableUnfoldedTwoPanePicker() { + return true; } @Override - public boolean enablePrivateSpace() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_search_is_cached) { - load_overrides_launcher_search(); - } - } - return enablePrivateSpace; + + public boolean enableUseTopVisibleActivityForExcludeFromRecentTask() { + return true; } @Override - public boolean enablePrivateSpaceInstallShortcut() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enablePrivateSpaceInstallShortcut; + + public boolean enableWidgetTapToAdd() { + return true; } @Override - public boolean enableRebootUnlockAnimation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableRebootUnlockAnimation; + + public boolean enableWorkspaceInflation() { + return true; + } + + @Override + + + public boolean enabledFoldersInAllApps() { + return false; } @Override - public boolean enableRecentsInTaskbar() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableRecentsInTaskbar; + + public boolean expressiveThemeInTaskbarAndNavigation() { + return true; } @Override - public boolean enableRefactorTaskThumbnail() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableRefactorTaskThumbnail; + + public boolean extendibleThemeManager() { + return true; } @Override - public boolean enableResponsiveWorkspace() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableResponsiveWorkspace; + + public boolean floatingSearchBar() { + return false; } @Override - public boolean enableScalingRevealHomeAnimation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableScalingRevealHomeAnimation; + + public boolean forceMonochromeAppIcons() { + return false; } @Override - public boolean enableShortcutDontSuggestApp() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableShortcutDontSuggestApp; + + public boolean gridMigrationRefactor() { + return true; } @Override - public boolean enableSmartspaceAsAWidget() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableSmartspaceAsAWidget; + + public boolean gsfRes() { + return false; } @Override - public boolean enableSmartspaceRemovalToggle() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableSmartspaceRemovalToggle; + + public boolean ignoreThreeFingerTrackpadForNavHandleLongPress() { + return true; } @Override - public boolean enableSupportForArchiving() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableSupportForArchiving; + + public boolean letterFastScroller() { + return false; } @Override - public boolean enableTabletTwoPanePickerV2() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableTabletTwoPanePickerV2; + + public boolean msdlFeedback() { + return true; } @Override - public boolean enableTaskbarCustomization() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableTaskbarCustomization; + + public boolean multilineSearchBar() { + return true; } @Override - public boolean enableTaskbarNoRecreate() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableTaskbarNoRecreate; + + public boolean navigateToChildPreference() { + return true; } @Override - public boolean enableTaskbarPinning() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableTaskbarPinning; + + public boolean oneGridMountedMode() { + return false; } @Override - public boolean enableTwoPaneLauncherSettings() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableTwoPaneLauncherSettings; + + public boolean oneGridRotationHandling() { + return false; } @Override - public boolean enableTwolineAllapps() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableTwolineAllapps; + + public boolean oneGridSpecs() { + return false; } @Override - public boolean enableTwolineToggle() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableTwolineToggle; + + public boolean predictiveBackToHomeBlur() { + return true; } @Override - public boolean enableUnfoldStateAnimation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableUnfoldStateAnimation; + + public boolean predictiveBackToHomePolish() { + return true; } @Override - public boolean enableUnfoldedTwoPanePicker() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableUnfoldedTwoPanePicker; + + public boolean privateSpaceAddFloatingMaskView() { + return true; } @Override - public boolean enableWidgetTapToAdd() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableWidgetTapToAdd; + + public boolean privateSpaceAnimation() { + return true; } @Override - public boolean enableWorkspaceInflation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enableWorkspaceInflation; + + public boolean privateSpaceAppInstallerButton() { + return true; + } + + @Override + + + public boolean privateSpaceRestrictAccessibilityDrag() { + return true; } @Override - public boolean enabledFoldersInAllApps() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return enabledFoldersInAllApps; + + public boolean privateSpaceRestrictItemDrag() { + return true; } @Override - public boolean floatingSearchBar() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return floatingSearchBar; + + public boolean privateSpaceSysAppsSeparation() { + return true; } @Override - public boolean forceMonochromeAppIcons() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return forceMonochromeAppIcons; + + public boolean removeAppsRefreshOnRightClick() { + return true; } @Override - public boolean privateSpaceAddFloatingMaskView() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_search_is_cached) { - load_overrides_launcher_search(); - } - } - return privateSpaceAddFloatingMaskView; + + public boolean removeExcludeFromScreenMagnificationFlagUsage() { + return true; } @Override - public boolean privateSpaceAnimation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_search_is_cached) { - load_overrides_launcher_search(); - } - } - return privateSpaceAnimation; + + public boolean restoreArchivedAppIconsFromDb() { + return false; } @Override - public boolean privateSpaceAppInstallerButton() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_search_is_cached) { - load_overrides_launcher_search(); - } - } - return privateSpaceAppInstallerButton; + + public boolean restoreArchivedShortcuts() { + return false; } @Override - public boolean privateSpaceRestrictAccessibilityDrag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_search_is_cached) { - load_overrides_launcher_search(); - } - } - return privateSpaceRestrictAccessibilityDrag; + + public boolean showTaskbarPinningPopupFromAnywhere() { + return false; } @Override - public boolean privateSpaceRestrictItemDrag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_search_is_cached) { - load_overrides_launcher_search(); - } - } - return privateSpaceRestrictItemDrag; + + public boolean syncAppLaunchWithTaskbarStash() { + return false; } @Override - public boolean privateSpaceSysAppsSeparation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_search_is_cached) { - load_overrides_launcher_search(); - } - } - return privateSpaceSysAppsSeparation; + + public boolean taskbarOverflow() { + return true; + } + + @Override + + + public boolean taskbarQuietModeChangeSupport() { + return false; } @Override + + public boolean useActivityOverlay() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!launcher_is_cached) { - load_overrides_launcher(); - } - } - return useActivityOverlay; + return true; + } + + @Override + + public boolean useNewIconForArchivedApps() { + return true; } -} + @Override + + + public boolean useSystemRadiusForAppWidgets() { + return true; + } + + @Override + + public boolean workSchedulerInWorkProfile() { + return false; + } + +} diff --git a/flags/src/com/android/launcher3/Flags.java b/flags/src/com/android/launcher3/Flags.java index 3e37a8f2a89..68ce0509a9c 100644 --- a/flags/src/com/android/launcher3/Flags.java +++ b/flags/src/com/android/launcher3/Flags.java @@ -1,18 +1,49 @@ package com.android.launcher3; // TODO(b/303773055): Remove the annotation after access issue is resolved. + /** @hide */ public final class Flags { + /** @hide */ + public static final String FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS = "com.android.launcher3.accessibility_scroll_on_allapps"; + /** @hide */ + public static final String FLAG_ALL_APPS_BLUR = "com.android.launcher3.all_apps_blur"; + /** @hide */ + public static final String FLAG_ALL_APPS_SHEET_FOR_HANDHELD = "com.android.launcher3.all_apps_sheet_for_handheld"; + /** @hide */ + public static final String FLAG_COORDINATE_WORKSPACE_SCALE = "com.android.launcher3.coordinate_workspace_scale"; + /** @hide */ + public static final String FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG = "com.android.launcher3.enable_active_gesture_proto_log"; /** @hide */ public static final String FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 = "com.android.launcher3.enable_add_app_widget_via_config_activity_v2"; /** @hide */ public static final String FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS = "com.android.launcher3.enable_additional_home_animations"; /** @hide */ + public static final String FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = "com.android.launcher3.enable_all_apps_button_in_hotseat"; + /** @hide */ + public static final String FLAG_ENABLE_ALT_TAB_KQS_FLATENNING = "com.android.launcher3.enable_alt_tab_kqs_flatenning"; + /** @hide */ + public static final String FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS = "com.android.launcher3.enable_alt_tab_kqs_on_connected_displays"; + /** @hide */ public static final String FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS = "com.android.launcher3.enable_categorized_widget_suggestions"; /** @hide */ + public static final String FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS = "com.android.launcher3.enable_container_return_animations"; + /** @hide */ + public static final String FLAG_ENABLE_CONTRAST_TILES = "com.android.launcher3.enable_contrast_tiles"; + /** @hide */ public static final String FLAG_ENABLE_CURSOR_HOVER_STATES = "com.android.launcher3.enable_cursor_hover_states"; /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_EXPLODED_VIEW = "com.android.launcher3.enable_desktop_exploded_view"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION = "com.android.launcher3.enable_desktop_task_alpha_animation"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH = "com.android.launcher3.enable_desktop_windowing_carousel_detach"; + /** @hide */ + public static final String FLAG_ENABLE_DISMISS_PREDICTION_UNDO = "com.android.launcher3.enable_dismiss_prediction_undo"; + /** @hide */ public static final String FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON = "com.android.launcher3.enable_expanding_pause_work_button"; /** @hide */ + public static final String FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION = "com.android.launcher3.enable_expressive_dismiss_task_motion"; + /** @hide */ public static final String FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW = "com.android.launcher3.enable_fallback_overview_in_window"; /** @hide */ public static final String FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS = "com.android.launcher3.enable_first_screen_broadcast_archiving_extras"; @@ -21,20 +52,50 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_GENERATED_PREVIEWS = "com.android.launcher3.enable_generated_previews"; /** @hide */ + public static final String FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP = "com.android.launcher3.enable_gesture_nav_horizontal_touch_slop"; + /** @hide */ + public static final String FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS = "com.android.launcher3.enable_gesture_nav_on_connected_displays"; + /** @hide */ public static final String FLAG_ENABLE_GRID_MIGRATION_FIX = "com.android.launcher3.enable_grid_migration_fix"; /** @hide */ public static final String FLAG_ENABLE_GRID_ONLY_OVERVIEW = "com.android.launcher3.enable_grid_only_overview"; /** @hide */ + public static final String FLAG_ENABLE_GROWTH_NUDGE = "com.android.launcher3.enable_growth_nudge"; + /** @hide */ public static final String FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS = "com.android.launcher3.enable_handle_delayed_gesture_callbacks"; /** @hide */ public static final String FLAG_ENABLE_HOME_TRANSITION_LISTENER = "com.android.launcher3.enable_home_transition_listener"; /** @hide */ + public static final String FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW = "com.android.launcher3.enable_hover_of_child_elements_in_taskview"; + /** @hide */ + public static final String FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE = "com.android.launcher3.enable_large_desktop_windowing_tile"; + /** @hide */ public static final String FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED = "com.android.launcher3.enable_launcher_br_metrics_fixed"; /** @hide */ + public static final String FLAG_ENABLE_LAUNCHER_ICON_SHAPES = "com.android.launcher3.enable_launcher_icon_shapes"; + /** @hide */ + public static final String FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW = "com.android.launcher3.enable_launcher_overview_in_window"; + /** @hide */ + public static final String FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH = "com.android.launcher3.enable_launcher_visual_refresh"; + /** @hide */ + public static final String FLAG_ENABLE_MOUSE_INTERACTION_CHANGES = "com.android.launcher3.enable_mouse_interaction_changes"; + /** @hide */ + public static final String FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR = "com.android.launcher3.enable_multi_instance_menu_taskbar"; + /** @hide */ public static final String FLAG_ENABLE_NARROW_GRID_RESTORE = "com.android.launcher3.enable_narrow_grid_restore"; /** @hide */ + public static final String FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR = "com.android.launcher3.enable_overview_background_wallpaper_blur"; + /** @hide */ + public static final String FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT = "com.android.launcher3.enable_overview_command_helper_timeout"; + /** @hide */ + public static final String FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND = "com.android.launcher3.enable_overview_desktop_tile_wallpaper_background"; + /** @hide */ public static final String FLAG_ENABLE_OVERVIEW_ICON_MENU = "com.android.launcher3.enable_overview_icon_menu"; /** @hide */ + public static final String FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS = "com.android.launcher3.enable_overview_on_connected_displays"; + /** @hide */ + public static final String FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU = "com.android.launcher3.enable_pinning_app_with_context_menu"; + /** @hide */ public static final String FLAG_ENABLE_PREDICTIVE_BACK_GESTURE = "com.android.launcher3.enable_predictive_back_gesture"; /** @hide */ public static final String FLAG_ENABLE_PRIVATE_SPACE = "com.android.launcher3.enable_private_space"; @@ -45,28 +106,46 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_RECENTS_IN_TASKBAR = "com.android.launcher3.enable_recents_in_taskbar"; /** @hide */ + public static final String FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG = "com.android.launcher3.enable_recents_window_proto_log"; + /** @hide */ public static final String FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL = "com.android.launcher3.enable_refactor_task_thumbnail"; /** @hide */ public static final String FLAG_ENABLE_RESPONSIVE_WORKSPACE = "com.android.launcher3.enable_responsive_workspace"; /** @hide */ + public static final String FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE = "com.android.launcher3.enable_scalability_for_desktop_experience"; + /** @hide */ public static final String FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION = "com.android.launcher3.enable_scaling_reveal_home_animation"; /** @hide */ + public static final String FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS = "com.android.launcher3.enable_separate_external_display_tasks"; + /** @hide */ public static final String FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP = "com.android.launcher3.enable_shortcut_dont_suggest_app"; /** @hide */ + public static final String FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU = "com.android.launcher3.enable_show_enabled_shortcuts_in_accessibility_menu"; + /** @hide */ public static final String FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET = "com.android.launcher3.enable_smartspace_as_a_widget"; /** @hide */ public static final String FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE = "com.android.launcher3.enable_smartspace_removal_toggle"; /** @hide */ + public static final String FLAG_ENABLE_STATE_MANAGER_PROTO_LOG = "com.android.launcher3.enable_state_manager_proto_log"; + /** @hide */ + public static final String FLAG_ENABLE_STRICT_MODE = "com.android.launcher3.enable_strict_mode"; + /** @hide */ public static final String FLAG_ENABLE_SUPPORT_FOR_ARCHIVING = "com.android.launcher3.enable_support_for_archiving"; /** @hide */ public static final String FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2 = "com.android.launcher3.enable_tablet_two_pane_picker_v2"; /** @hide */ + public static final String FLAG_ENABLE_TASKBAR_BEHIND_SHADE = "com.android.launcher3.enable_taskbar_behind_shade"; + /** @hide */ public static final String FLAG_ENABLE_TASKBAR_CUSTOMIZATION = "com.android.launcher3.enable_taskbar_customization"; /** @hide */ + public static final String FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT = "com.android.launcher3.enable_taskbar_for_direct_boot"; + /** @hide */ public static final String FLAG_ENABLE_TASKBAR_NO_RECREATE = "com.android.launcher3.enable_taskbar_no_recreate"; /** @hide */ public static final String FLAG_ENABLE_TASKBAR_PINNING = "com.android.launcher3.enable_taskbar_pinning"; /** @hide */ + public static final String FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER = "com.android.launcher3.enable_tiered_widgets_by_default_in_picker"; + /** @hide */ public static final String FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS = "com.android.launcher3.enable_two_pane_launcher_settings"; /** @hide */ public static final String FLAG_ENABLE_TWOLINE_ALLAPPS = "com.android.launcher3.enable_twoline_allapps"; @@ -77,16 +156,46 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER = "com.android.launcher3.enable_unfolded_two_pane_picker"; /** @hide */ + public static final String FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK = "com.android.launcher3.enable_use_top_visible_activity_for_exclude_from_recent_task"; + /** @hide */ public static final String FLAG_ENABLE_WIDGET_TAP_TO_ADD = "com.android.launcher3.enable_widget_tap_to_add"; /** @hide */ public static final String FLAG_ENABLE_WORKSPACE_INFLATION = "com.android.launcher3.enable_workspace_inflation"; /** @hide */ public static final String FLAG_ENABLED_FOLDERS_IN_ALL_APPS = "com.android.launcher3.enabled_folders_in_all_apps"; /** @hide */ + public static final String FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION = "com.android.launcher3.expressive_theme_in_taskbar_and_navigation"; + /** @hide */ + public static final String FLAG_EXTENDIBLE_THEME_MANAGER = "com.android.launcher3.extendible_theme_manager"; + /** @hide */ public static final String FLAG_FLOATING_SEARCH_BAR = "com.android.launcher3.floating_search_bar"; /** @hide */ public static final String FLAG_FORCE_MONOCHROME_APP_ICONS = "com.android.launcher3.force_monochrome_app_icons"; /** @hide */ + public static final String FLAG_GRID_MIGRATION_REFACTOR = "com.android.launcher3.grid_migration_refactor"; + /** @hide */ + public static final String FLAG_GSF_RES = "com.android.launcher3.gsf_res"; + /** @hide */ + public static final String FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS = "com.android.launcher3.ignore_three_finger_trackpad_for_nav_handle_long_press"; + /** @hide */ + public static final String FLAG_LETTER_FAST_SCROLLER = "com.android.launcher3.letter_fast_scroller"; + /** @hide */ + public static final String FLAG_MSDL_FEEDBACK = "com.android.launcher3.msdl_feedback"; + /** @hide */ + public static final String FLAG_MULTILINE_SEARCH_BAR = "com.android.launcher3.multiline_search_bar"; + /** @hide */ + public static final String FLAG_NAVIGATE_TO_CHILD_PREFERENCE = "com.android.launcher3.navigate_to_child_preference"; + /** @hide */ + public static final String FLAG_ONE_GRID_MOUNTED_MODE = "com.android.launcher3.one_grid_mounted_mode"; + /** @hide */ + public static final String FLAG_ONE_GRID_ROTATION_HANDLING = "com.android.launcher3.one_grid_rotation_handling"; + /** @hide */ + public static final String FLAG_ONE_GRID_SPECS = "com.android.launcher3.one_grid_specs"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_TO_HOME_BLUR = "com.android.launcher3.predictive_back_to_home_blur"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_TO_HOME_POLISH = "com.android.launcher3.predictive_back_to_home_polish"; + /** @hide */ public static final String FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW = "com.android.launcher3.private_space_add_floating_mask_view"; /** @hide */ public static final String FLAG_PRIVATE_SPACE_ANIMATION = "com.android.launcher3.private_space_animation"; @@ -99,204 +208,714 @@ public final class Flags { /** @hide */ public static final String FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION = "com.android.launcher3.private_space_sys_apps_separation"; /** @hide */ + public static final String FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK = "com.android.launcher3.remove_apps_refresh_on_right_click"; + /** @hide */ + public static final String FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE = "com.android.launcher3.remove_exclude_from_screen_magnification_flag_usage"; + /** @hide */ + public static final String FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB = "com.android.launcher3.restore_archived_app_icons_from_db"; + /** @hide */ + public static final String FLAG_RESTORE_ARCHIVED_SHORTCUTS = "com.android.launcher3.restore_archived_shortcuts"; + /** @hide */ + public static final String FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE = "com.android.launcher3.show_taskbar_pinning_popup_from_anywhere"; + /** @hide */ + public static final String FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH = "com.android.launcher3.sync_app_launch_with_taskbar_stash"; + /** @hide */ + public static final String FLAG_TASKBAR_OVERFLOW = "com.android.launcher3.taskbar_overflow"; + /** @hide */ + public static final String FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT = "com.android.launcher3.taskbar_quiet_mode_change_support"; + /** @hide */ public static final String FLAG_USE_ACTIVITY_OVERLAY = "com.android.launcher3.use_activity_overlay"; + /** @hide */ + public static final String FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS = "com.android.launcher3.use_new_icon_for_archived_apps"; + /** @hide */ + public static final String FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS = "com.android.launcher3.use_system_radius_for_app_widgets"; + /** @hide */ + public static final String FLAG_WORK_SCHEDULER_IN_WORK_PROFILE = "com.android.launcher3.work_scheduler_in_work_profile"; + + + public static boolean accessibilityScrollOnAllapps() { + + return FEATURE_FLAGS.accessibilityScrollOnAllapps(); + } + + + public static boolean allAppsBlur() { + + return FEATURE_FLAGS.allAppsBlur(); + } + + + public static boolean allAppsSheetForHandheld() { + + return FEATURE_FLAGS.allAppsSheetForHandheld(); + } + + + public static boolean coordinateWorkspaceScale() { + + return FEATURE_FLAGS.coordinateWorkspaceScale(); + } + + + public static boolean enableActiveGestureProtoLog() { + + return FEATURE_FLAGS.enableActiveGestureProtoLog(); + } + public static boolean enableAddAppWidgetViaConfigActivityV2() { + return FEATURE_FLAGS.enableAddAppWidgetViaConfigActivityV2(); } + public static boolean enableAdditionalHomeAnimations() { + return FEATURE_FLAGS.enableAdditionalHomeAnimations(); } + + public static boolean enableAllAppsButtonInHotseat() { + + return FEATURE_FLAGS.enableAllAppsButtonInHotseat(); + } + + + public static boolean enableAltTabKqsFlatenning() { + + return FEATURE_FLAGS.enableAltTabKqsFlatenning(); + } + + + public static boolean enableAltTabKqsOnConnectedDisplays() { + + return FEATURE_FLAGS.enableAltTabKqsOnConnectedDisplays(); + } + + public static boolean enableCategorizedWidgetSuggestions() { + return FEATURE_FLAGS.enableCategorizedWidgetSuggestions(); } + + public static boolean enableContainerReturnAnimations() { + + return FEATURE_FLAGS.enableContainerReturnAnimations(); + } + + + public static boolean enableContrastTiles() { + + return FEATURE_FLAGS.enableContrastTiles(); + } + + public static boolean enableCursorHoverStates() { + return FEATURE_FLAGS.enableCursorHoverStates(); } + + public static boolean enableDesktopExplodedView() { + + return FEATURE_FLAGS.enableDesktopExplodedView(); + } + + + public static boolean enableDesktopTaskAlphaAnimation() { + + return FEATURE_FLAGS.enableDesktopTaskAlphaAnimation(); + } + + + public static boolean enableDesktopWindowingCarouselDetach() { + + return FEATURE_FLAGS.enableDesktopWindowingCarouselDetach(); + } + + + public static boolean enableDismissPredictionUndo() { + + return FEATURE_FLAGS.enableDismissPredictionUndo(); + } + + public static boolean enableExpandingPauseWorkButton() { + return FEATURE_FLAGS.enableExpandingPauseWorkButton(); } + + public static boolean enableExpressiveDismissTaskMotion() { + + return FEATURE_FLAGS.enableExpressiveDismissTaskMotion(); + } + + public static boolean enableFallbackOverviewInWindow() { + return FEATURE_FLAGS.enableFallbackOverviewInWindow(); } + public static boolean enableFirstScreenBroadcastArchivingExtras() { + return FEATURE_FLAGS.enableFirstScreenBroadcastArchivingExtras(); } + public static boolean enableFocusOutline() { + return FEATURE_FLAGS.enableFocusOutline(); } + public static boolean enableGeneratedPreviews() { + return FEATURE_FLAGS.enableGeneratedPreviews(); } + + public static boolean enableGestureNavHorizontalTouchSlop() { + + return FEATURE_FLAGS.enableGestureNavHorizontalTouchSlop(); + } + + + public static boolean enableGestureNavOnConnectedDisplays() { + + return FEATURE_FLAGS.enableGestureNavOnConnectedDisplays(); + } + + public static boolean enableGridMigrationFix() { + return FEATURE_FLAGS.enableGridMigrationFix(); } + public static boolean enableGridOnlyOverview() { + return FEATURE_FLAGS.enableGridOnlyOverview(); } + + public static boolean enableGrowthNudge() { + + return FEATURE_FLAGS.enableGrowthNudge(); + } + + public static boolean enableHandleDelayedGestureCallbacks() { + return FEATURE_FLAGS.enableHandleDelayedGestureCallbacks(); } + public static boolean enableHomeTransitionListener() { + return FEATURE_FLAGS.enableHomeTransitionListener(); } + + public static boolean enableHoverOfChildElementsInTaskview() { + + return FEATURE_FLAGS.enableHoverOfChildElementsInTaskview(); + } + + + public static boolean enableLargeDesktopWindowingTile() { + + return FEATURE_FLAGS.enableLargeDesktopWindowingTile(); + } + + public static boolean enableLauncherBrMetricsFixed() { + return FEATURE_FLAGS.enableLauncherBrMetricsFixed(); } + + public static boolean enableLauncherIconShapes() { + + return FEATURE_FLAGS.enableLauncherIconShapes(); + } + + + public static boolean enableLauncherOverviewInWindow() { + + return FEATURE_FLAGS.enableLauncherOverviewInWindow(); + } + + + public static boolean enableLauncherVisualRefresh() { + + return FEATURE_FLAGS.enableLauncherVisualRefresh(); + } + + + public static boolean enableMouseInteractionChanges() { + + return FEATURE_FLAGS.enableMouseInteractionChanges(); + } + + + public static boolean enableMultiInstanceMenuTaskbar() { + + return FEATURE_FLAGS.enableMultiInstanceMenuTaskbar(); + } + + public static boolean enableNarrowGridRestore() { + return FEATURE_FLAGS.enableNarrowGridRestore(); } + + public static boolean enableOverviewBackgroundWallpaperBlur() { + + return FEATURE_FLAGS.enableOverviewBackgroundWallpaperBlur(); + } + + + public static boolean enableOverviewCommandHelperTimeout() { + + return FEATURE_FLAGS.enableOverviewCommandHelperTimeout(); + } + + + public static boolean enableOverviewDesktopTileWallpaperBackground() { + + return FEATURE_FLAGS.enableOverviewDesktopTileWallpaperBackground(); + } + + public static boolean enableOverviewIconMenu() { + return FEATURE_FLAGS.enableOverviewIconMenu(); } + + public static boolean enableOverviewOnConnectedDisplays() { + + return FEATURE_FLAGS.enableOverviewOnConnectedDisplays(); + } + + + public static boolean enablePinningAppWithContextMenu() { + + return FEATURE_FLAGS.enablePinningAppWithContextMenu(); + } + + public static boolean enablePredictiveBackGesture() { + return FEATURE_FLAGS.enablePredictiveBackGesture(); } + public static boolean enablePrivateSpace() { + return FEATURE_FLAGS.enablePrivateSpace(); } + public static boolean enablePrivateSpaceInstallShortcut() { + return FEATURE_FLAGS.enablePrivateSpaceInstallShortcut(); } + public static boolean enableRebootUnlockAnimation() { + return FEATURE_FLAGS.enableRebootUnlockAnimation(); } + public static boolean enableRecentsInTaskbar() { + return FEATURE_FLAGS.enableRecentsInTaskbar(); } + + public static boolean enableRecentsWindowProtoLog() { + + return FEATURE_FLAGS.enableRecentsWindowProtoLog(); + } + + public static boolean enableRefactorTaskThumbnail() { + return FEATURE_FLAGS.enableRefactorTaskThumbnail(); } + public static boolean enableResponsiveWorkspace() { + return FEATURE_FLAGS.enableResponsiveWorkspace(); } + + public static boolean enableScalabilityForDesktopExperience() { + + return FEATURE_FLAGS.enableScalabilityForDesktopExperience(); + } + + public static boolean enableScalingRevealHomeAnimation() { + return FEATURE_FLAGS.enableScalingRevealHomeAnimation(); } + + public static boolean enableSeparateExternalDisplayTasks() { + + return FEATURE_FLAGS.enableSeparateExternalDisplayTasks(); + } + + public static boolean enableShortcutDontSuggestApp() { + return FEATURE_FLAGS.enableShortcutDontSuggestApp(); } + + public static boolean enableShowEnabledShortcutsInAccessibilityMenu() { + + return FEATURE_FLAGS.enableShowEnabledShortcutsInAccessibilityMenu(); + } + + public static boolean enableSmartspaceAsAWidget() { + return FEATURE_FLAGS.enableSmartspaceAsAWidget(); } + public static boolean enableSmartspaceRemovalToggle() { + return FEATURE_FLAGS.enableSmartspaceRemovalToggle(); } + + public static boolean enableStateManagerProtoLog() { + + return FEATURE_FLAGS.enableStateManagerProtoLog(); + } + + + public static boolean enableStrictMode() { + + return FEATURE_FLAGS.enableStrictMode(); + } + + public static boolean enableSupportForArchiving() { + return FEATURE_FLAGS.enableSupportForArchiving(); } + public static boolean enableTabletTwoPanePickerV2() { + return FEATURE_FLAGS.enableTabletTwoPanePickerV2(); } + + public static boolean enableTaskbarBehindShade() { + + return FEATURE_FLAGS.enableTaskbarBehindShade(); + } + + public static boolean enableTaskbarCustomization() { + return FEATURE_FLAGS.enableTaskbarCustomization(); } + + public static boolean enableTaskbarForDirectBoot() { + + return FEATURE_FLAGS.enableTaskbarForDirectBoot(); + } + + public static boolean enableTaskbarNoRecreate() { + return FEATURE_FLAGS.enableTaskbarNoRecreate(); } + public static boolean enableTaskbarPinning() { + return FEATURE_FLAGS.enableTaskbarPinning(); } + + public static boolean enableTieredWidgetsByDefaultInPicker() { + + return FEATURE_FLAGS.enableTieredWidgetsByDefaultInPicker(); + } + + public static boolean enableTwoPaneLauncherSettings() { + return FEATURE_FLAGS.enableTwoPaneLauncherSettings(); } + public static boolean enableTwolineAllapps() { + return FEATURE_FLAGS.enableTwolineAllapps(); } + public static boolean enableTwolineToggle() { + return FEATURE_FLAGS.enableTwolineToggle(); } + public static boolean enableUnfoldStateAnimation() { + return FEATURE_FLAGS.enableUnfoldStateAnimation(); } + public static boolean enableUnfoldedTwoPanePicker() { + return FEATURE_FLAGS.enableUnfoldedTwoPanePicker(); } + + public static boolean enableUseTopVisibleActivityForExcludeFromRecentTask() { + + return FEATURE_FLAGS.enableUseTopVisibleActivityForExcludeFromRecentTask(); + } + + public static boolean enableWidgetTapToAdd() { + return FEATURE_FLAGS.enableWidgetTapToAdd(); } + public static boolean enableWorkspaceInflation() { + return FEATURE_FLAGS.enableWorkspaceInflation(); } + public static boolean enabledFoldersInAllApps() { + return FEATURE_FLAGS.enabledFoldersInAllApps(); } + + public static boolean expressiveThemeInTaskbarAndNavigation() { + + return FEATURE_FLAGS.expressiveThemeInTaskbarAndNavigation(); + } + + + public static boolean extendibleThemeManager() { + + return FEATURE_FLAGS.extendibleThemeManager(); + } + + public static boolean floatingSearchBar() { + return FEATURE_FLAGS.floatingSearchBar(); } + public static boolean forceMonochromeAppIcons() { + return FEATURE_FLAGS.forceMonochromeAppIcons(); } + + public static boolean gridMigrationRefactor() { + + return FEATURE_FLAGS.gridMigrationRefactor(); + } + + + public static boolean gsfRes() { + + return FEATURE_FLAGS.gsfRes(); + } + + + public static boolean ignoreThreeFingerTrackpadForNavHandleLongPress() { + + return FEATURE_FLAGS.ignoreThreeFingerTrackpadForNavHandleLongPress(); + } + + + public static boolean letterFastScroller() { + + return FEATURE_FLAGS.letterFastScroller(); + } + + + public static boolean msdlFeedback() { + + return FEATURE_FLAGS.msdlFeedback(); + } + + + public static boolean multilineSearchBar() { + + return FEATURE_FLAGS.multilineSearchBar(); + } + + + public static boolean navigateToChildPreference() { + + return FEATURE_FLAGS.navigateToChildPreference(); + } + + + public static boolean oneGridMountedMode() { + + return FEATURE_FLAGS.oneGridMountedMode(); + } + + + public static boolean oneGridRotationHandling() { + + return FEATURE_FLAGS.oneGridRotationHandling(); + } + + + public static boolean oneGridSpecs() { + + return FEATURE_FLAGS.oneGridSpecs(); + } + + + public static boolean predictiveBackToHomeBlur() { + + return FEATURE_FLAGS.predictiveBackToHomeBlur(); + } + + + public static boolean predictiveBackToHomePolish() { + + return FEATURE_FLAGS.predictiveBackToHomePolish(); + } + + public static boolean privateSpaceAddFloatingMaskView() { + return FEATURE_FLAGS.privateSpaceAddFloatingMaskView(); } + public static boolean privateSpaceAnimation() { + return FEATURE_FLAGS.privateSpaceAnimation(); } + public static boolean privateSpaceAppInstallerButton() { + return FEATURE_FLAGS.privateSpaceAppInstallerButton(); } + public static boolean privateSpaceRestrictAccessibilityDrag() { + return FEATURE_FLAGS.privateSpaceRestrictAccessibilityDrag(); } + public static boolean privateSpaceRestrictItemDrag() { + return FEATURE_FLAGS.privateSpaceRestrictItemDrag(); } + public static boolean privateSpaceSysAppsSeparation() { + return FEATURE_FLAGS.privateSpaceSysAppsSeparation(); } + + public static boolean removeAppsRefreshOnRightClick() { + + return FEATURE_FLAGS.removeAppsRefreshOnRightClick(); + } + + + public static boolean removeExcludeFromScreenMagnificationFlagUsage() { + + return FEATURE_FLAGS.removeExcludeFromScreenMagnificationFlagUsage(); + } + + + public static boolean restoreArchivedAppIconsFromDb() { + + return FEATURE_FLAGS.restoreArchivedAppIconsFromDb(); + } + + + public static boolean restoreArchivedShortcuts() { + + return FEATURE_FLAGS.restoreArchivedShortcuts(); + } + + + public static boolean showTaskbarPinningPopupFromAnywhere() { + + return FEATURE_FLAGS.showTaskbarPinningPopupFromAnywhere(); + } + + + public static boolean syncAppLaunchWithTaskbarStash() { + + return FEATURE_FLAGS.syncAppLaunchWithTaskbarStash(); + } + + + public static boolean taskbarOverflow() { + + return FEATURE_FLAGS.taskbarOverflow(); + } + + + public static boolean taskbarQuietModeChangeSupport() { + + return FEATURE_FLAGS.taskbarQuietModeChangeSupport(); + } + + public static boolean useActivityOverlay() { + return FEATURE_FLAGS.useActivityOverlay(); } + + public static boolean useNewIconForArchivedApps() { + + return FEATURE_FLAGS.useNewIconForArchivedApps(); + } + + + public static boolean useSystemRadiusForAppWidgets() { + + return FEATURE_FLAGS.useSystemRadiusForAppWidgets(); + } + + + public static boolean workSchedulerInWorkProfile() { + + return FEATURE_FLAGS.workSchedulerInWorkProfile(); + } + private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); -} \ No newline at end of file +} diff --git a/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt b/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt index a8a2991fd1c..d8716a5e8d8 100644 --- a/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt +++ b/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt @@ -20,61 +20,72 @@ 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 java.util.concurrent.CopyOnWriteArrayList /** Utility class to manage a set of device configurations */ class DeviceConfigHelper(private val factory: (PropReader) -> ConfigType) { var config: ConfigType private set + private val allKeys: Set + private val propertiesListener = OnPropertiesChangedListener { onDevicePropsChanges(it) } private val sharedPrefChangeListener = OnSharedPreferenceChangeListener { _, _ -> recreateConfig() } - private val changeListeners = mutableListOf() + private val changeListeners = CopyOnWriteArrayList() init { // Initialize the default config once. allKeys = HashSet() - config = factory( - PropReader( - object : PropProvider { - override fun get(key: String, fallback: T): T { - val prefs = prefs - allKeys.add(key) - return when (fallback) { - is Int -> prefs.getInt(key, fallback) as T - is Boolean -> prefs.getBoolean(key, fallback) as T - else -> fallback + config = + factory( + PropReader( + object : PropProvider { + override fun get(key: String, fallback: T): T { + val prefs = prefs + if (fallback is Int) { + allKeys.add(key) + return prefs.getInt(key, fallback) as T + } else if (fallback is Boolean) { + allKeys.add(key) + return prefs.getBoolean(key, fallback) as T + } else return fallback } } - } + ) ) - ) - prefs.registerOnSharedPreferenceChangeListener(sharedPrefChangeListener) } @WorkerThread - private fun onDevicePropsChanges() { + private fun onDevicePropsChanges(properties: Properties) { + if (NAMESPACE_LAUNCHER != properties.namespace) return + if (!allKeys.any(properties.keyset::contains)) return recreateConfig() } private fun recreateConfig() { - config = factory( - PropReader( - object : PropProvider { - override fun get(key: String, fallback: T): T { - return when (fallback) { - is Int -> prefs.getInt(key, fallback) as T - is Boolean -> prefs.getBoolean(key, fallback) as T - else -> fallback + val myProps = + DeviceConfig.getProperties(NAMESPACE_LAUNCHER, *allKeys.toTypedArray()) + config = + factory( + PropReader( + object : PropProvider { + override fun 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 } } - } + ) ) - ) } /** Adds a listener for property changes */ @@ -84,6 +95,7 @@ class DeviceConfigHelper(private val factory: (PropReader) -> Config fun removeChangeListener(r: Runnable) = changeListeners.remove(r) fun close() { + DeviceConfig.removeOnPropertiesChangedListener(propertiesListener) prefs.unregisterOnSharedPreferenceChangeListener(sharedPrefChangeListener) } diff --git a/flags/src/com/android/systemui/CustomFeatureFlags.java b/flags/src/com/android/systemui/CustomFeatureFlags.java index bba7a59f652..8dbbac27555 100644 --- a/flags/src/com/android/systemui/CustomFeatureFlags.java +++ b/flags/src/com/android/systemui/CustomFeatureFlags.java @@ -7,7 +7,6 @@ import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; - /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -17,925 +16,1974 @@ public CustomFeatureFlags(BiPredicate> getValueI mGetValueImpl = getValueImpl; } @Override + public boolean activityTransitionUseLargestWindow() { return getValue(Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, - FeatureFlags::activityTransitionUseLargestWindow); + FeatureFlags::activityTransitionUseLargestWindow); + } + + @Override + + public boolean addBlackBackgroundForWindowMagnifier() { + return getValue(Flags.FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER, + FeatureFlags::addBlackBackgroundForWindowMagnifier); + } + + @Override + + public boolean alwaysComposeQsUiFragment() { + return getValue(Flags.FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT, + FeatureFlags::alwaysComposeQsUiFragment); } @Override + public boolean ambientTouchMonitorListenToDisplayChanges() { return getValue(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, - FeatureFlags::ambientTouchMonitorListenToDisplayChanges); + FeatureFlags::ambientTouchMonitorListenToDisplayChanges); } @Override + public boolean appClipsBacklinks() { return getValue(Flags.FLAG_APP_CLIPS_BACKLINKS, - FeatureFlags::appClipsBacklinks); + FeatureFlags::appClipsBacklinks); + } + + @Override + + public boolean appShortcutRemovalFix() { + return getValue(Flags.FLAG_APP_SHORTCUT_REMOVAL_FIX, + FeatureFlags::appShortcutRemovalFix); } @Override + + public boolean avalancheReplaceHunWhenCritical() { + return getValue(Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL, + FeatureFlags::avalancheReplaceHunWhenCritical); + } + + @Override + public boolean bindKeyguardMediaVisibility() { return getValue(Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, - FeatureFlags::bindKeyguardMediaVisibility); + FeatureFlags::bindKeyguardMediaVisibility); + } + + @Override + + public boolean bouncerUiRevamp() { + return getValue(Flags.FLAG_BOUNCER_UI_REVAMP, + FeatureFlags::bouncerUiRevamp); + } + + @Override + + public boolean bouncerUiRevamp2() { + return getValue(Flags.FLAG_BOUNCER_UI_REVAMP_2, + FeatureFlags::bouncerUiRevamp2); } @Override - public boolean bpTalkback() { - return getValue(Flags.FLAG_BP_TALKBACK, - FeatureFlags::bpTalkback); + + public boolean bpColors() { + return getValue(Flags.FLAG_BP_COLORS, + FeatureFlags::bpColors); } @Override + public boolean brightnessSliderFocusState() { return getValue(Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, - FeatureFlags::brightnessSliderFocusState); + FeatureFlags::brightnessSliderFocusState); + } + + @Override + + public boolean checkLockscreenGoneTransition() { + return getValue(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION, + FeatureFlags::checkLockscreenGoneTransition); + } + + @Override + + public boolean classicFlagsMultiUser() { + return getValue(Flags.FLAG_CLASSIC_FLAGS_MULTI_USER, + FeatureFlags::classicFlagsMultiUser); } @Override - public boolean centralizedStatusBarHeightFix() { - return getValue(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, - FeatureFlags::centralizedStatusBarHeightFix); + + public boolean clipboardImageTimeout() { + return getValue(Flags.FLAG_CLIPBOARD_IMAGE_TIMEOUT, + FeatureFlags::clipboardImageTimeout); } @Override + public boolean clipboardNoninteractiveOnLockscreen() { return getValue(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, - FeatureFlags::clipboardNoninteractiveOnLockscreen); + FeatureFlags::clipboardNoninteractiveOnLockscreen); + } + + @Override + + public boolean clipboardOverlayMultiuser() { + return getValue(Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER, + FeatureFlags::clipboardOverlayMultiuser); + } + + @Override + + public boolean clipboardSharedTransitions() { + return getValue(Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS, + FeatureFlags::clipboardSharedTransitions); + } + + @Override + + public boolean clipboardUseDescriptionMimetype() { + return getValue(Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE, + FeatureFlags::clipboardUseDescriptionMimetype); } @Override - public boolean clockReactiveVariants() { - return getValue(Flags.FLAG_CLOCK_REACTIVE_VARIANTS, - FeatureFlags::clockReactiveVariants); + + public boolean clockFidgetAnimation() { + return getValue(Flags.FLAG_CLOCK_FIDGET_ANIMATION, + FeatureFlags::clockFidgetAnimation); } @Override + public boolean communalBouncerDoNotModifyPluginOpen() { return getValue(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, - FeatureFlags::communalBouncerDoNotModifyPluginOpen); + FeatureFlags::communalBouncerDoNotModifyPluginOpen); + } + + @Override + + public boolean communalEditWidgetsActivityFinishFix() { + return getValue(Flags.FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX, + FeatureFlags::communalEditWidgetsActivityFinishFix); } @Override + public boolean communalHub() { return getValue(Flags.FLAG_COMMUNAL_HUB, - FeatureFlags::communalHub); + FeatureFlags::communalHub); } @Override - public boolean composeBouncer() { - return getValue(Flags.FLAG_COMPOSE_BOUNCER, - FeatureFlags::composeBouncer); + + public boolean communalHubUseThreadPoolForWidgets() { + return getValue(Flags.FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS, + FeatureFlags::communalHubUseThreadPoolForWidgets); + } + + @Override + + public boolean communalResponsiveGrid() { + return getValue(Flags.FLAG_COMMUNAL_RESPONSIVE_GRID, + FeatureFlags::communalResponsiveGrid); + } + + @Override + + public boolean communalSceneKtfRefactor() { + return getValue(Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR, + FeatureFlags::communalSceneKtfRefactor); + } + + @Override + + public boolean communalStandaloneSupport() { + return getValue(Flags.FLAG_COMMUNAL_STANDALONE_SUPPORT, + FeatureFlags::communalStandaloneSupport); + } + + @Override + + public boolean communalTimerFlickerFix() { + return getValue(Flags.FLAG_COMMUNAL_TIMER_FLICKER_FIX, + FeatureFlags::communalTimerFlickerFix); + } + + @Override + + public boolean communalWidgetResizing() { + return getValue(Flags.FLAG_COMMUNAL_WIDGET_RESIZING, + FeatureFlags::communalWidgetResizing); + } + + @Override + + public boolean communalWidgetTrampolineFix() { + return getValue(Flags.FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX, + FeatureFlags::communalWidgetTrampolineFix); } @Override - public boolean composeLockscreen() { - return getValue(Flags.FLAG_COMPOSE_LOCKSCREEN, - FeatureFlags::composeLockscreen); + + public boolean composeBouncer() { + return getValue(Flags.FLAG_COMPOSE_BOUNCER, + FeatureFlags::composeBouncer); } @Override + public boolean confineNotificationTouchToViewWidth() { return getValue(Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, - FeatureFlags::confineNotificationTouchToViewWidth); + FeatureFlags::confineNotificationTouchToViewWidth); } @Override - public boolean constraintBp() { - return getValue(Flags.FLAG_CONSTRAINT_BP, - FeatureFlags::constraintBp); + + public boolean contAuthPlugin() { + return getValue(Flags.FLAG_CONT_AUTH_PLUGIN, + FeatureFlags::contAuthPlugin); } @Override + public boolean contextualTipsAssistantDismissFix() { return getValue(Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, - FeatureFlags::contextualTipsAssistantDismissFix); + FeatureFlags::contextualTipsAssistantDismissFix); } @Override + public boolean coroutineTracing() { return getValue(Flags.FLAG_COROUTINE_TRACING, - FeatureFlags::coroutineTracing); + FeatureFlags::coroutineTracing); } @Override + public boolean createWindowlessWindowMagnifier() { return getValue(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, - FeatureFlags::createWindowlessWindowMagnifier); + FeatureFlags::createWindowlessWindowMagnifier); + } + + @Override + + public boolean debugLiveUpdatesPromoteAll() { + return getValue(Flags.FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL, + FeatureFlags::debugLiveUpdatesPromoteAll); } @Override - public boolean dedicatedNotifInflationThread() { - return getValue(Flags.FLAG_DEDICATED_NOTIF_INFLATION_THREAD, - FeatureFlags::dedicatedNotifInflationThread); + + public boolean decoupleViewControllerInAnimlib() { + return getValue(Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB, + FeatureFlags::decoupleViewControllerInAnimlib); } @Override + public boolean delayShowMagnificationButton() { return getValue(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, - FeatureFlags::delayShowMagnificationButton); + FeatureFlags::delayShowMagnificationButton); } @Override - public boolean delayedWakelockReleaseOnBackgroundThread() { - return getValue(Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD, - FeatureFlags::delayedWakelockReleaseOnBackgroundThread); + + public boolean desktopEffectsQsTile() { + return getValue(Flags.FLAG_DESKTOP_EFFECTS_QS_TILE, + FeatureFlags::desktopEffectsQsTile); } @Override + public boolean deviceEntryUdfpsRefactor() { return getValue(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, - FeatureFlags::deviceEntryUdfpsRefactor); + FeatureFlags::deviceEntryUdfpsRefactor); + } + + @Override + + public boolean disableBlurredShadeVisible() { + return getValue(Flags.FLAG_DISABLE_BLURRED_SHADE_VISIBLE, + FeatureFlags::disableBlurredShadeVisible); } @Override + public boolean disableContextualTipsFrequencyCheck() { return getValue(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, - FeatureFlags::disableContextualTipsFrequencyCheck); + FeatureFlags::disableContextualTipsFrequencyCheck); } @Override + public boolean disableContextualTipsIosSwitcherCheck() { return getValue(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, - FeatureFlags::disableContextualTipsIosSwitcherCheck); + FeatureFlags::disableContextualTipsIosSwitcherCheck); + } + + @Override + + public boolean disableShadeTrackpadTwoFingerSwipe() { + return getValue(Flags.FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE, + FeatureFlags::disableShadeTrackpadTwoFingerSwipe); } @Override - public boolean dozeuiSchedulingAlarmsBackgroundExecution() { - return getValue(Flags.FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION, - FeatureFlags::dozeuiSchedulingAlarmsBackgroundExecution); + + public boolean doubleTapToSleep() { + return getValue(Flags.FLAG_DOUBLE_TAP_TO_SLEEP, + FeatureFlags::doubleTapToSleep); } @Override + public boolean dreamInputSessionPilferOnce() { return getValue(Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, - FeatureFlags::dreamInputSessionPilferOnce); + FeatureFlags::dreamInputSessionPilferOnce); } @Override + public boolean dreamOverlayBouncerSwipeDirectionFiltering() { return getValue(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, - FeatureFlags::dreamOverlayBouncerSwipeDirectionFiltering); + FeatureFlags::dreamOverlayBouncerSwipeDirectionFiltering); } @Override - public boolean dualShade() { - return getValue(Flags.FLAG_DUAL_SHADE, - FeatureFlags::dualShade); + + public boolean dreamOverlayUpdatedFont() { + return getValue(Flags.FLAG_DREAM_OVERLAY_UPDATED_FONT, + FeatureFlags::dreamOverlayUpdatedFont); } @Override + public boolean edgeBackGestureHandlerThread() { return getValue(Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, - FeatureFlags::edgeBackGestureHandlerThread); + FeatureFlags::edgeBackGestureHandlerThread); } @Override + public boolean edgebackGestureHandlerGetRunningTasksBackground() { return getValue(Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, - FeatureFlags::edgebackGestureHandlerGetRunningTasksBackground); + FeatureFlags::edgebackGestureHandlerGetRunningTasksBackground); } @Override + public boolean enableBackgroundKeyguardOndrawnCallback() { return getValue(Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, - FeatureFlags::enableBackgroundKeyguardOndrawnCallback); + FeatureFlags::enableBackgroundKeyguardOndrawnCallback); } @Override + public boolean enableContextualTipForMuteVolume() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, - FeatureFlags::enableContextualTipForMuteVolume); + FeatureFlags::enableContextualTipForMuteVolume); } @Override + public boolean enableContextualTipForPowerOff() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, - FeatureFlags::enableContextualTipForPowerOff); + FeatureFlags::enableContextualTipForPowerOff); } @Override + public boolean enableContextualTipForTakeScreenshot() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, - FeatureFlags::enableContextualTipForTakeScreenshot); + FeatureFlags::enableContextualTipForTakeScreenshot); } @Override + public boolean enableContextualTips() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, - FeatureFlags::enableContextualTips); + FeatureFlags::enableContextualTips); } @Override + public boolean enableEfficientDisplayRepository() { return getValue(Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, - FeatureFlags::enableEfficientDisplayRepository); + FeatureFlags::enableEfficientDisplayRepository); } @Override + public boolean enableLayoutTracing() { return getValue(Flags.FLAG_ENABLE_LAYOUT_TRACING, - FeatureFlags::enableLayoutTracing); + FeatureFlags::enableLayoutTracing); } @Override - public boolean enableViewCaptureTracing() { - return getValue(Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, - FeatureFlags::enableViewCaptureTracing); + + public boolean enableUnderlay() { + return getValue(Flags.FLAG_ENABLE_UNDERLAY, + FeatureFlags::enableUnderlay); } @Override - public boolean enableWidgetPickerSizeFilter() { - return getValue(Flags.FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER, - FeatureFlags::enableWidgetPickerSizeFilter); + + public boolean enableViewCaptureTracing() { + return getValue(Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, + FeatureFlags::enableViewCaptureTracing); } @Override + public boolean enforceBrightnessBaseUserRestriction() { return getValue(Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, - FeatureFlags::enforceBrightnessBaseUserRestriction); + FeatureFlags::enforceBrightnessBaseUserRestriction); } @Override + public boolean exampleFlag() { return getValue(Flags.FLAG_EXAMPLE_FLAG, - FeatureFlags::exampleFlag); + FeatureFlags::exampleFlag); + } + + @Override + + public boolean expandCollapsePrivacyDialog() { + return getValue(Flags.FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG, + FeatureFlags::expandCollapsePrivacyDialog); + } + + @Override + + public boolean expandHeadsUpOnInlineReply() { + return getValue(Flags.FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY, + FeatureFlags::expandHeadsUpOnInlineReply); + } + + @Override + + public boolean expandedPrivacyIndicatorsOnLargeScreen() { + return getValue(Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN, + FeatureFlags::expandedPrivacyIndicatorsOnLargeScreen); + } + + @Override + + public boolean extendedAppsShortcutCategory() { + return getValue(Flags.FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY, + FeatureFlags::extendedAppsShortcutCategory); + } + + @Override + + public boolean faceMessageDeferUpdate() { + return getValue(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE, + FeatureFlags::faceMessageDeferUpdate); + } + + @Override + + public boolean faceScanningAnimationNpeFix() { + return getValue(Flags.FLAG_FACE_SCANNING_ANIMATION_NPE_FIX, + FeatureFlags::faceScanningAnimationNpeFix); + } + + @Override + + public boolean fasterUnlockTransition() { + return getValue(Flags.FLAG_FASTER_UNLOCK_TRANSITION, + FeatureFlags::fasterUnlockTransition); } @Override - public boolean fastUnlockTransition() { - return getValue(Flags.FLAG_FAST_UNLOCK_TRANSITION, - FeatureFlags::fastUnlockTransition); + + public boolean fetchBookmarksXmlKeyboardShortcuts() { + return getValue(Flags.FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS, + FeatureFlags::fetchBookmarksXmlKeyboardShortcuts); } @Override + public boolean fixImageWallpaperCrashSurfaceAlreadyReleased() { return getValue(Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, - FeatureFlags::fixImageWallpaperCrashSurfaceAlreadyReleased); + FeatureFlags::fixImageWallpaperCrashSurfaceAlreadyReleased); } @Override + public boolean fixScreenshotActionDismissSystemWindows() { return getValue(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - FeatureFlags::fixScreenshotActionDismissSystemWindows); + FeatureFlags::fixScreenshotActionDismissSystemWindows); } @Override + public boolean floatingMenuAnimatedTuck() { return getValue(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, - FeatureFlags::floatingMenuAnimatedTuck); + FeatureFlags::floatingMenuAnimatedTuck); + } + + @Override + + public boolean floatingMenuDisplayCutoutSupport() { + return getValue(Flags.FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT, + FeatureFlags::floatingMenuDisplayCutoutSupport); } @Override + public boolean floatingMenuDragToEdit() { return getValue(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, - FeatureFlags::floatingMenuDragToEdit); + FeatureFlags::floatingMenuDragToEdit); } @Override + public boolean floatingMenuDragToHide() { return getValue(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, - FeatureFlags::floatingMenuDragToHide); + FeatureFlags::floatingMenuDragToHide); + } + + @Override + + public boolean floatingMenuHearingDeviceStatusIcon() { + return getValue(Flags.FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON, + FeatureFlags::floatingMenuHearingDeviceStatusIcon); } @Override + public boolean floatingMenuImeDisplacementAnimation() { return getValue(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, - FeatureFlags::floatingMenuImeDisplacementAnimation); + FeatureFlags::floatingMenuImeDisplacementAnimation); } @Override + public boolean floatingMenuNarrowTargetContentObserver() { return getValue(Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, - FeatureFlags::floatingMenuNarrowTargetContentObserver); + FeatureFlags::floatingMenuNarrowTargetContentObserver); + } + + @Override + + public boolean floatingMenuNotifyTargetsChangedOnStrictDiff() { + return getValue(Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF, + FeatureFlags::floatingMenuNotifyTargetsChangedOnStrictDiff); } @Override + public boolean floatingMenuOverlapsNavBarsFlag() { return getValue(Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, - FeatureFlags::floatingMenuOverlapsNavBarsFlag); + FeatureFlags::floatingMenuOverlapsNavBarsFlag); } @Override + public boolean floatingMenuRadiiAnimation() { return getValue(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, - FeatureFlags::floatingMenuRadiiAnimation); - } - - @Override - public boolean generatedPreviews() { - return getValue(Flags.FLAG_GENERATED_PREVIEWS, - FeatureFlags::generatedPreviews); + FeatureFlags::floatingMenuRadiiAnimation); } @Override + public boolean getConnectedDeviceNameUnsynchronized() { return getValue(Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, - FeatureFlags::getConnectedDeviceNameUnsynchronized); + FeatureFlags::getConnectedDeviceNameUnsynchronized); } @Override + public boolean glanceableHubAllowKeyguardWhenDreaming() { return getValue(Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, - FeatureFlags::glanceableHubAllowKeyguardWhenDreaming); + FeatureFlags::glanceableHubAllowKeyguardWhenDreaming); + } + + @Override + + public boolean glanceableHubBlurredBackground() { + return getValue(Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND, + FeatureFlags::glanceableHubBlurredBackground); } @Override - public boolean glanceableHubFullscreenSwipe() { - return getValue(Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE, - FeatureFlags::glanceableHubFullscreenSwipe); + + public boolean glanceableHubDirectEditMode() { + return getValue(Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE, + FeatureFlags::glanceableHubDirectEditMode); } @Override - public boolean glanceableHubGestureHandle() { - return getValue(Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE, - FeatureFlags::glanceableHubGestureHandle); + + public boolean glanceableHubV2() { + return getValue(Flags.FLAG_GLANCEABLE_HUB_V2, + FeatureFlags::glanceableHubV2); } @Override - public boolean glanceableHubShortcutButton() { - return getValue(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, - FeatureFlags::glanceableHubShortcutButton); + + public boolean glanceableHubV2Resources() { + return getValue(Flags.FLAG_GLANCEABLE_HUB_V2_RESOURCES, + FeatureFlags::glanceableHubV2Resources); } @Override - public boolean hapticBrightnessSlider() { - return getValue(Flags.FLAG_HAPTIC_BRIGHTNESS_SLIDER, - FeatureFlags::hapticBrightnessSlider); + + public boolean hapticsForComposeSliders() { + return getValue(Flags.FLAG_HAPTICS_FOR_COMPOSE_SLIDERS, + FeatureFlags::hapticsForComposeSliders); } @Override - public boolean hapticVolumeSlider() { - return getValue(Flags.FLAG_HAPTIC_VOLUME_SLIDER, - FeatureFlags::hapticVolumeSlider); + + public boolean hardwareColorStyles() { + return getValue(Flags.FLAG_HARDWARE_COLOR_STYLES, + FeatureFlags::hardwareColorStyles); } @Override + public boolean hearingAidsQsTileDialog() { return getValue(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, - FeatureFlags::hearingAidsQsTileDialog); + FeatureFlags::hearingAidsQsTileDialog); } @Override + public boolean hearingDevicesDialogRelatedTools() { return getValue(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, - FeatureFlags::hearingDevicesDialogRelatedTools); + FeatureFlags::hearingDevicesDialogRelatedTools); } @Override - public boolean keyboardDockingIndicator() { - return getValue(Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, - FeatureFlags::keyboardDockingIndicator); + + public boolean hideRingerButtonInSingleVolumeMode() { + return getValue(Flags.FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE, + FeatureFlags::hideRingerButtonInSingleVolumeMode); } @Override - public boolean keyboardShortcutHelperRewrite() { - return getValue(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, - FeatureFlags::keyboardShortcutHelperRewrite); + + public boolean homeControlsDreamHsum() { + return getValue(Flags.FLAG_HOME_CONTROLS_DREAM_HSUM, + FeatureFlags::homeControlsDreamHsum); } @Override - public boolean keyguardBottomAreaRefactor() { - return getValue(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, - FeatureFlags::keyguardBottomAreaRefactor); + + public boolean hubEditModeTouchAdjustments() { + return getValue(Flags.FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS, + FeatureFlags::hubEditModeTouchAdjustments); } @Override - public boolean keyguardWmStateRefactor() { - return getValue(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - FeatureFlags::keyguardWmStateRefactor); + + public boolean hubmodeFullscreenVerticalSwipe() { + return getValue(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE, + FeatureFlags::hubmodeFullscreenVerticalSwipe); } @Override - public boolean lightRevealMigration() { - return getValue(Flags.FLAG_LIGHT_REVEAL_MIGRATION, - FeatureFlags::lightRevealMigration); + + public boolean hubmodeFullscreenVerticalSwipeFix() { + return getValue(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, + FeatureFlags::hubmodeFullscreenVerticalSwipeFix); } @Override - public boolean mediaControlsLockscreenShadeBugFix() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, - FeatureFlags::mediaControlsLockscreenShadeBugFix); + + public boolean iconRefresh2025() { + return getValue(Flags.FLAG_ICON_REFRESH_2025, + FeatureFlags::iconRefresh2025); } @Override - public boolean mediaControlsRefactor() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_REFACTOR, - FeatureFlags::mediaControlsRefactor); + + public boolean ignoreTouchesNextToNotificationShelf() { + return getValue(Flags.FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF, + FeatureFlags::ignoreTouchesNextToNotificationShelf); } @Override - public boolean mediaControlsUserInitiatedDeleteintent() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, - FeatureFlags::mediaControlsUserInitiatedDeleteintent); + + public boolean indicationTextA11yFix() { + return getValue(Flags.FLAG_INDICATION_TEXT_A11Y_FIX, + FeatureFlags::indicationTextA11yFix); } @Override - public boolean migrateClocksToBlueprint() { - return getValue(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - FeatureFlags::migrateClocksToBlueprint); + + public boolean keyboardDockingIndicator() { + return getValue(Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, + FeatureFlags::keyboardDockingIndicator); } @Override - public boolean newAodTransition() { - return getValue(Flags.FLAG_NEW_AOD_TRANSITION, - FeatureFlags::newAodTransition); + + public boolean keyboardShortcutHelperRewrite() { + return getValue(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, + FeatureFlags::keyboardShortcutHelperRewrite); } @Override - public boolean newTouchpadGesturesTutorial() { - return getValue(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, - FeatureFlags::newTouchpadGesturesTutorial); + + public boolean keyboardShortcutHelperShortcutCustomizer() { + return getValue(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER, + FeatureFlags::keyboardShortcutHelperShortcutCustomizer); } @Override - public boolean newVolumePanel() { - return getValue(Flags.FLAG_NEW_VOLUME_PANEL, - FeatureFlags::newVolumePanel); + + public boolean keyboardTouchpadContextualEducation() { + return getValue(Flags.FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION, + FeatureFlags::keyboardTouchpadContextualEducation); } @Override - public boolean notificationAsyncGroupHeaderInflation() { - return getValue(Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, - FeatureFlags::notificationAsyncGroupHeaderInflation); + + public boolean keyguardTransitionForceFinishOnScreenOff() { + return getValue(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF, + FeatureFlags::keyguardTransitionForceFinishOnScreenOff); } @Override - public boolean notificationAsyncHybridViewInflation() { - return getValue(Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, - FeatureFlags::notificationAsyncHybridViewInflation); + + public boolean keyguardWmReorderAtmsCalls() { + return getValue(Flags.FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS, + FeatureFlags::keyguardWmReorderAtmsCalls); } @Override - public boolean notificationAvalancheSuppression() { - return getValue(Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, - FeatureFlags::notificationAvalancheSuppression); + + public boolean keyguardWmStateRefactor() { + return getValue(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + FeatureFlags::keyguardWmStateRefactor); } @Override - public boolean notificationAvalancheThrottleHun() { - return getValue(Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, - FeatureFlags::notificationAvalancheThrottleHun); + + public boolean lockscreenFont() { + return getValue(Flags.FLAG_LOCKSCREEN_FONT, + FeatureFlags::lockscreenFont); } @Override - public boolean notificationBackgroundTintOptimization() { - return getValue(Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, - FeatureFlags::notificationBackgroundTintOptimization); + + public boolean lowLightClockDream() { + return getValue(Flags.FLAG_LOW_LIGHT_CLOCK_DREAM, + FeatureFlags::lowLightClockDream); } @Override - public boolean notificationColorUpdateLogger() { - return getValue(Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, - FeatureFlags::notificationColorUpdateLogger); + + public boolean magneticNotificationSwipes() { + return getValue(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES, + FeatureFlags::magneticNotificationSwipes); } @Override - public boolean notificationContentAlphaOptimization() { - return getValue(Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, - FeatureFlags::notificationContentAlphaOptimization); + + public boolean mediaControlsA11yColors() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS, + FeatureFlags::mediaControlsA11yColors); } @Override - public boolean notificationFooterBackgroundTintOptimization() { - return getValue(Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, - FeatureFlags::notificationFooterBackgroundTintOptimization); + + public boolean mediaControlsButtonMedia3() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3, + FeatureFlags::mediaControlsButtonMedia3); } @Override - public boolean notificationMediaManagerBackgroundExecution() { - return getValue(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION, - FeatureFlags::notificationMediaManagerBackgroundExecution); + + public boolean mediaControlsButtonMedia3Placement() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT, + FeatureFlags::mediaControlsButtonMedia3Placement); } @Override - public boolean notificationMinimalismPrototype() { - return getValue(Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE, - FeatureFlags::notificationMinimalismPrototype); + + public boolean mediaControlsDeviceManagerBackgroundExecution() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION, + FeatureFlags::mediaControlsDeviceManagerBackgroundExecution); + } + + @Override + + public boolean mediaControlsDrawablesReuseBugfix() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX, + FeatureFlags::mediaControlsDrawablesReuseBugfix); + } + + @Override + + public boolean mediaControlsLockscreenShadeBugFix() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, + FeatureFlags::mediaControlsLockscreenShadeBugFix); + } + + @Override + + public boolean mediaControlsUiUpdate() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_UI_UPDATE, + FeatureFlags::mediaControlsUiUpdate); + } + + @Override + + public boolean mediaControlsUmoInflationInBackground() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND, + FeatureFlags::mediaControlsUmoInflationInBackground); + } + + @Override + + public boolean mediaControlsUserInitiatedDeleteintent() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, + FeatureFlags::mediaControlsUserInitiatedDeleteintent); + } + + @Override + + public boolean mediaLoadMetadataViaMediaDataLoader() { + return getValue(Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER, + FeatureFlags::mediaLoadMetadataViaMediaDataLoader); + } + + @Override + + public boolean mediaLockscreenLaunchAnimation() { + return getValue(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION, + FeatureFlags::mediaLockscreenLaunchAnimation); + } + + @Override + + public boolean mediaProjectionDialogBehindLockscreen() { + return getValue(Flags.FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN, + FeatureFlags::mediaProjectionDialogBehindLockscreen); + } + + @Override + + public boolean mediaProjectionGreyErrorText() { + return getValue(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT, + FeatureFlags::mediaProjectionGreyErrorText); + } + + @Override + + public boolean mediaProjectionRequestAttributionFix() { + return getValue(Flags.FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX, + FeatureFlags::mediaProjectionRequestAttributionFix); + } + + @Override + + public boolean modesUiDialogPaging() { + return getValue(Flags.FLAG_MODES_UI_DIALOG_PAGING, + FeatureFlags::modesUiDialogPaging); + } + + @Override + + public boolean moveTransitionAnimationLayer() { + return getValue(Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER, + FeatureFlags::moveTransitionAnimationLayer); + } + + @Override + + public boolean msdlFeedback() { + return getValue(Flags.FLAG_MSDL_FEEDBACK, + FeatureFlags::msdlFeedback); + } + + @Override + + public boolean multiuserWifiPickerTrackerSupport() { + return getValue(Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT, + FeatureFlags::multiuserWifiPickerTrackerSupport); + } + + @Override + + public boolean newAodTransition() { + return getValue(Flags.FLAG_NEW_AOD_TRANSITION, + FeatureFlags::newAodTransition); + } + + @Override + + public boolean newVolumePanel() { + return getValue(Flags.FLAG_NEW_VOLUME_PANEL, + FeatureFlags::newVolumePanel); + } + + @Override + + public boolean nonTouchscreenDevicesBypassFalsing() { + return getValue(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING, + FeatureFlags::nonTouchscreenDevicesBypassFalsing); + } + + @Override + + public boolean notesRoleQsTile() { + return getValue(Flags.FLAG_NOTES_ROLE_QS_TILE, + FeatureFlags::notesRoleQsTile); + } + + @Override + + public boolean notificationAddXOnHoverToDismiss() { + return getValue(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS, + FeatureFlags::notificationAddXOnHoverToDismiss); + } + + @Override + + public boolean notificationAmbientSuppressionAfterInflation() { + return getValue(Flags.FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION, + FeatureFlags::notificationAmbientSuppressionAfterInflation); + } + + @Override + + public boolean notificationAnimatedActionsTreatment() { + return getValue(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT, + FeatureFlags::notificationAnimatedActionsTreatment); + } + + @Override + + public boolean notificationAppearNonlinear() { + return getValue(Flags.FLAG_NOTIFICATION_APPEAR_NONLINEAR, + FeatureFlags::notificationAppearNonlinear); + } + + @Override + + public boolean notificationAsyncGroupHeaderInflation() { + return getValue(Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, + FeatureFlags::notificationAsyncGroupHeaderInflation); + } + + @Override + + public boolean notificationAsyncHybridViewInflation() { + return getValue(Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, + FeatureFlags::notificationAsyncHybridViewInflation); + } + + @Override + + public boolean notificationAvalancheSuppression() { + return getValue(Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, + FeatureFlags::notificationAvalancheSuppression); + } + + @Override + + public boolean notificationAvalancheThrottleHun() { + return getValue(Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, + FeatureFlags::notificationAvalancheThrottleHun); + } + + @Override + + public boolean notificationBackgroundTintOptimization() { + return getValue(Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, + FeatureFlags::notificationBackgroundTintOptimization); + } + + @Override + + public boolean notificationBundleUi() { + return getValue(Flags.FLAG_NOTIFICATION_BUNDLE_UI, + FeatureFlags::notificationBundleUi); + } + + @Override + + public boolean notificationColorUpdateLogger() { + return getValue(Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, + FeatureFlags::notificationColorUpdateLogger); + } + + @Override + + public boolean notificationContentAlphaOptimization() { + return getValue(Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, + FeatureFlags::notificationContentAlphaOptimization); + } + + @Override + + public boolean notificationFooterBackgroundTintOptimization() { + return getValue(Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, + FeatureFlags::notificationFooterBackgroundTintOptimization); } @Override + public boolean notificationOverExpansionClippingFix() { return getValue(Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, - FeatureFlags::notificationOverExpansionClippingFix); + FeatureFlags::notificationOverExpansionClippingFix); + } + + @Override + + public boolean notificationReentrantDismiss() { + return getValue(Flags.FLAG_NOTIFICATION_REENTRANT_DISMISS, + FeatureFlags::notificationReentrantDismiss); } @Override - public boolean notificationPulsingFix() { - return getValue(Flags.FLAG_NOTIFICATION_PULSING_FIX, - FeatureFlags::notificationPulsingFix); + + public boolean notificationRowAccessibilityExpanded() { + return getValue(Flags.FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED, + FeatureFlags::notificationRowAccessibilityExpanded); } @Override + public boolean notificationRowContentBinderRefactor() { return getValue(Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, - FeatureFlags::notificationRowContentBinderRefactor); + FeatureFlags::notificationRowContentBinderRefactor); } @Override + + public boolean notificationRowTransparency() { + return getValue(Flags.FLAG_NOTIFICATION_ROW_TRANSPARENCY, + FeatureFlags::notificationRowTransparency); + } + + @Override + public boolean notificationRowUserContext() { return getValue(Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, - FeatureFlags::notificationRowUserContext); + FeatureFlags::notificationRowUserContext); + } + + @Override + + public boolean notificationShadeBlur() { + return getValue(Flags.FLAG_NOTIFICATION_SHADE_BLUR, + FeatureFlags::notificationShadeBlur); + } + + @Override + + public boolean notificationShadeUiThread() { + return getValue(Flags.FLAG_NOTIFICATION_SHADE_UI_THREAD, + FeatureFlags::notificationShadeUiThread); + } + + @Override + + public boolean notificationSkipSilentUpdates() { + return getValue(Flags.FLAG_NOTIFICATION_SKIP_SILENT_UPDATES, + FeatureFlags::notificationSkipSilentUpdates); } @Override + + public boolean notificationTransparentHeaderFix() { + return getValue(Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX, + FeatureFlags::notificationTransparentHeaderFix); + } + + @Override + public boolean notificationViewFlipperPausingV2() { return getValue(Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, - FeatureFlags::notificationViewFlipperPausingV2); + FeatureFlags::notificationViewFlipperPausingV2); } @Override + public boolean notificationsBackgroundIcons() { return getValue(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, - FeatureFlags::notificationsBackgroundIcons); + FeatureFlags::notificationsBackgroundIcons); } @Override - public boolean notificationsFooterViewRefactor() { - return getValue(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR, - FeatureFlags::notificationsFooterViewRefactor); - } - @Override - public boolean notificationsHeadsUpRefactor() { - return getValue(Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR, - FeatureFlags::notificationsHeadsUpRefactor); + public boolean notificationsFooterVisibilityFix() { + return getValue(Flags.FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX, + FeatureFlags::notificationsFooterVisibilityFix); } @Override + public boolean notificationsHideOnDisplaySwitch() { return getValue(Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, - FeatureFlags::notificationsHideOnDisplaySwitch); + FeatureFlags::notificationsHideOnDisplaySwitch); } @Override + + public boolean notificationsHunSharedAnimationValues() { + return getValue(Flags.FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES, + FeatureFlags::notificationsHunSharedAnimationValues); + } + + @Override + public boolean notificationsIconContainerRefactor() { return getValue(Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, - FeatureFlags::notificationsIconContainerRefactor); + FeatureFlags::notificationsIconContainerRefactor); } @Override - public boolean notificationsImprovedHunAnimation() { - return getValue(Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION, - FeatureFlags::notificationsImprovedHunAnimation); + + public boolean notificationsLaunchRadius() { + return getValue(Flags.FLAG_NOTIFICATIONS_LAUNCH_RADIUS, + FeatureFlags::notificationsLaunchRadius); } @Override + public boolean notificationsLiveDataStoreRefactor() { return getValue(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, - FeatureFlags::notificationsLiveDataStoreRefactor); + FeatureFlags::notificationsLiveDataStoreRefactor); + } + + @Override + + public boolean notificationsPinnedHunInShade() { + return getValue(Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE, + FeatureFlags::notificationsPinnedHunInShade); + } + + @Override + + public boolean notificationsRedesignFooterView() { + return getValue(Flags.FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW, + FeatureFlags::notificationsRedesignFooterView); + } + + @Override + + public boolean notificationsRedesignGuts() { + return getValue(Flags.FLAG_NOTIFICATIONS_REDESIGN_GUTS, + FeatureFlags::notificationsRedesignGuts); } @Override + + public boolean notifyPasswordTextViewUserActivityInBackground() { + return getValue(Flags.FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND, + FeatureFlags::notifyPasswordTextViewUserActivityInBackground); + } + + @Override + public boolean notifyPowerManagerUserActivityBackground() { return getValue(Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, - FeatureFlags::notifyPowerManagerUserActivityBackground); + FeatureFlags::notifyPowerManagerUserActivityBackground); } @Override - public boolean pinInputFieldStyledFocusState() { - return getValue(Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, - FeatureFlags::pinInputFieldStyledFocusState); + + public boolean onlyShowMediaStreamSliderInSingleVolumeMode() { + return getValue(Flags.FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE, + FeatureFlags::onlyShowMediaStreamSliderInSingleVolumeMode); + } + + @Override + + public boolean outputSwitcherRedesign() { + return getValue(Flags.FLAG_OUTPUT_SWITCHER_REDESIGN, + FeatureFlags::outputSwitcherRedesign); + } + + @Override + + public boolean overrideSuppressOverlayCondition() { + return getValue(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION, + FeatureFlags::overrideSuppressOverlayCondition); + } + + @Override + + public boolean permissionHelperInlineUiRichOngoing() { + return getValue(Flags.FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING, + FeatureFlags::permissionHelperInlineUiRichOngoing); + } + + @Override + + public boolean permissionHelperUiRichOngoing() { + return getValue(Flags.FLAG_PERMISSION_HELPER_UI_RICH_ONGOING, + FeatureFlags::permissionHelperUiRichOngoing); } @Override - public boolean predictiveBackAnimateBouncer() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER, - FeatureFlags::predictiveBackAnimateBouncer); + + public boolean physicalNotificationMovement() { + return getValue(Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT, + FeatureFlags::physicalNotificationMovement); } @Override - public boolean predictiveBackAnimateDialogs() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS, - FeatureFlags::predictiveBackAnimateDialogs); + + public boolean pinInputFieldStyledFocusState() { + return getValue(Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, + FeatureFlags::pinInputFieldStyledFocusState); } @Override + public boolean predictiveBackAnimateShade() { return getValue(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, - FeatureFlags::predictiveBackAnimateShade); + FeatureFlags::predictiveBackAnimateShade); } @Override - public boolean predictiveBackSysui() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_SYSUI, - FeatureFlags::predictiveBackSysui); + + public boolean predictiveBackDelayWmTransition() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION, + FeatureFlags::predictiveBackDelayWmTransition); } @Override + public boolean priorityPeopleSection() { return getValue(Flags.FLAG_PRIORITY_PEOPLE_SECTION, - FeatureFlags::priorityPeopleSection); + FeatureFlags::priorityPeopleSection); } @Override - public boolean privacyDotUnfoldWrongCornerFix() { - return getValue(Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX, - FeatureFlags::privacyDotUnfoldWrongCornerFix); - } - @Override - public boolean pssAppSelectorAbruptExitFix() { - return getValue(Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, - FeatureFlags::pssAppSelectorAbruptExitFix); + public boolean promoteNotificationsAutomatically() { + return getValue(Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, + FeatureFlags::promoteNotificationsAutomatically); } @Override + public boolean pssAppSelectorRecentsSplitScreen() { return getValue(Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, - FeatureFlags::pssAppSelectorRecentsSplitScreen); + FeatureFlags::pssAppSelectorRecentsSplitScreen); } @Override + public boolean pssTaskSwitcher() { return getValue(Flags.FLAG_PSS_TASK_SWITCHER, - FeatureFlags::pssTaskSwitcher); + FeatureFlags::pssTaskSwitcher); } @Override + public boolean qsCustomTileClickGuaranteedBugFix() { return getValue(Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, - FeatureFlags::qsCustomTileClickGuaranteedBugFix); + FeatureFlags::qsCustomTileClickGuaranteedBugFix); } @Override - public boolean qsNewPipeline() { - return getValue(Flags.FLAG_QS_NEW_PIPELINE, - FeatureFlags::qsNewPipeline); - } - @Override public boolean qsNewTiles() { return getValue(Flags.FLAG_QS_NEW_TILES, - FeatureFlags::qsNewTiles); + FeatureFlags::qsNewTiles); } @Override + public boolean qsNewTilesFuture() { return getValue(Flags.FLAG_QS_NEW_TILES_FUTURE, - FeatureFlags::qsNewTilesFuture); + FeatureFlags::qsNewTilesFuture); } @Override + + public boolean qsQuickRebindActiveTiles() { + return getValue(Flags.FLAG_QS_QUICK_REBIND_ACTIVE_TILES, + FeatureFlags::qsQuickRebindActiveTiles); + } + + @Override + + public boolean qsRegisterSettingObserverOnBgThread() { + return getValue(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD, + FeatureFlags::qsRegisterSettingObserverOnBgThread); + } + + @Override + + public boolean qsTileDetailedView() { + return getValue(Flags.FLAG_QS_TILE_DETAILED_VIEW, + FeatureFlags::qsTileDetailedView); + } + + @Override + public boolean qsTileFocusState() { return getValue(Flags.FLAG_QS_TILE_FOCUS_STATE, - FeatureFlags::qsTileFocusState); + FeatureFlags::qsTileFocusState); } @Override + public boolean qsUiRefactor() { return getValue(Flags.FLAG_QS_UI_REFACTOR, - FeatureFlags::qsUiRefactor); + FeatureFlags::qsUiRefactor); } @Override - public boolean quickSettingsVisualHapticsLongpress() { - return getValue(Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS, - FeatureFlags::quickSettingsVisualHapticsLongpress); + + public boolean qsUiRefactorComposeFragment() { + return getValue(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT, + FeatureFlags::qsUiRefactorComposeFragment); } @Override + public boolean recordIssueQsTile() { return getValue(Flags.FLAG_RECORD_ISSUE_QS_TILE, - FeatureFlags::recordIssueQsTile); + FeatureFlags::recordIssueQsTile); + } + + @Override + + public boolean redesignMagnificationWindowSize() { + return getValue(Flags.FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE, + FeatureFlags::redesignMagnificationWindowSize); } @Override + public boolean refactorGetCurrentUser() { return getValue(Flags.FLAG_REFACTOR_GET_CURRENT_USER, - FeatureFlags::refactorGetCurrentUser); + FeatureFlags::refactorGetCurrentUser); } @Override + public boolean registerBatteryControllerReceiversInCorestartable() { return getValue(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, - FeatureFlags::registerBatteryControllerReceiversInCorestartable); + FeatureFlags::registerBatteryControllerReceiversInCorestartable); + } + + @Override + + public boolean registerContentObserversAsync() { + return getValue(Flags.FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC, + FeatureFlags::registerContentObserversAsync); } @Override + public boolean registerNewWalletCardInBackground() { return getValue(Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, - FeatureFlags::registerNewWalletCardInBackground); + FeatureFlags::registerNewWalletCardInBackground); } @Override + public boolean registerWallpaperNotifierBackground() { return getValue(Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, - FeatureFlags::registerWallpaperNotifierBackground); + FeatureFlags::registerWallpaperNotifierBackground); } @Override - public boolean registerZenModeContentObserverBackground() { - return getValue(Flags.FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND, - FeatureFlags::registerZenModeContentObserverBackground); + + public boolean relockWithPowerButtonImmediately() { + return getValue(Flags.FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY, + FeatureFlags::relockWithPowerButtonImmediately); } @Override + public boolean removeDreamOverlayHideOnTouch() { return getValue(Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, - FeatureFlags::removeDreamOverlayHideOnTouch); + FeatureFlags::removeDreamOverlayHideOnTouch); + } + + @Override + + public boolean removeUpdateListenerInQsIconViewImpl() { + return getValue(Flags.FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL, + FeatureFlags::removeUpdateListenerInQsIconViewImpl); } @Override + public boolean restToUnlock() { return getValue(Flags.FLAG_REST_TO_UNLOCK, - FeatureFlags::restToUnlock); + FeatureFlags::restToUnlock); } @Override + public boolean restartDreamOnUnocclude() { return getValue(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, - FeatureFlags::restartDreamOnUnocclude); + FeatureFlags::restartDreamOnUnocclude); } @Override + public boolean revampedBouncerMessages() { return getValue(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, - FeatureFlags::revampedBouncerMessages); + FeatureFlags::revampedBouncerMessages); } @Override + public boolean runFingerprintDetectOnDismissibleKeyguard() { return getValue(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, - FeatureFlags::runFingerprintDetectOnDismissibleKeyguard); + FeatureFlags::runFingerprintDetectOnDismissibleKeyguard); } @Override + public boolean saveAndRestoreMagnificationSettingsButtons() { return getValue(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, - FeatureFlags::saveAndRestoreMagnificationSettingsButtons); + FeatureFlags::saveAndRestoreMagnificationSettingsButtons); } @Override + public boolean sceneContainer() { return getValue(Flags.FLAG_SCENE_CONTAINER, - FeatureFlags::sceneContainer); + FeatureFlags::sceneContainer); } @Override + public boolean screenshareNotificationHidingBugFix() { return getValue(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - FeatureFlags::screenshareNotificationHidingBugFix); + FeatureFlags::screenshareNotificationHidingBugFix); } @Override + public boolean screenshotActionDismissSystemWindows() { return getValue(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - FeatureFlags::screenshotActionDismissSystemWindows); + FeatureFlags::screenshotActionDismissSystemWindows); } @Override - public boolean screenshotPrivateProfileAccessibilityAnnouncementFix() { - return getValue(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX, - FeatureFlags::screenshotPrivateProfileAccessibilityAnnouncementFix); + + public boolean screenshotMultidisplayFocusChange() { + return getValue(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE, + FeatureFlags::screenshotMultidisplayFocusChange); } @Override - public boolean screenshotPrivateProfileBehaviorFix() { - return getValue(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX, - FeatureFlags::screenshotPrivateProfileBehaviorFix); + + public boolean screenshotPolicySplitAndDesktopMode() { + return getValue(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE, + FeatureFlags::screenshotPolicySplitAndDesktopMode); } @Override + public boolean screenshotScrollCropViewCrashFix() { return getValue(Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, - FeatureFlags::screenshotScrollCropViewCrashFix); + FeatureFlags::screenshotScrollCropViewCrashFix); + } + + @Override + + public boolean screenshotUiControllerRefactor() { + return getValue(Flags.FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR, + FeatureFlags::screenshotUiControllerRefactor); + } + + @Override + + public boolean secondaryUserWidgetHost() { + return getValue(Flags.FLAG_SECONDARY_USER_WIDGET_HOST, + FeatureFlags::secondaryUserWidgetHost); } @Override - public boolean screenshotShelfUi2() { - return getValue(Flags.FLAG_SCREENSHOT_SHELF_UI2, - FeatureFlags::screenshotShelfUi2); + + public boolean settingsExtRegisterContentObserverOnBgThread() { + return getValue(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD, + FeatureFlags::settingsExtRegisterContentObserverOnBgThread); } @Override - public boolean shadeCollapseActivityLaunchFix() { - return getValue(Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX, - FeatureFlags::shadeCollapseActivityLaunchFix); + + public boolean shadeExpandsOnStatusBarLongPress() { + return getValue(Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS, + FeatureFlags::shadeExpandsOnStatusBarLongPress); } @Override + + public boolean shadeHeaderFontUpdate() { + return getValue(Flags.FLAG_SHADE_HEADER_FONT_UPDATE, + FeatureFlags::shadeHeaderFontUpdate); + } + + @Override + + public boolean shadeLaunchAccessibility() { + return getValue(Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY, + FeatureFlags::shadeLaunchAccessibility); + } + + @Override + + public boolean shadeWindowGoesAround() { + return getValue(Flags.FLAG_SHADE_WINDOW_GOES_AROUND, + FeatureFlags::shadeWindowGoesAround); + } + + @Override + public boolean shaderlibLoadingEffectRefactor() { return getValue(Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, - FeatureFlags::shaderlibLoadingEffectRefactor); + FeatureFlags::shaderlibLoadingEffectRefactor); } @Override + + public boolean shortcutHelperKeyGlyph() { + return getValue(Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH, + FeatureFlags::shortcutHelperKeyGlyph); + } + + @Override + + public boolean showAudioSharingSliderInVolumePanel() { + return getValue(Flags.FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL, + FeatureFlags::showAudioSharingSliderInVolumePanel); + } + + @Override + + public boolean showClipboardIndication() { + return getValue(Flags.FLAG_SHOW_CLIPBOARD_INDICATION, + FeatureFlags::showClipboardIndication); + } + + @Override + + public boolean showLockedByYourWatchKeyguardIndicator() { + return getValue(Flags.FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR, + FeatureFlags::showLockedByYourWatchKeyguardIndicator); + } + + @Override + + public boolean showToastWhenAppControlBrightness() { + return getValue(Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS, + FeatureFlags::showToastWhenAppControlBrightness); + } + + @Override + + public boolean simPinBouncerReset() { + return getValue(Flags.FLAG_SIM_PIN_BOUNCER_RESET, + FeatureFlags::simPinBouncerReset); + } + + @Override + + public boolean simPinRaceConditionOnRestart() { + return getValue(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART, + FeatureFlags::simPinRaceConditionOnRestart); + } + + @Override + + public boolean simPinUseSlotId() { + return getValue(Flags.FLAG_SIM_PIN_USE_SLOT_ID, + FeatureFlags::simPinUseSlotId); + } + + @Override + + public boolean skipHideSensitiveNotifAnimation() { + return getValue(Flags.FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION, + FeatureFlags::skipHideSensitiveNotifAnimation); + } + + @Override + public boolean sliceBroadcastRelayInBackground() { return getValue(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, - FeatureFlags::sliceBroadcastRelayInBackground); + FeatureFlags::sliceBroadcastRelayInBackground); } @Override + public boolean sliceManagerBinderCallBackground() { return getValue(Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, - FeatureFlags::sliceManagerBinderCallBackground); + FeatureFlags::sliceManagerBinderCallBackground); } @Override + public boolean smartspaceLockscreenViewmodel() { return getValue(Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, - FeatureFlags::smartspaceLockscreenViewmodel); + FeatureFlags::smartspaceLockscreenViewmodel); } @Override + public boolean smartspaceRelocateToBottom() { return getValue(Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, - FeatureFlags::smartspaceRelocateToBottom); + FeatureFlags::smartspaceRelocateToBottom); + } + + @Override + + public boolean smartspaceRemoteviewsRenderingFix() { + return getValue(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX, + FeatureFlags::smartspaceRemoteviewsRenderingFix); } @Override - public boolean smartspaceRemoteviewsRendering() { - return getValue(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING, - FeatureFlags::smartspaceRemoteviewsRendering); + + public boolean smartspaceSwipeEventLoggingFix() { + return getValue(Flags.FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX, + FeatureFlags::smartspaceSwipeEventLoggingFix); + } + + @Override + + public boolean smartspaceViewpager2() { + return getValue(Flags.FLAG_SMARTSPACE_VIEWPAGER2, + FeatureFlags::smartspaceViewpager2); } @Override + + public boolean sounddoseCustomization() { + return getValue(Flags.FLAG_SOUNDDOSE_CUSTOMIZATION, + FeatureFlags::sounddoseCustomization); + } + + @Override + + public boolean spatialModelAppPushback() { + return getValue(Flags.FLAG_SPATIAL_MODEL_APP_PUSHBACK, + FeatureFlags::spatialModelAppPushback); + } + + @Override + + public boolean stabilizeHeadsUpGroupV2() { + return getValue(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2, + FeatureFlags::stabilizeHeadsUpGroupV2); + } + + @Override + + public boolean statusBarAlwaysCheckUnderlyingNetworks() { + return getValue(Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS, + FeatureFlags::statusBarAlwaysCheckUnderlyingNetworks); + } + + @Override + + public boolean statusBarAutoStartScreenRecordChip() { + return getValue(Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP, + FeatureFlags::statusBarAutoStartScreenRecordChip); + } + + @Override + + public boolean statusBarChipsModernization() { + return getValue(Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION, + FeatureFlags::statusBarChipsModernization); + } + + @Override + + public boolean statusBarChipsReturnAnimations() { + return getValue(Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS, + FeatureFlags::statusBarChipsReturnAnimations); + } + + @Override + + public boolean statusBarFontUpdates() { + return getValue(Flags.FLAG_STATUS_BAR_FONT_UPDATES, + FeatureFlags::statusBarFontUpdates); + } + + @Override + + public boolean statusBarMobileIconKairos() { + return getValue(Flags.FLAG_STATUS_BAR_MOBILE_ICON_KAIROS, + FeatureFlags::statusBarMobileIconKairos); + } + + @Override + public boolean statusBarMonochromeIconsFix() { return getValue(Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, - FeatureFlags::statusBarMonochromeIconsFix); + FeatureFlags::statusBarMonochromeIconsFix); + } + + @Override + + public boolean statusBarNoHunBehavior() { + return getValue(Flags.FLAG_STATUS_BAR_NO_HUN_BEHAVIOR, + FeatureFlags::statusBarNoHunBehavior); + } + + @Override + + public boolean statusBarPopupChips() { + return getValue(Flags.FLAG_STATUS_BAR_POPUP_CHIPS, + FeatureFlags::statusBarPopupChips); + } + + @Override + + public boolean statusBarRootModernization() { + return getValue(Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION, + FeatureFlags::statusBarRootModernization); } @Override - public boolean statusBarScreenSharingChips() { - return getValue(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, - FeatureFlags::statusBarScreenSharingChips); + + public boolean statusBarShowAudioOnlyProjectionChip() { + return getValue(Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, + FeatureFlags::statusBarShowAudioOnlyProjectionChip); + } + + @Override + + public boolean statusBarSignalPolicyRefactor() { + return getValue(Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR, + FeatureFlags::statusBarSignalPolicyRefactor); + } + + @Override + + public boolean statusBarSignalPolicyRefactorEthernet() { + return getValue(Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET, + FeatureFlags::statusBarSignalPolicyRefactorEthernet); } @Override + public boolean statusBarStaticInoutIndicators() { return getValue(Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, - FeatureFlags::statusBarStaticInoutIndicators); + FeatureFlags::statusBarStaticInoutIndicators); + } + + @Override + + public boolean statusBarStopUpdatingWindowHeight() { + return getValue(Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT, + FeatureFlags::statusBarStopUpdatingWindowHeight); + } + + @Override + + public boolean statusBarSwipeOverChip() { + return getValue(Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP, + FeatureFlags::statusBarSwipeOverChip); + } + + @Override + + public boolean statusBarSwitchToSpnFromDataSpn() { + return getValue(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN, + FeatureFlags::statusBarSwitchToSpnFromDataSpn); + } + + @Override + + public boolean statusBarUiThread() { + return getValue(Flags.FLAG_STATUS_BAR_UI_THREAD, + FeatureFlags::statusBarUiThread); + } + + @Override + + public boolean statusBarWindowNoCustomTouch() { + return getValue(Flags.FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH, + FeatureFlags::statusBarWindowNoCustomTouch); + } + + @Override + + public boolean stoppableFgsSystemApp() { + return getValue(Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP, + FeatureFlags::stoppableFgsSystemApp); } @Override + public boolean switchUserOnBg() { return getValue(Flags.FLAG_SWITCH_USER_ON_BG, - FeatureFlags::switchUserOnBg); + FeatureFlags::switchUserOnBg); } @Override + public boolean sysuiTeamfood() { return getValue(Flags.FLAG_SYSUI_TEAMFOOD, - FeatureFlags::sysuiTeamfood); + FeatureFlags::sysuiTeamfood); } @Override + public boolean themeOverlayControllerWakefulnessDeprecation() { return getValue(Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, - FeatureFlags::themeOverlayControllerWakefulnessDeprecation); + FeatureFlags::themeOverlayControllerWakefulnessDeprecation); + } + + @Override + + public boolean transitionRaceCondition() { + return getValue(Flags.FLAG_TRANSITION_RACE_CONDITION, + FeatureFlags::transitionRaceCondition); } @Override + public boolean translucentOccludingActivityFix() { return getValue(Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, - FeatureFlags::translucentOccludingActivityFix); + FeatureFlags::translucentOccludingActivityFix); } @Override - public boolean truncatedStatusBarIconsFix() { - return getValue(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX, - FeatureFlags::truncatedStatusBarIconsFix); + + public boolean tvGlobalActionsFocus() { + return getValue(Flags.FLAG_TV_GLOBAL_ACTIONS_FOCUS, + FeatureFlags::tvGlobalActionsFocus); } @Override + public boolean udfpsViewPerformance() { return getValue(Flags.FLAG_UDFPS_VIEW_PERFORMANCE, - FeatureFlags::udfpsViewPerformance); + FeatureFlags::udfpsViewPerformance); } @Override + public boolean unfoldAnimationBackgroundProgress() { return getValue(Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, - FeatureFlags::unfoldAnimationBackgroundProgress); + FeatureFlags::unfoldAnimationBackgroundProgress); } @Override + + public boolean unfoldLatencyTrackingFix() { + return getValue(Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX, + FeatureFlags::unfoldLatencyTrackingFix); + } + + @Override + + public boolean updateCornerRadiusOnDisplayChanged() { + return getValue(Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED, + FeatureFlags::updateCornerRadiusOnDisplayChanged); + } + + @Override + public boolean updateUserSwitcherBackground() { return getValue(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, - FeatureFlags::updateUserSwitcherBackground); + FeatureFlags::updateUserSwitcherBackground); + } + + @Override + + public boolean updateWindowMagnifierBottomBoundary() { + return getValue(Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY, + FeatureFlags::updateWindowMagnifierBottomBoundary); + } + + @Override + + public boolean useAadProxSensor() { + return getValue(Flags.FLAG_USE_AAD_PROX_SENSOR, + FeatureFlags::useAadProxSensor); } @Override - public boolean validateKeyboardShortcutHelperIconUri() { - return getValue(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI, - FeatureFlags::validateKeyboardShortcutHelperIconUri); + + public boolean useNotifInflationThreadForFooter() { + return getValue(Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER, + FeatureFlags::useNotifInflationThreadForFooter); + } + + @Override + + public boolean useNotifInflationThreadForRow() { + return getValue(Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW, + FeatureFlags::useNotifInflationThreadForRow); + } + + @Override + + public boolean useTransitionsForKeyguardOccluded() { + return getValue(Flags.FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED, + FeatureFlags::useTransitionsForKeyguardOccluded); + } + + @Override + + public boolean useVolumeController() { + return getValue(Flags.FLAG_USE_VOLUME_CONTROLLER, + FeatureFlags::useVolumeController); } @Override + + public boolean userAwareSettingsRepositories() { + return getValue(Flags.FLAG_USER_AWARE_SETTINGS_REPOSITORIES, + FeatureFlags::userAwareSettingsRepositories); + } + + @Override + + public boolean userEncryptedSource() { + return getValue(Flags.FLAG_USER_ENCRYPTED_SOURCE, + FeatureFlags::userEncryptedSource); + } + + @Override + + public boolean userSwitcherAddSignOutOption() { + return getValue(Flags.FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION, + FeatureFlags::userSwitcherAddSignOutOption); + } + + @Override + public boolean visualInterruptionsRefactor() { return getValue(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, - FeatureFlags::visualInterruptionsRefactor); + FeatureFlags::visualInterruptionsRefactor); + } + + @Override + + public boolean volumeRedesign() { + return getValue(Flags.FLAG_VOLUME_REDESIGN, + FeatureFlags::volumeRedesign); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } + private boolean isOptimizationEnabled() { return false; } @@ -946,163 +1994,572 @@ protected boolean getValue(String flagName, Predicate getter) { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, - Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, - Flags.FLAG_APP_CLIPS_BACKLINKS, - Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, - Flags.FLAG_BP_TALKBACK, - Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, - Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, - Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, - Flags.FLAG_CLOCK_REACTIVE_VARIANTS, - Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, - Flags.FLAG_COMMUNAL_HUB, - Flags.FLAG_COMPOSE_BOUNCER, - Flags.FLAG_COMPOSE_LOCKSCREEN, - Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, - Flags.FLAG_CONSTRAINT_BP, - Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, - Flags.FLAG_COROUTINE_TRACING, - Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, - Flags.FLAG_DEDICATED_NOTIF_INFLATION_THREAD, - Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, - Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD, - Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, - Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, - Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, - Flags.FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION, - Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, - Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, - Flags.FLAG_DUAL_SHADE, - Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, - Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, - Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, - Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, - Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, - Flags.FLAG_ENABLE_LAYOUT_TRACING, - Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, - Flags.FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER, - Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, - Flags.FLAG_EXAMPLE_FLAG, - Flags.FLAG_FAST_UNLOCK_TRANSITION, - Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, - Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, - Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, - Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, - Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, - Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, - Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, - Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, - Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, - Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, - Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE, - Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE, - Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, - Flags.FLAG_HAPTIC_BRIGHTNESS_SLIDER, - Flags.FLAG_HAPTIC_VOLUME_SLIDER, - Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, - Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, - Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, - Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, - Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, - Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - Flags.FLAG_LIGHT_REVEAL_MIGRATION, - Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, - Flags.FLAG_MEDIA_CONTROLS_REFACTOR, - Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, - Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - Flags.FLAG_NEW_AOD_TRANSITION, - Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, - Flags.FLAG_NEW_VOLUME_PANEL, - Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, - Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, - Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, - Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, - Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, - Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION, - Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE, - Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, - Flags.FLAG_NOTIFICATION_PULSING_FIX, - Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, - Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, - Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, - Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, - Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR, - Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR, - Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, - Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, - Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION, - Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, - Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, - Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, - Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER, - Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS, - Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, - Flags.FLAG_PREDICTIVE_BACK_SYSUI, - Flags.FLAG_PRIORITY_PEOPLE_SECTION, - Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX, - Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, - Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, - Flags.FLAG_PSS_TASK_SWITCHER, - Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, - Flags.FLAG_QS_NEW_PIPELINE, - Flags.FLAG_QS_NEW_TILES, - Flags.FLAG_QS_NEW_TILES_FUTURE, - Flags.FLAG_QS_TILE_FOCUS_STATE, - Flags.FLAG_QS_UI_REFACTOR, - Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS, - Flags.FLAG_RECORD_ISSUE_QS_TILE, - Flags.FLAG_REFACTOR_GET_CURRENT_USER, - Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, - Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, - Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, - Flags.FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND, - Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, - Flags.FLAG_REST_TO_UNLOCK, - Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, - Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, - Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, - Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, - Flags.FLAG_SCENE_CONTAINER, - Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX, - Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX, - Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, - Flags.FLAG_SCREENSHOT_SHELF_UI2, - Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX, - Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, - Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, - Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, - Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, - Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, - Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING, - Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, - Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, - Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, - Flags.FLAG_SWITCH_USER_ON_BG, - Flags.FLAG_SYSUI_TEAMFOOD, - Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, - Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, - Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX, - Flags.FLAG_UDFPS_VIEW_PERFORMANCE, - Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, - Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, - Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI, - Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR + Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, + Flags.FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER, + Flags.FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT, + Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, + Flags.FLAG_APP_CLIPS_BACKLINKS, + Flags.FLAG_APP_SHORTCUT_REMOVAL_FIX, + Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL, + Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, + Flags.FLAG_BOUNCER_UI_REVAMP, + Flags.FLAG_BOUNCER_UI_REVAMP_2, + Flags.FLAG_BP_COLORS, + Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, + Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION, + Flags.FLAG_CLASSIC_FLAGS_MULTI_USER, + Flags.FLAG_CLIPBOARD_IMAGE_TIMEOUT, + Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, + Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER, + Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS, + Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE, + Flags.FLAG_CLOCK_FIDGET_ANIMATION, + Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, + Flags.FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX, + Flags.FLAG_COMMUNAL_HUB, + Flags.FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS, + Flags.FLAG_COMMUNAL_RESPONSIVE_GRID, + Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR, + Flags.FLAG_COMMUNAL_STANDALONE_SUPPORT, + Flags.FLAG_COMMUNAL_TIMER_FLICKER_FIX, + Flags.FLAG_COMMUNAL_WIDGET_RESIZING, + Flags.FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX, + Flags.FLAG_COMPOSE_BOUNCER, + Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, + Flags.FLAG_CONT_AUTH_PLUGIN, + Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, + Flags.FLAG_COROUTINE_TRACING, + Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, + Flags.FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL, + Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB, + Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, + Flags.FLAG_DESKTOP_EFFECTS_QS_TILE, + Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, + Flags.FLAG_DISABLE_BLURRED_SHADE_VISIBLE, + Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, + Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, + Flags.FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE, + Flags.FLAG_DOUBLE_TAP_TO_SLEEP, + Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, + Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, + Flags.FLAG_DREAM_OVERLAY_UPDATED_FONT, + Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, + Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, + Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, + Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, + Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, + Flags.FLAG_ENABLE_LAYOUT_TRACING, + Flags.FLAG_ENABLE_UNDERLAY, + Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, + Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, + Flags.FLAG_EXAMPLE_FLAG, + Flags.FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG, + Flags.FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY, + Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN, + Flags.FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY, + Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE, + Flags.FLAG_FACE_SCANNING_ANIMATION_NPE_FIX, + Flags.FLAG_FASTER_UNLOCK_TRANSITION, + Flags.FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS, + Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, + Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, + Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, + Flags.FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT, + Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, + Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, + Flags.FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON, + Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, + Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, + Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF, + Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, + Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, + Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, + Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, + Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND, + Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE, + Flags.FLAG_GLANCEABLE_HUB_V2, + Flags.FLAG_GLANCEABLE_HUB_V2_RESOURCES, + Flags.FLAG_HAPTICS_FOR_COMPOSE_SLIDERS, + Flags.FLAG_HARDWARE_COLOR_STYLES, + Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, + Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, + Flags.FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE, + Flags.FLAG_HOME_CONTROLS_DREAM_HSUM, + Flags.FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS, + Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE, + Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, + Flags.FLAG_ICON_REFRESH_2025, + Flags.FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF, + Flags.FLAG_INDICATION_TEXT_A11Y_FIX, + Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, + Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, + Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER, + Flags.FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION, + Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF, + Flags.FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS, + Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + Flags.FLAG_LOCKSCREEN_FONT, + Flags.FLAG_LOW_LIGHT_CLOCK_DREAM, + Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES, + Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS, + Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3, + Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT, + Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION, + Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX, + Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, + Flags.FLAG_MEDIA_CONTROLS_UI_UPDATE, + Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND, + Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, + Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER, + Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION, + Flags.FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN, + Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT, + Flags.FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX, + Flags.FLAG_MODES_UI_DIALOG_PAGING, + Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER, + Flags.FLAG_MSDL_FEEDBACK, + Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT, + Flags.FLAG_NEW_AOD_TRANSITION, + Flags.FLAG_NEW_VOLUME_PANEL, + Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING, + Flags.FLAG_NOTES_ROLE_QS_TILE, + Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS, + Flags.FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION, + Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT, + Flags.FLAG_NOTIFICATION_APPEAR_NONLINEAR, + Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, + Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, + Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, + Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, + Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_BUNDLE_UI, + Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, + Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, + Flags.FLAG_NOTIFICATION_REENTRANT_DISMISS, + Flags.FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED, + Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, + Flags.FLAG_NOTIFICATION_ROW_TRANSPARENCY, + Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, + Flags.FLAG_NOTIFICATION_SHADE_BLUR, + Flags.FLAG_NOTIFICATION_SHADE_UI_THREAD, + Flags.FLAG_NOTIFICATION_SKIP_SILENT_UPDATES, + Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX, + Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, + Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, + Flags.FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX, + Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, + Flags.FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES, + Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, + Flags.FLAG_NOTIFICATIONS_LAUNCH_RADIUS, + Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, + Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE, + Flags.FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW, + Flags.FLAG_NOTIFICATIONS_REDESIGN_GUTS, + Flags.FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND, + Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, + Flags.FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE, + Flags.FLAG_OUTPUT_SWITCHER_REDESIGN, + Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION, + Flags.FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING, + Flags.FLAG_PERMISSION_HELPER_UI_RICH_ONGOING, + Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT, + Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, + Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, + Flags.FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION, + Flags.FLAG_PRIORITY_PEOPLE_SECTION, + Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, + Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, + Flags.FLAG_PSS_TASK_SWITCHER, + Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, + Flags.FLAG_QS_NEW_TILES, + Flags.FLAG_QS_NEW_TILES_FUTURE, + Flags.FLAG_QS_QUICK_REBIND_ACTIVE_TILES, + Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD, + Flags.FLAG_QS_TILE_DETAILED_VIEW, + Flags.FLAG_QS_TILE_FOCUS_STATE, + Flags.FLAG_QS_UI_REFACTOR, + Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT, + Flags.FLAG_RECORD_ISSUE_QS_TILE, + Flags.FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE, + Flags.FLAG_REFACTOR_GET_CURRENT_USER, + Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, + Flags.FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC, + Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, + Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, + Flags.FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY, + Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, + Flags.FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL, + Flags.FLAG_REST_TO_UNLOCK, + Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, + Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, + Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, + Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, + Flags.FLAG_SCENE_CONTAINER, + Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, + Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, + Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE, + Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE, + Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, + Flags.FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR, + Flags.FLAG_SECONDARY_USER_WIDGET_HOST, + Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD, + Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS, + Flags.FLAG_SHADE_HEADER_FONT_UPDATE, + Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY, + Flags.FLAG_SHADE_WINDOW_GOES_AROUND, + Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, + Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH, + Flags.FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL, + Flags.FLAG_SHOW_CLIPBOARD_INDICATION, + Flags.FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR, + Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS, + Flags.FLAG_SIM_PIN_BOUNCER_RESET, + Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART, + Flags.FLAG_SIM_PIN_USE_SLOT_ID, + Flags.FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION, + Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, + Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, + Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, + Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, + Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX, + Flags.FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX, + Flags.FLAG_SMARTSPACE_VIEWPAGER2, + Flags.FLAG_SOUNDDOSE_CUSTOMIZATION, + Flags.FLAG_SPATIAL_MODEL_APP_PUSHBACK, + Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2, + Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS, + Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP, + Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION, + Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS, + Flags.FLAG_STATUS_BAR_FONT_UPDATES, + Flags.FLAG_STATUS_BAR_MOBILE_ICON_KAIROS, + Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, + Flags.FLAG_STATUS_BAR_NO_HUN_BEHAVIOR, + Flags.FLAG_STATUS_BAR_POPUP_CHIPS, + Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION, + Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, + Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR, + Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET, + Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, + Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT, + Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP, + Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN, + Flags.FLAG_STATUS_BAR_UI_THREAD, + Flags.FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH, + Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP, + Flags.FLAG_SWITCH_USER_ON_BG, + Flags.FLAG_SYSUI_TEAMFOOD, + Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, + Flags.FLAG_TRANSITION_RACE_CONDITION, + Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, + Flags.FLAG_TV_GLOBAL_ACTIONS_FOCUS, + Flags.FLAG_UDFPS_VIEW_PERFORMANCE, + Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, + Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX, + Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED, + Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, + Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY, + Flags.FLAG_USE_AAD_PROX_SENSOR, + Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER, + Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW, + Flags.FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED, + Flags.FLAG_USE_VOLUME_CONTROLLER, + Flags.FLAG_USER_AWARE_SETTINGS_REPOSITORIES, + Flags.FLAG_USER_ENCRYPTED_SOURCE, + Flags.FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION, + Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, + Flags.FLAG_VOLUME_REDESIGN ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - "" - ) + Arrays.asList( + Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, + Flags.FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER, + Flags.FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT, + Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, + Flags.FLAG_APP_CLIPS_BACKLINKS, + Flags.FLAG_APP_SHORTCUT_REMOVAL_FIX, + Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL, + Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, + Flags.FLAG_BOUNCER_UI_REVAMP, + Flags.FLAG_BOUNCER_UI_REVAMP_2, + Flags.FLAG_BP_COLORS, + Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, + Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION, + Flags.FLAG_CLASSIC_FLAGS_MULTI_USER, + Flags.FLAG_CLIPBOARD_IMAGE_TIMEOUT, + Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, + Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER, + Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS, + Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE, + Flags.FLAG_CLOCK_FIDGET_ANIMATION, + Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, + Flags.FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX, + Flags.FLAG_COMMUNAL_HUB, + Flags.FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS, + Flags.FLAG_COMMUNAL_RESPONSIVE_GRID, + Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR, + Flags.FLAG_COMMUNAL_STANDALONE_SUPPORT, + Flags.FLAG_COMMUNAL_TIMER_FLICKER_FIX, + Flags.FLAG_COMMUNAL_WIDGET_RESIZING, + Flags.FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX, + Flags.FLAG_COMPOSE_BOUNCER, + Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, + Flags.FLAG_CONT_AUTH_PLUGIN, + Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, + Flags.FLAG_COROUTINE_TRACING, + Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, + Flags.FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL, + Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB, + Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, + Flags.FLAG_DESKTOP_EFFECTS_QS_TILE, + Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, + Flags.FLAG_DISABLE_BLURRED_SHADE_VISIBLE, + Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, + Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, + Flags.FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE, + Flags.FLAG_DOUBLE_TAP_TO_SLEEP, + Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, + Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, + Flags.FLAG_DREAM_OVERLAY_UPDATED_FONT, + Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, + Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, + Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, + Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, + Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, + Flags.FLAG_ENABLE_LAYOUT_TRACING, + Flags.FLAG_ENABLE_UNDERLAY, + Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, + Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, + Flags.FLAG_EXAMPLE_FLAG, + Flags.FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG, + Flags.FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY, + Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN, + Flags.FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY, + Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE, + Flags.FLAG_FACE_SCANNING_ANIMATION_NPE_FIX, + Flags.FLAG_FASTER_UNLOCK_TRANSITION, + Flags.FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS, + Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, + Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, + Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, + Flags.FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT, + Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, + Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, + Flags.FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON, + Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, + Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, + Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF, + Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, + Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, + Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, + Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, + Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND, + Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE, + Flags.FLAG_GLANCEABLE_HUB_V2, + Flags.FLAG_GLANCEABLE_HUB_V2_RESOURCES, + Flags.FLAG_HAPTICS_FOR_COMPOSE_SLIDERS, + Flags.FLAG_HARDWARE_COLOR_STYLES, + Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, + Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, + Flags.FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE, + Flags.FLAG_HOME_CONTROLS_DREAM_HSUM, + Flags.FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS, + Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE, + Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, + Flags.FLAG_ICON_REFRESH_2025, + Flags.FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF, + Flags.FLAG_INDICATION_TEXT_A11Y_FIX, + Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, + Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, + Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER, + Flags.FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION, + Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF, + Flags.FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS, + Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + Flags.FLAG_LOCKSCREEN_FONT, + Flags.FLAG_LOW_LIGHT_CLOCK_DREAM, + Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES, + Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS, + Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3, + Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT, + Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION, + Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX, + Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, + Flags.FLAG_MEDIA_CONTROLS_UI_UPDATE, + Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND, + Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, + Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER, + Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION, + Flags.FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN, + Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT, + Flags.FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX, + Flags.FLAG_MODES_UI_DIALOG_PAGING, + Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER, + Flags.FLAG_MSDL_FEEDBACK, + Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT, + Flags.FLAG_NEW_AOD_TRANSITION, + Flags.FLAG_NEW_VOLUME_PANEL, + Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING, + Flags.FLAG_NOTES_ROLE_QS_TILE, + Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS, + Flags.FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION, + Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT, + Flags.FLAG_NOTIFICATION_APPEAR_NONLINEAR, + Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, + Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, + Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, + Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, + Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_BUNDLE_UI, + Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, + Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, + Flags.FLAG_NOTIFICATION_REENTRANT_DISMISS, + Flags.FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED, + Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, + Flags.FLAG_NOTIFICATION_ROW_TRANSPARENCY, + Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, + Flags.FLAG_NOTIFICATION_SHADE_BLUR, + Flags.FLAG_NOTIFICATION_SHADE_UI_THREAD, + Flags.FLAG_NOTIFICATION_SKIP_SILENT_UPDATES, + Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX, + Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, + Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, + Flags.FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX, + Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, + Flags.FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES, + Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, + Flags.FLAG_NOTIFICATIONS_LAUNCH_RADIUS, + Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, + Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE, + Flags.FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW, + Flags.FLAG_NOTIFICATIONS_REDESIGN_GUTS, + Flags.FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND, + Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, + Flags.FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE, + Flags.FLAG_OUTPUT_SWITCHER_REDESIGN, + Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION, + Flags.FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING, + Flags.FLAG_PERMISSION_HELPER_UI_RICH_ONGOING, + Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT, + Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, + Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, + Flags.FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION, + Flags.FLAG_PRIORITY_PEOPLE_SECTION, + Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, + Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, + Flags.FLAG_PSS_TASK_SWITCHER, + Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, + Flags.FLAG_QS_NEW_TILES, + Flags.FLAG_QS_NEW_TILES_FUTURE, + Flags.FLAG_QS_QUICK_REBIND_ACTIVE_TILES, + Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD, + Flags.FLAG_QS_TILE_DETAILED_VIEW, + Flags.FLAG_QS_TILE_FOCUS_STATE, + Flags.FLAG_QS_UI_REFACTOR, + Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT, + Flags.FLAG_RECORD_ISSUE_QS_TILE, + Flags.FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE, + Flags.FLAG_REFACTOR_GET_CURRENT_USER, + Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, + Flags.FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC, + Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, + Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, + Flags.FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY, + Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, + Flags.FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL, + Flags.FLAG_REST_TO_UNLOCK, + Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, + Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, + Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, + Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, + Flags.FLAG_SCENE_CONTAINER, + Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, + Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, + Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE, + Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE, + Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, + Flags.FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR, + Flags.FLAG_SECONDARY_USER_WIDGET_HOST, + Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD, + Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS, + Flags.FLAG_SHADE_HEADER_FONT_UPDATE, + Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY, + Flags.FLAG_SHADE_WINDOW_GOES_AROUND, + Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, + Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH, + Flags.FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL, + Flags.FLAG_SHOW_CLIPBOARD_INDICATION, + Flags.FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR, + Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS, + Flags.FLAG_SIM_PIN_BOUNCER_RESET, + Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART, + Flags.FLAG_SIM_PIN_USE_SLOT_ID, + Flags.FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION, + Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, + Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, + Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, + Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, + Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX, + Flags.FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX, + Flags.FLAG_SMARTSPACE_VIEWPAGER2, + Flags.FLAG_SOUNDDOSE_CUSTOMIZATION, + Flags.FLAG_SPATIAL_MODEL_APP_PUSHBACK, + Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2, + Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS, + Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP, + Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION, + Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS, + Flags.FLAG_STATUS_BAR_FONT_UPDATES, + Flags.FLAG_STATUS_BAR_MOBILE_ICON_KAIROS, + Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, + Flags.FLAG_STATUS_BAR_NO_HUN_BEHAVIOR, + Flags.FLAG_STATUS_BAR_POPUP_CHIPS, + Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION, + Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, + Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR, + Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET, + Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, + Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT, + Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP, + Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN, + Flags.FLAG_STATUS_BAR_UI_THREAD, + Flags.FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH, + Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP, + Flags.FLAG_SWITCH_USER_ON_BG, + Flags.FLAG_SYSUI_TEAMFOOD, + Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, + Flags.FLAG_TRANSITION_RACE_CONDITION, + Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, + Flags.FLAG_TV_GLOBAL_ACTIONS_FOCUS, + Flags.FLAG_UDFPS_VIEW_PERFORMANCE, + Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, + Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX, + Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED, + Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, + Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY, + Flags.FLAG_USE_AAD_PROX_SENSOR, + Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER, + Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW, + Flags.FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED, + Flags.FLAG_USE_VOLUME_CONTROLLER, + Flags.FLAG_USER_AWARE_SETTINGS_REPOSITORIES, + Flags.FLAG_USER_ENCRYPTED_SOURCE, + Flags.FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION, + Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, + Flags.FLAG_VOLUME_REDESIGN, + "" + ) ); } diff --git a/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java b/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java index 0054d4f3a50..49523621c08 100644 --- a/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; - /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/systemui/FeatureFlags.java b/flags/src/com/android/systemui/FeatureFlags.java index 6ce51d50c32..681e59c0eb0 100644 --- a/flags/src/com/android/systemui/FeatureFlags.java +++ b/flags/src/com/android/systemui/FeatureFlags.java @@ -3,308 +3,1124 @@ /** @hide */ public interface FeatureFlags { - + + + boolean activityTransitionUseLargestWindow(); - + + + + boolean addBlackBackgroundForWindowMagnifier(); + + + + boolean alwaysComposeQsUiFragment(); + + + boolean ambientTouchMonitorListenToDisplayChanges(); - + + + boolean appClipsBacklinks(); - + + + + boolean appShortcutRemovalFix(); + + + + boolean avalancheReplaceHunWhenCritical(); + + + boolean bindKeyguardMediaVisibility(); - - boolean bpTalkback(); - + + + + boolean bouncerUiRevamp(); + + + + boolean bouncerUiRevamp2(); + + + + boolean bpColors(); + + + boolean brightnessSliderFocusState(); - - boolean centralizedStatusBarHeightFix(); - + + + + boolean checkLockscreenGoneTransition(); + + + + boolean classicFlagsMultiUser(); + + + + boolean clipboardImageTimeout(); + + + boolean clipboardNoninteractiveOnLockscreen(); - - boolean clockReactiveVariants(); - + + + + boolean clipboardOverlayMultiuser(); + + + + boolean clipboardSharedTransitions(); + + + + boolean clipboardUseDescriptionMimetype(); + + + + boolean clockFidgetAnimation(); + + + boolean communalBouncerDoNotModifyPluginOpen(); - + + + + boolean communalEditWidgetsActivityFinishFix(); + + + boolean communalHub(); - + + + + boolean communalHubUseThreadPoolForWidgets(); + + + + boolean communalResponsiveGrid(); + + + + boolean communalSceneKtfRefactor(); + + + + boolean communalStandaloneSupport(); + + + + boolean communalTimerFlickerFix(); + + + + boolean communalWidgetResizing(); + + + + boolean communalWidgetTrampolineFix(); + + + boolean composeBouncer(); - - boolean composeLockscreen(); - + + + boolean confineNotificationTouchToViewWidth(); - - boolean constraintBp(); - + + + + boolean contAuthPlugin(); + + + boolean contextualTipsAssistantDismissFix(); - + + + boolean coroutineTracing(); - + + + boolean createWindowlessWindowMagnifier(); - - boolean dedicatedNotifInflationThread(); - + + + + boolean debugLiveUpdatesPromoteAll(); + + + + boolean decoupleViewControllerInAnimlib(); + + + boolean delayShowMagnificationButton(); - - boolean delayedWakelockReleaseOnBackgroundThread(); - + + + + boolean desktopEffectsQsTile(); + + + boolean deviceEntryUdfpsRefactor(); - + + + + boolean disableBlurredShadeVisible(); + + + boolean disableContextualTipsFrequencyCheck(); - + + + boolean disableContextualTipsIosSwitcherCheck(); - - boolean dozeuiSchedulingAlarmsBackgroundExecution(); - + + + + boolean disableShadeTrackpadTwoFingerSwipe(); + + + + boolean doubleTapToSleep(); + + + boolean dreamInputSessionPilferOnce(); - + + + boolean dreamOverlayBouncerSwipeDirectionFiltering(); - - boolean dualShade(); - + + + + boolean dreamOverlayUpdatedFont(); + + + boolean edgeBackGestureHandlerThread(); - + + + boolean edgebackGestureHandlerGetRunningTasksBackground(); - + + + boolean enableBackgroundKeyguardOndrawnCallback(); - + + + boolean enableContextualTipForMuteVolume(); - + + + boolean enableContextualTipForPowerOff(); - + + + boolean enableContextualTipForTakeScreenshot(); - + + + boolean enableContextualTips(); - + + + boolean enableEfficientDisplayRepository(); - + + + boolean enableLayoutTracing(); - + + + + boolean enableUnderlay(); + + + boolean enableViewCaptureTracing(); - - boolean enableWidgetPickerSizeFilter(); - + + + boolean enforceBrightnessBaseUserRestriction(); - + + + boolean exampleFlag(); - - boolean fastUnlockTransition(); - + + + + boolean expandCollapsePrivacyDialog(); + + + + boolean expandHeadsUpOnInlineReply(); + + + + boolean expandedPrivacyIndicatorsOnLargeScreen(); + + + + boolean extendedAppsShortcutCategory(); + + + + boolean faceMessageDeferUpdate(); + + + + boolean faceScanningAnimationNpeFix(); + + + + boolean fasterUnlockTransition(); + + + + boolean fetchBookmarksXmlKeyboardShortcuts(); + + + boolean fixImageWallpaperCrashSurfaceAlreadyReleased(); - + + + boolean fixScreenshotActionDismissSystemWindows(); - + + + boolean floatingMenuAnimatedTuck(); - + + + + boolean floatingMenuDisplayCutoutSupport(); + + + boolean floatingMenuDragToEdit(); - + + + boolean floatingMenuDragToHide(); - + + + + boolean floatingMenuHearingDeviceStatusIcon(); + + + boolean floatingMenuImeDisplacementAnimation(); - + + + boolean floatingMenuNarrowTargetContentObserver(); - + + + + boolean floatingMenuNotifyTargetsChangedOnStrictDiff(); + + + boolean floatingMenuOverlapsNavBarsFlag(); - + + + boolean floatingMenuRadiiAnimation(); - - boolean generatedPreviews(); - + + + boolean getConnectedDeviceNameUnsynchronized(); - + + + boolean glanceableHubAllowKeyguardWhenDreaming(); - - boolean glanceableHubFullscreenSwipe(); - - boolean glanceableHubGestureHandle(); - - boolean glanceableHubShortcutButton(); - - boolean hapticBrightnessSlider(); - - boolean hapticVolumeSlider(); - - boolean hearingAidsQsTileDialog(); - - boolean hearingDevicesDialogRelatedTools(); - - boolean keyboardDockingIndicator(); - - boolean keyboardShortcutHelperRewrite(); - - boolean keyguardBottomAreaRefactor(); - + + + + boolean glanceableHubBlurredBackground(); + + + + boolean glanceableHubDirectEditMode(); + + + + boolean glanceableHubV2(); + + + + boolean glanceableHubV2Resources(); + + + + boolean hapticsForComposeSliders(); + + + + boolean hardwareColorStyles(); + + + + boolean hearingAidsQsTileDialog(); + + + + boolean hearingDevicesDialogRelatedTools(); + + + + boolean hideRingerButtonInSingleVolumeMode(); + + + + boolean homeControlsDreamHsum(); + + + + boolean hubEditModeTouchAdjustments(); + + + + boolean hubmodeFullscreenVerticalSwipe(); + + + + boolean hubmodeFullscreenVerticalSwipeFix(); + + + + boolean iconRefresh2025(); + + + + boolean ignoreTouchesNextToNotificationShelf(); + + + + boolean indicationTextA11yFix(); + + + + boolean keyboardDockingIndicator(); + + + + boolean keyboardShortcutHelperRewrite(); + + + + boolean keyboardShortcutHelperShortcutCustomizer(); + + + + boolean keyboardTouchpadContextualEducation(); + + + + boolean keyguardTransitionForceFinishOnScreenOff(); + + + + boolean keyguardWmReorderAtmsCalls(); + + + boolean keyguardWmStateRefactor(); - - boolean lightRevealMigration(); - + + + + boolean lockscreenFont(); + + + + boolean lowLightClockDream(); + + + + boolean magneticNotificationSwipes(); + + + + boolean mediaControlsA11yColors(); + + + + boolean mediaControlsButtonMedia3(); + + + + boolean mediaControlsButtonMedia3Placement(); + + + + boolean mediaControlsDeviceManagerBackgroundExecution(); + + + + boolean mediaControlsDrawablesReuseBugfix(); + + + boolean mediaControlsLockscreenShadeBugFix(); - - boolean mediaControlsRefactor(); - + + + + boolean mediaControlsUiUpdate(); + + + + boolean mediaControlsUmoInflationInBackground(); + + + boolean mediaControlsUserInitiatedDeleteintent(); - - boolean migrateClocksToBlueprint(); - + + + + boolean mediaLoadMetadataViaMediaDataLoader(); + + + + boolean mediaLockscreenLaunchAnimation(); + + + + boolean mediaProjectionDialogBehindLockscreen(); + + + + boolean mediaProjectionGreyErrorText(); + + + + boolean mediaProjectionRequestAttributionFix(); + + + + boolean modesUiDialogPaging(); + + + + boolean moveTransitionAnimationLayer(); + + + + boolean msdlFeedback(); + + + + boolean multiuserWifiPickerTrackerSupport(); + + + boolean newAodTransition(); - - boolean newTouchpadGesturesTutorial(); - + + + boolean newVolumePanel(); - + + + + boolean nonTouchscreenDevicesBypassFalsing(); + + + + boolean notesRoleQsTile(); + + + + boolean notificationAddXOnHoverToDismiss(); + + + + boolean notificationAmbientSuppressionAfterInflation(); + + + + boolean notificationAnimatedActionsTreatment(); + + + + boolean notificationAppearNonlinear(); + + + boolean notificationAsyncGroupHeaderInflation(); - + + + boolean notificationAsyncHybridViewInflation(); - + + + boolean notificationAvalancheSuppression(); - + + + boolean notificationAvalancheThrottleHun(); - + + + boolean notificationBackgroundTintOptimization(); - + + + + boolean notificationBundleUi(); + + + boolean notificationColorUpdateLogger(); - + + + boolean notificationContentAlphaOptimization(); - + + + boolean notificationFooterBackgroundTintOptimization(); - - boolean notificationMediaManagerBackgroundExecution(); - - boolean notificationMinimalismPrototype(); - + + + boolean notificationOverExpansionClippingFix(); - - boolean notificationPulsingFix(); - + + + + boolean notificationReentrantDismiss(); + + + + boolean notificationRowAccessibilityExpanded(); + + + boolean notificationRowContentBinderRefactor(); - + + + + boolean notificationRowTransparency(); + + + boolean notificationRowUserContext(); - + + + + boolean notificationShadeBlur(); + + + + boolean notificationShadeUiThread(); + + + + boolean notificationSkipSilentUpdates(); + + + + boolean notificationTransparentHeaderFix(); + + + boolean notificationViewFlipperPausingV2(); - + + + boolean notificationsBackgroundIcons(); - - boolean notificationsFooterViewRefactor(); - - boolean notificationsHeadsUpRefactor(); - + + + + boolean notificationsFooterVisibilityFix(); + + + boolean notificationsHideOnDisplaySwitch(); - + + + + boolean notificationsHunSharedAnimationValues(); + + + boolean notificationsIconContainerRefactor(); - - boolean notificationsImprovedHunAnimation(); - + + + + boolean notificationsLaunchRadius(); + + + boolean notificationsLiveDataStoreRefactor(); - + + + + boolean notificationsPinnedHunInShade(); + + + + boolean notificationsRedesignFooterView(); + + + + boolean notificationsRedesignGuts(); + + + + boolean notifyPasswordTextViewUserActivityInBackground(); + + + boolean notifyPowerManagerUserActivityBackground(); - + + + + boolean onlyShowMediaStreamSliderInSingleVolumeMode(); + + + + boolean outputSwitcherRedesign(); + + + + boolean overrideSuppressOverlayCondition(); + + + + boolean permissionHelperInlineUiRichOngoing(); + + + + boolean permissionHelperUiRichOngoing(); + + + + boolean physicalNotificationMovement(); + + + boolean pinInputFieldStyledFocusState(); - - boolean predictiveBackAnimateBouncer(); - - boolean predictiveBackAnimateDialogs(); - + + + boolean predictiveBackAnimateShade(); - - boolean predictiveBackSysui(); - + + + + boolean predictiveBackDelayWmTransition(); + + + boolean priorityPeopleSection(); - - boolean privacyDotUnfoldWrongCornerFix(); - - boolean pssAppSelectorAbruptExitFix(); - + + + + boolean promoteNotificationsAutomatically(); + + + boolean pssAppSelectorRecentsSplitScreen(); - + + + boolean pssTaskSwitcher(); - + + + boolean qsCustomTileClickGuaranteedBugFix(); - - boolean qsNewPipeline(); - + + + boolean qsNewTiles(); - + + + boolean qsNewTilesFuture(); - + + + + boolean qsQuickRebindActiveTiles(); + + + + boolean qsRegisterSettingObserverOnBgThread(); + + + + boolean qsTileDetailedView(); + + + boolean qsTileFocusState(); - + + + boolean qsUiRefactor(); - - boolean quickSettingsVisualHapticsLongpress(); - + + + + boolean qsUiRefactorComposeFragment(); + + + boolean recordIssueQsTile(); - + + + + boolean redesignMagnificationWindowSize(); + + + boolean refactorGetCurrentUser(); - + + + boolean registerBatteryControllerReceiversInCorestartable(); - + + + + boolean registerContentObserversAsync(); + + + boolean registerNewWalletCardInBackground(); - + + + boolean registerWallpaperNotifierBackground(); - - boolean registerZenModeContentObserverBackground(); - + + + + boolean relockWithPowerButtonImmediately(); + + + boolean removeDreamOverlayHideOnTouch(); - + + + + boolean removeUpdateListenerInQsIconViewImpl(); + + + boolean restToUnlock(); - + + + boolean restartDreamOnUnocclude(); - + + + boolean revampedBouncerMessages(); - + + + boolean runFingerprintDetectOnDismissibleKeyguard(); - + + + boolean saveAndRestoreMagnificationSettingsButtons(); - + + + boolean sceneContainer(); - + + + boolean screenshareNotificationHidingBugFix(); - + + + boolean screenshotActionDismissSystemWindows(); - - boolean screenshotPrivateProfileAccessibilityAnnouncementFix(); - - boolean screenshotPrivateProfileBehaviorFix(); - + + + + boolean screenshotMultidisplayFocusChange(); + + + + boolean screenshotPolicySplitAndDesktopMode(); + + + boolean screenshotScrollCropViewCrashFix(); - - boolean screenshotShelfUi2(); - - boolean shadeCollapseActivityLaunchFix(); - + + + + boolean screenshotUiControllerRefactor(); + + + + boolean secondaryUserWidgetHost(); + + + + boolean settingsExtRegisterContentObserverOnBgThread(); + + + + boolean shadeExpandsOnStatusBarLongPress(); + + + + boolean shadeHeaderFontUpdate(); + + + + boolean shadeLaunchAccessibility(); + + + + boolean shadeWindowGoesAround(); + + + boolean shaderlibLoadingEffectRefactor(); - + + + + boolean shortcutHelperKeyGlyph(); + + + + boolean showAudioSharingSliderInVolumePanel(); + + + + boolean showClipboardIndication(); + + + + boolean showLockedByYourWatchKeyguardIndicator(); + + + + boolean showToastWhenAppControlBrightness(); + + + + boolean simPinBouncerReset(); + + + + boolean simPinRaceConditionOnRestart(); + + + + boolean simPinUseSlotId(); + + + + boolean skipHideSensitiveNotifAnimation(); + + + boolean sliceBroadcastRelayInBackground(); - + + + boolean sliceManagerBinderCallBackground(); - + + + boolean smartspaceLockscreenViewmodel(); - + + + boolean smartspaceRelocateToBottom(); - - boolean smartspaceRemoteviewsRendering(); - + + + + boolean smartspaceRemoteviewsRenderingFix(); + + + + boolean smartspaceSwipeEventLoggingFix(); + + + + boolean smartspaceViewpager2(); + + + + boolean sounddoseCustomization(); + + + + boolean spatialModelAppPushback(); + + + + boolean stabilizeHeadsUpGroupV2(); + + + + boolean statusBarAlwaysCheckUnderlyingNetworks(); + + + + boolean statusBarAutoStartScreenRecordChip(); + + + + boolean statusBarChipsModernization(); + + + + boolean statusBarChipsReturnAnimations(); + + + + boolean statusBarFontUpdates(); + + + + boolean statusBarMobileIconKairos(); + + + boolean statusBarMonochromeIconsFix(); - - boolean statusBarScreenSharingChips(); - + + + + boolean statusBarNoHunBehavior(); + + + + boolean statusBarPopupChips(); + + + + boolean statusBarRootModernization(); + + + + boolean statusBarShowAudioOnlyProjectionChip(); + + + + boolean statusBarSignalPolicyRefactor(); + + + + boolean statusBarSignalPolicyRefactorEthernet(); + + + boolean statusBarStaticInoutIndicators(); - + + + + boolean statusBarStopUpdatingWindowHeight(); + + + + boolean statusBarSwipeOverChip(); + + + + boolean statusBarSwitchToSpnFromDataSpn(); + + + + boolean statusBarUiThread(); + + + + boolean statusBarWindowNoCustomTouch(); + + + + boolean stoppableFgsSystemApp(); + + + boolean switchUserOnBg(); - + + + boolean sysuiTeamfood(); - + + + boolean themeOverlayControllerWakefulnessDeprecation(); - + + + + boolean transitionRaceCondition(); + + + boolean translucentOccludingActivityFix(); - - boolean truncatedStatusBarIconsFix(); - + + + + boolean tvGlobalActionsFocus(); + + + boolean udfpsViewPerformance(); - + + + boolean unfoldAnimationBackgroundProgress(); - + + + + boolean unfoldLatencyTrackingFix(); + + + + boolean updateCornerRadiusOnDisplayChanged(); + + + boolean updateUserSwitcherBackground(); - - boolean validateKeyboardShortcutHelperIconUri(); - + + + + boolean updateWindowMagnifierBottomBoundary(); + + + + boolean useAadProxSensor(); + + + + boolean useNotifInflationThreadForFooter(); + + + + boolean useNotifInflationThreadForRow(); + + + + boolean useTransitionsForKeyguardOccluded(); + + + + boolean useVolumeController(); + + + + boolean userAwareSettingsRepositories(); + + + + boolean userEncryptedSource(); + + + + boolean userSwitcherAddSignOutOption(); + + + boolean visualInterruptionsRefactor(); + + + + boolean volumeRedesign(); } diff --git a/flags/src/com/android/systemui/FeatureFlagsImpl.java b/flags/src/com/android/systemui/FeatureFlagsImpl.java index 32049600d55..7dbc6b650eb 100644 --- a/flags/src/com/android/systemui/FeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/FeatureFlagsImpl.java @@ -1,3178 +1,1965 @@ package com.android.systemui; // TODO(b/303773055): Remove the annotation after access issue is resolved. - -import com.android.quickstep.util.DeviceConfigHelper; - -import java.nio.file.Files; -import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); - private static volatile boolean isCached = false; - private static volatile boolean accessibility_is_cached = false; - private static volatile boolean biometrics_framework_is_cached = false; - private static volatile boolean communal_is_cached = false; - private static volatile boolean systemui_is_cached = false; - private static boolean activityTransitionUseLargestWindow = true; - private static boolean ambientTouchMonitorListenToDisplayChanges = false; - private static boolean appClipsBacklinks = false; - private static boolean bindKeyguardMediaVisibility = true; - private static boolean bpTalkback = true; - private static boolean brightnessSliderFocusState = false; - private static boolean centralizedStatusBarHeightFix = true; - private static boolean clipboardNoninteractiveOnLockscreen = false; - private static boolean clockReactiveVariants = false; - private static boolean communalBouncerDoNotModifyPluginOpen = false; - private static boolean communalHub = true; - private static boolean composeBouncer = false; - private static boolean composeLockscreen = false; - private static boolean confineNotificationTouchToViewWidth = true; - private static boolean constraintBp = true; - private static boolean contextualTipsAssistantDismissFix = true; - private static boolean coroutineTracing = true; - private static boolean createWindowlessWindowMagnifier = true; - private static boolean dedicatedNotifInflationThread = true; - private static boolean delayShowMagnificationButton = true; - private static boolean delayedWakelockReleaseOnBackgroundThread = true; - private static boolean deviceEntryUdfpsRefactor = true; - private static boolean disableContextualTipsFrequencyCheck = true; - private static boolean disableContextualTipsIosSwitcherCheck = true; - private static boolean dozeuiSchedulingAlarmsBackgroundExecution = false; - private static boolean dreamInputSessionPilferOnce = false; - private static boolean dreamOverlayBouncerSwipeDirectionFiltering = true; - private static boolean dualShade = false; - private static boolean edgeBackGestureHandlerThread = false; - private static boolean edgebackGestureHandlerGetRunningTasksBackground = true; - private static boolean enableBackgroundKeyguardOndrawnCallback = true; - private static boolean enableContextualTipForMuteVolume = false; - private static boolean enableContextualTipForPowerOff = true; - private static boolean enableContextualTipForTakeScreenshot = true; - private static boolean enableContextualTips = true; - private static boolean enableEfficientDisplayRepository = false; - private static boolean enableLayoutTracing = false; - private static boolean enableViewCaptureTracing = false; - private static boolean enableWidgetPickerSizeFilter = false; - private static boolean enforceBrightnessBaseUserRestriction = true; - private static boolean exampleFlag = false; - private static boolean fastUnlockTransition = false; - private static boolean fixImageWallpaperCrashSurfaceAlreadyReleased = true; - private static boolean fixScreenshotActionDismissSystemWindows = true; - private static boolean floatingMenuAnimatedTuck = true; - private static boolean floatingMenuDragToEdit = true; - private static boolean floatingMenuDragToHide = false; - private static boolean floatingMenuImeDisplacementAnimation = true; - private static boolean floatingMenuNarrowTargetContentObserver = true; - private static boolean floatingMenuOverlapsNavBarsFlag = true; - private static boolean floatingMenuRadiiAnimation = true; - private static boolean generatedPreviews = true; - private static boolean getConnectedDeviceNameUnsynchronized = true; - private static boolean glanceableHubAllowKeyguardWhenDreaming = false; - private static boolean glanceableHubFullscreenSwipe = false; - private static boolean glanceableHubGestureHandle = false; - private static boolean glanceableHubShortcutButton = false; - private static boolean hapticBrightnessSlider = true; - private static boolean hapticVolumeSlider = true; - private static boolean hearingAidsQsTileDialog = true; - private static boolean hearingDevicesDialogRelatedTools = true; - private static boolean keyboardDockingIndicator = true; - private static boolean keyboardShortcutHelperRewrite = false; - private static boolean keyguardBottomAreaRefactor = true; - private static boolean keyguardWmStateRefactor = false; - private static boolean lightRevealMigration = true; - private static boolean mediaControlsLockscreenShadeBugFix = true; - private static boolean mediaControlsRefactor = true; - private static boolean mediaControlsUserInitiatedDeleteintent = true; - private static boolean migrateClocksToBlueprint = true; - private static boolean newAodTransition = true; - private static boolean newTouchpadGesturesTutorial = false; - private static boolean newVolumePanel = true; - private static boolean notificationAsyncGroupHeaderInflation = true; - private static boolean notificationAsyncHybridViewInflation = true; - private static boolean notificationAvalancheSuppression = true; - private static boolean notificationAvalancheThrottleHun = true; - private static boolean notificationBackgroundTintOptimization = true; - private static boolean notificationColorUpdateLogger = false; - private static boolean notificationContentAlphaOptimization = true; - private static boolean notificationFooterBackgroundTintOptimization = false; - private static boolean notificationMediaManagerBackgroundExecution = true; - private static boolean notificationMinimalismPrototype = false; - private static boolean notificationOverExpansionClippingFix = true; - private static boolean notificationPulsingFix = true; - private static boolean notificationRowContentBinderRefactor = false; - private static boolean notificationRowUserContext = true; - private static boolean notificationViewFlipperPausingV2 = true; - private static boolean notificationsBackgroundIcons = false; - private static boolean notificationsFooterViewRefactor = true; - private static boolean notificationsHeadsUpRefactor = true; - private static boolean notificationsHideOnDisplaySwitch = false; - private static boolean notificationsIconContainerRefactor = true; - private static boolean notificationsImprovedHunAnimation = true; - private static boolean notificationsLiveDataStoreRefactor = true; - private static boolean notifyPowerManagerUserActivityBackground = true; - private static boolean pinInputFieldStyledFocusState = true; - private static boolean predictiveBackAnimateBouncer = true; - private static boolean predictiveBackAnimateDialogs = true; - private static boolean predictiveBackAnimateShade = false; - private static boolean predictiveBackSysui = true; - private static boolean priorityPeopleSection = true; - private static boolean privacyDotUnfoldWrongCornerFix = true; - private static boolean pssAppSelectorAbruptExitFix = true; - private static boolean pssAppSelectorRecentsSplitScreen = true; - private static boolean pssTaskSwitcher = false; - private static boolean qsCustomTileClickGuaranteedBugFix = true; - private static boolean qsNewPipeline = true; - private static boolean qsNewTiles = false; - private static boolean qsNewTilesFuture = false; - private static boolean qsTileFocusState = true; - private static boolean qsUiRefactor = false; - private static boolean quickSettingsVisualHapticsLongpress = true; - private static boolean recordIssueQsTile = true; - private static boolean refactorGetCurrentUser = true; - private static boolean registerBatteryControllerReceiversInCorestartable = false; - private static boolean registerNewWalletCardInBackground = true; - private static boolean registerWallpaperNotifierBackground = true; - private static boolean registerZenModeContentObserverBackground = true; - private static boolean removeDreamOverlayHideOnTouch = true; - private static boolean restToUnlock = false; - private static boolean restartDreamOnUnocclude = false; - private static boolean revampedBouncerMessages = true; - private static boolean runFingerprintDetectOnDismissibleKeyguard = true; - private static boolean saveAndRestoreMagnificationSettingsButtons = false; - private static boolean sceneContainer = false; - private static boolean screenshareNotificationHidingBugFix = true; - private static boolean screenshotActionDismissSystemWindows = true; - private static boolean screenshotPrivateProfileAccessibilityAnnouncementFix = true; - private static boolean screenshotPrivateProfileBehaviorFix = true; - private static boolean screenshotScrollCropViewCrashFix = true; - private static boolean screenshotShelfUi2 = true; - private static boolean shadeCollapseActivityLaunchFix = false; - private static boolean shaderlibLoadingEffectRefactor = true; - private static boolean sliceBroadcastRelayInBackground = true; - private static boolean sliceManagerBinderCallBackground = true; - private static boolean smartspaceLockscreenViewmodel = true; - private static boolean smartspaceRelocateToBottom = false; - private static boolean smartspaceRemoteviewsRendering = false; - private static boolean statusBarMonochromeIconsFix = true; - private static boolean statusBarScreenSharingChips = true; - private static boolean statusBarStaticInoutIndicators = false; - private static boolean switchUserOnBg = true; - private static boolean sysuiTeamfood = true; - private static boolean themeOverlayControllerWakefulnessDeprecation = false; - private static boolean translucentOccludingActivityFix = false; - private static boolean truncatedStatusBarIconsFix = true; - private static boolean udfpsViewPerformance = true; - private static boolean unfoldAnimationBackgroundProgress = true; - private static boolean updateUserSwitcherBackground = true; - private static boolean validateKeyboardShortcutHelperIconUri = true; - private static boolean visualInterruptionsRefactor = true; - - - private void init() { - boolean foundPackage = true; - - createWindowlessWindowMagnifier = foundPackage; - - - delayShowMagnificationButton = foundPackage; - - - floatingMenuAnimatedTuck = foundPackage; - - - floatingMenuDragToEdit = foundPackage; - - - floatingMenuDragToHide = foundPackage; - - - floatingMenuImeDisplacementAnimation = foundPackage; - - - floatingMenuNarrowTargetContentObserver = foundPackage; - - - floatingMenuOverlapsNavBarsFlag = foundPackage; - - - floatingMenuRadiiAnimation = foundPackage; - - - hearingDevicesDialogRelatedTools = foundPackage; - - saveAndRestoreMagnificationSettingsButtons = foundPackage; - bpTalkback = foundPackage; - constraintBp = foundPackage; - communalHub = foundPackage; - enableWidgetPickerSizeFilter = foundPackage; - activityTransitionUseLargestWindow = foundPackage; - ambientTouchMonitorListenToDisplayChanges = foundPackage; - appClipsBacklinks = foundPackage; - bindKeyguardMediaVisibility = foundPackage; - brightnessSliderFocusState = foundPackage; - centralizedStatusBarHeightFix = foundPackage; - clipboardNoninteractiveOnLockscreen = foundPackage; - clockReactiveVariants = foundPackage; - communalBouncerDoNotModifyPluginOpen = foundPackage; - composeBouncer = foundPackage; - composeLockscreen = foundPackage; - confineNotificationTouchToViewWidth = foundPackage; - contextualTipsAssistantDismissFix = foundPackage; - coroutineTracing = foundPackage; - dedicatedNotifInflationThread = foundPackage; - delayedWakelockReleaseOnBackgroundThread = foundPackage; - deviceEntryUdfpsRefactor = foundPackage; - disableContextualTipsFrequencyCheck = foundPackage; - disableContextualTipsIosSwitcherCheck = foundPackage; - dozeuiSchedulingAlarmsBackgroundExecution = foundPackage; - dreamInputSessionPilferOnce = foundPackage; - dreamOverlayBouncerSwipeDirectionFiltering = foundPackage; - dualShade = foundPackage; - edgeBackGestureHandlerThread = foundPackage; - edgebackGestureHandlerGetRunningTasksBackground = foundPackage; - enableBackgroundKeyguardOndrawnCallback = foundPackage; - enableContextualTipForMuteVolume = foundPackage; - enableContextualTipForPowerOff = foundPackage; - enableContextualTipForTakeScreenshot = foundPackage; - enableContextualTips = foundPackage; - enableEfficientDisplayRepository = foundPackage; - enableLayoutTracing = foundPackage; - enableViewCaptureTracing = foundPackage; - enforceBrightnessBaseUserRestriction = foundPackage; - exampleFlag = foundPackage; - fastUnlockTransition = foundPackage; - fixImageWallpaperCrashSurfaceAlreadyReleased = foundPackage; - fixScreenshotActionDismissSystemWindows = foundPackage; - generatedPreviews = foundPackage; - getConnectedDeviceNameUnsynchronized = foundPackage; - glanceableHubAllowKeyguardWhenDreaming = foundPackage; - glanceableHubFullscreenSwipe = foundPackage; - glanceableHubGestureHandle = foundPackage; - glanceableHubShortcutButton = foundPackage; - hapticBrightnessSlider = foundPackage; - hapticVolumeSlider = foundPackage; - hearingAidsQsTileDialog = foundPackage; - keyboardDockingIndicator = foundPackage; - keyboardShortcutHelperRewrite = foundPackage; - keyguardBottomAreaRefactor = foundPackage; - keyguardWmStateRefactor = foundPackage; - lightRevealMigration = foundPackage; - mediaControlsLockscreenShadeBugFix = foundPackage; - mediaControlsRefactor = foundPackage; - mediaControlsUserInitiatedDeleteintent = foundPackage; - migrateClocksToBlueprint = foundPackage; - newAodTransition = foundPackage; - newTouchpadGesturesTutorial = foundPackage; - newVolumePanel = foundPackage; - notificationAsyncGroupHeaderInflation = foundPackage; - notificationAsyncHybridViewInflation = foundPackage; - notificationAvalancheSuppression = foundPackage; - notificationAvalancheThrottleHun = foundPackage; - notificationBackgroundTintOptimization = foundPackage; - notificationColorUpdateLogger = foundPackage; - notificationContentAlphaOptimization = foundPackage; - notificationFooterBackgroundTintOptimization = foundPackage; - notificationMediaManagerBackgroundExecution = foundPackage; - notificationMinimalismPrototype = foundPackage; - notificationOverExpansionClippingFix = foundPackage; - notificationPulsingFix = foundPackage; - notificationRowContentBinderRefactor = foundPackage; - notificationRowUserContext = foundPackage; - notificationViewFlipperPausingV2 = foundPackage; - notificationsBackgroundIcons = foundPackage; - notificationsFooterViewRefactor = foundPackage; - notificationsHeadsUpRefactor = foundPackage; - notificationsHideOnDisplaySwitch = foundPackage; - notificationsIconContainerRefactor = foundPackage; - notificationsImprovedHunAnimation = foundPackage; - notificationsLiveDataStoreRefactor = foundPackage; - notifyPowerManagerUserActivityBackground = foundPackage; - pinInputFieldStyledFocusState = foundPackage; - predictiveBackAnimateBouncer = foundPackage; - predictiveBackAnimateDialogs = foundPackage; - predictiveBackAnimateShade = foundPackage; - predictiveBackSysui = foundPackage; - priorityPeopleSection = foundPackage; - privacyDotUnfoldWrongCornerFix = foundPackage; - pssAppSelectorAbruptExitFix = foundPackage; - pssAppSelectorRecentsSplitScreen = foundPackage; - pssTaskSwitcher = foundPackage; - qsCustomTileClickGuaranteedBugFix = foundPackage; - qsNewPipeline = foundPackage; - qsNewTiles = foundPackage; - qsNewTilesFuture = foundPackage; - qsTileFocusState = foundPackage; - qsUiRefactor = foundPackage; - quickSettingsVisualHapticsLongpress = foundPackage; - recordIssueQsTile = foundPackage; - refactorGetCurrentUser = foundPackage; - registerBatteryControllerReceiversInCorestartable = foundPackage; - registerNewWalletCardInBackground = foundPackage; - registerWallpaperNotifierBackground = foundPackage; - registerZenModeContentObserverBackground = foundPackage; - removeDreamOverlayHideOnTouch = foundPackage; - restToUnlock = foundPackage; - restartDreamOnUnocclude = foundPackage; - revampedBouncerMessages = foundPackage; - runFingerprintDetectOnDismissibleKeyguard = foundPackage; - sceneContainer = foundPackage; - screenshareNotificationHidingBugFix = foundPackage; - screenshotActionDismissSystemWindows = foundPackage; - screenshotPrivateProfileAccessibilityAnnouncementFix = foundPackage; - screenshotPrivateProfileBehaviorFix = foundPackage; - screenshotScrollCropViewCrashFix = foundPackage; - screenshotShelfUi2 = foundPackage; - shadeCollapseActivityLaunchFix = foundPackage; - shaderlibLoadingEffectRefactor = foundPackage; - sliceBroadcastRelayInBackground = foundPackage; - sliceManagerBinderCallBackground = foundPackage; - smartspaceLockscreenViewmodel = foundPackage; - smartspaceRelocateToBottom = foundPackage; - smartspaceRemoteviewsRendering = foundPackage; - - - statusBarMonochromeIconsFix = foundPackage; - - - statusBarScreenSharingChips = foundPackage; - - - statusBarStaticInoutIndicators = foundPackage; - - - switchUserOnBg = foundPackage; - - - sysuiTeamfood = foundPackage; - - - themeOverlayControllerWakefulnessDeprecation = foundPackage; - - - translucentOccludingActivityFix = foundPackage; - - - truncatedStatusBarIconsFix = foundPackage; - - - udfpsViewPerformance = foundPackage; - - - unfoldAnimationBackgroundProgress = foundPackage; - - - updateUserSwitcherBackground = foundPackage; - - - validateKeyboardShortcutHelperIconUri = foundPackage; - - - visualInterruptionsRefactor = foundPackage; - - isCached = true; - } - - - - - private void load_overrides_accessibility() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - createWindowlessWindowMagnifier = - properties.getBoolean(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, true); - delayShowMagnificationButton = - properties.getBoolean(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, true); - floatingMenuAnimatedTuck = - properties.getBoolean(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, true); - floatingMenuDragToEdit = - properties.getBoolean(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, true); - floatingMenuDragToHide = - properties.getBoolean(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, false); - floatingMenuImeDisplacementAnimation = - properties.getBoolean(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, true); - floatingMenuNarrowTargetContentObserver = - properties.getBoolean(Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, true); - floatingMenuOverlapsNavBarsFlag = - properties.getBoolean(Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, true); - floatingMenuRadiiAnimation = - properties.getBoolean(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, true); - hearingDevicesDialogRelatedTools = - properties.getBoolean(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, true); - saveAndRestoreMagnificationSettingsButtons = - properties.getBoolean(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, false); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace accessibility " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - accessibility_is_cached = true; - } - - private void load_overrides_biometrics_framework() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - bpTalkback = - properties.getBoolean(Flags.FLAG_BP_TALKBACK, true); - constraintBp = - properties.getBoolean(Flags.FLAG_CONSTRAINT_BP, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace biometrics_framework " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - biometrics_framework_is_cached = true; - } - - private void load_overrides_communal() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - communalHub = - properties.getBoolean(Flags.FLAG_COMMUNAL_HUB, true); - enableWidgetPickerSizeFilter = - properties.getBoolean(Flags.FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER, false); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace communal " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - communal_is_cached = true; - } - - private void load_overrides_systemui() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - activityTransitionUseLargestWindow = - properties.getBoolean(Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, true); - ambientTouchMonitorListenToDisplayChanges = - properties.getBoolean(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, false); - appClipsBacklinks = - properties.getBoolean(Flags.FLAG_APP_CLIPS_BACKLINKS, false); - bindKeyguardMediaVisibility = - properties.getBoolean(Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, true); - brightnessSliderFocusState = - properties.getBoolean(Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, false); - centralizedStatusBarHeightFix = - properties.getBoolean(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, true); - clipboardNoninteractiveOnLockscreen = - properties.getBoolean(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, false); - clockReactiveVariants = - properties.getBoolean(Flags.FLAG_CLOCK_REACTIVE_VARIANTS, false); - communalBouncerDoNotModifyPluginOpen = - properties.getBoolean(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, false); - composeBouncer = - properties.getBoolean(Flags.FLAG_COMPOSE_BOUNCER, false); - composeLockscreen = - properties.getBoolean(Flags.FLAG_COMPOSE_LOCKSCREEN, false); - confineNotificationTouchToViewWidth = - properties.getBoolean(Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, true); - contextualTipsAssistantDismissFix = - properties.getBoolean(Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, true); - coroutineTracing = - properties.getBoolean(Flags.FLAG_COROUTINE_TRACING, true); - dedicatedNotifInflationThread = - properties.getBoolean(Flags.FLAG_DEDICATED_NOTIF_INFLATION_THREAD, true); - delayedWakelockReleaseOnBackgroundThread = - properties.getBoolean(Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD, true); - deviceEntryUdfpsRefactor = - properties.getBoolean(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, true); - disableContextualTipsFrequencyCheck = - properties.getBoolean(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, true); - disableContextualTipsIosSwitcherCheck = - properties.getBoolean(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, true); - dozeuiSchedulingAlarmsBackgroundExecution = - properties.getBoolean(Flags.FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION, false); - dreamInputSessionPilferOnce = - properties.getBoolean(Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, false); - dreamOverlayBouncerSwipeDirectionFiltering = - properties.getBoolean(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, true); - dualShade = - properties.getBoolean(Flags.FLAG_DUAL_SHADE, false); - edgeBackGestureHandlerThread = - properties.getBoolean(Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, false); - edgebackGestureHandlerGetRunningTasksBackground = - properties.getBoolean(Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, true); - enableBackgroundKeyguardOndrawnCallback = - properties.getBoolean(Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, true); - enableContextualTipForMuteVolume = - properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, false); - enableContextualTipForPowerOff = - properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, true); - enableContextualTipForTakeScreenshot = - properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, true); - enableContextualTips = - properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, true); - enableEfficientDisplayRepository = - properties.getBoolean(Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, false); - enableLayoutTracing = - properties.getBoolean(Flags.FLAG_ENABLE_LAYOUT_TRACING, false); - enableViewCaptureTracing = - properties.getBoolean(Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, false); - enforceBrightnessBaseUserRestriction = - properties.getBoolean(Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, true); - exampleFlag = - properties.getBoolean(Flags.FLAG_EXAMPLE_FLAG, false); - fastUnlockTransition = - properties.getBoolean(Flags.FLAG_FAST_UNLOCK_TRANSITION, false); - fixImageWallpaperCrashSurfaceAlreadyReleased = - properties.getBoolean(Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, true); - fixScreenshotActionDismissSystemWindows = - properties.getBoolean(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, true); - generatedPreviews = - properties.getBoolean(Flags.FLAG_GENERATED_PREVIEWS, true); - getConnectedDeviceNameUnsynchronized = - properties.getBoolean(Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, true); - glanceableHubAllowKeyguardWhenDreaming = - properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, false); - glanceableHubFullscreenSwipe = - properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE, false); - glanceableHubGestureHandle = - properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE, false); - glanceableHubShortcutButton = - properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, false); - hapticBrightnessSlider = - properties.getBoolean(Flags.FLAG_HAPTIC_BRIGHTNESS_SLIDER, true); - hapticVolumeSlider = - properties.getBoolean(Flags.FLAG_HAPTIC_VOLUME_SLIDER, true); - hearingAidsQsTileDialog = - properties.getBoolean(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, true); - keyboardDockingIndicator = - properties.getBoolean(Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, true); - keyboardShortcutHelperRewrite = - properties.getBoolean(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, false); - keyguardBottomAreaRefactor = - properties.getBoolean(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, true); - keyguardWmStateRefactor = - properties.getBoolean(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, false); - lightRevealMigration = - properties.getBoolean(Flags.FLAG_LIGHT_REVEAL_MIGRATION, true); - mediaControlsLockscreenShadeBugFix = - properties.getBoolean(Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, true); - mediaControlsRefactor = - properties.getBoolean(Flags.FLAG_MEDIA_CONTROLS_REFACTOR, true); - mediaControlsUserInitiatedDeleteintent = - properties.getBoolean(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, true); - migrateClocksToBlueprint = - properties.getBoolean(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, true); - newAodTransition = - properties.getBoolean(Flags.FLAG_NEW_AOD_TRANSITION, true); - newTouchpadGesturesTutorial = - properties.getBoolean(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, false); - newVolumePanel = - properties.getBoolean(Flags.FLAG_NEW_VOLUME_PANEL, true); - notificationAsyncGroupHeaderInflation = - properties.getBoolean(Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, true); - notificationAsyncHybridViewInflation = - properties.getBoolean(Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, true); - notificationAvalancheSuppression = - properties.getBoolean(Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, true); - notificationAvalancheThrottleHun = - properties.getBoolean(Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, true); - notificationBackgroundTintOptimization = - properties.getBoolean(Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, true); - notificationColorUpdateLogger = - properties.getBoolean(Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, false); - notificationContentAlphaOptimization = - properties.getBoolean(Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, true); - notificationFooterBackgroundTintOptimization = - properties.getBoolean(Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, false); - notificationMediaManagerBackgroundExecution = - properties.getBoolean(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION, true); - notificationMinimalismPrototype = - properties.getBoolean(Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE, false); - notificationOverExpansionClippingFix = - properties.getBoolean(Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, true); - notificationPulsingFix = - properties.getBoolean(Flags.FLAG_NOTIFICATION_PULSING_FIX, true); - notificationRowContentBinderRefactor = - properties.getBoolean(Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, false); - notificationRowUserContext = - properties.getBoolean(Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, true); - notificationViewFlipperPausingV2 = - properties.getBoolean(Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, true); - notificationsBackgroundIcons = - properties.getBoolean(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, false); - notificationsFooterViewRefactor = - properties.getBoolean(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR, true); - notificationsHeadsUpRefactor = - properties.getBoolean(Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR, true); - notificationsHideOnDisplaySwitch = - properties.getBoolean(Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, false); - notificationsIconContainerRefactor = - properties.getBoolean(Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, true); - notificationsImprovedHunAnimation = - properties.getBoolean(Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION, true); - notificationsLiveDataStoreRefactor = - properties.getBoolean(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, true); - notifyPowerManagerUserActivityBackground = - properties.getBoolean(Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, true); - pinInputFieldStyledFocusState = - properties.getBoolean(Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, true); - predictiveBackAnimateBouncer = - properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER, true); - predictiveBackAnimateDialogs = - properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS, true); - predictiveBackAnimateShade = - properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, false); - predictiveBackSysui = - properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_SYSUI, true); - priorityPeopleSection = - properties.getBoolean(Flags.FLAG_PRIORITY_PEOPLE_SECTION, true); - privacyDotUnfoldWrongCornerFix = - properties.getBoolean(Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX, true); - pssAppSelectorAbruptExitFix = - properties.getBoolean(Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, true); - pssAppSelectorRecentsSplitScreen = - properties.getBoolean(Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, true); - pssTaskSwitcher = - properties.getBoolean(Flags.FLAG_PSS_TASK_SWITCHER, false); - qsCustomTileClickGuaranteedBugFix = - properties.getBoolean(Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, true); - qsNewPipeline = - properties.getBoolean(Flags.FLAG_QS_NEW_PIPELINE, true); - qsNewTiles = - properties.getBoolean(Flags.FLAG_QS_NEW_TILES, false); - qsNewTilesFuture = - properties.getBoolean(Flags.FLAG_QS_NEW_TILES_FUTURE, false); - qsTileFocusState = - properties.getBoolean(Flags.FLAG_QS_TILE_FOCUS_STATE, true); - qsUiRefactor = - properties.getBoolean(Flags.FLAG_QS_UI_REFACTOR, false); - quickSettingsVisualHapticsLongpress = - properties.getBoolean(Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS, true); - recordIssueQsTile = - properties.getBoolean(Flags.FLAG_RECORD_ISSUE_QS_TILE, true); - refactorGetCurrentUser = - properties.getBoolean(Flags.FLAG_REFACTOR_GET_CURRENT_USER, true); - registerBatteryControllerReceiversInCorestartable = - properties.getBoolean(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, false); - registerNewWalletCardInBackground = - properties.getBoolean(Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, true); - registerWallpaperNotifierBackground = - properties.getBoolean(Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, true); - registerZenModeContentObserverBackground = - properties.getBoolean(Flags.FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND, true); - removeDreamOverlayHideOnTouch = - properties.getBoolean(Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, true); - restToUnlock = - properties.getBoolean(Flags.FLAG_REST_TO_UNLOCK, false); - restartDreamOnUnocclude = - properties.getBoolean(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, false); - revampedBouncerMessages = - properties.getBoolean(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, true); - runFingerprintDetectOnDismissibleKeyguard = - properties.getBoolean(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, true); - sceneContainer = - properties.getBoolean(Flags.FLAG_SCENE_CONTAINER, false); - screenshareNotificationHidingBugFix = - properties.getBoolean(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, true); - screenshotActionDismissSystemWindows = - properties.getBoolean(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, true); - screenshotPrivateProfileAccessibilityAnnouncementFix = - properties.getBoolean(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX, true); - screenshotPrivateProfileBehaviorFix = - properties.getBoolean(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX, true); - screenshotScrollCropViewCrashFix = - properties.getBoolean(Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, true); - screenshotShelfUi2 = - properties.getBoolean(Flags.FLAG_SCREENSHOT_SHELF_UI2, true); - shadeCollapseActivityLaunchFix = - properties.getBoolean(Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX, false); - shaderlibLoadingEffectRefactor = - properties.getBoolean(Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, true); - sliceBroadcastRelayInBackground = - properties.getBoolean(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, true); - sliceManagerBinderCallBackground = - properties.getBoolean(Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, true); - smartspaceLockscreenViewmodel = - properties.getBoolean(Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, true); - smartspaceRelocateToBottom = - properties.getBoolean(Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, false); - smartspaceRemoteviewsRendering = - properties.getBoolean(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING, false); - statusBarMonochromeIconsFix = - properties.getBoolean(Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, true); - statusBarScreenSharingChips = - properties.getBoolean(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, true); - statusBarStaticInoutIndicators = - properties.getBoolean(Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, false); - switchUserOnBg = - properties.getBoolean(Flags.FLAG_SWITCH_USER_ON_BG, true); - sysuiTeamfood = - properties.getBoolean(Flags.FLAG_SYSUI_TEAMFOOD, true); - themeOverlayControllerWakefulnessDeprecation = - properties.getBoolean(Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, false); - translucentOccludingActivityFix = - properties.getBoolean(Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, false); - truncatedStatusBarIconsFix = - properties.getBoolean(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX, true); - udfpsViewPerformance = - properties.getBoolean(Flags.FLAG_UDFPS_VIEW_PERFORMANCE, true); - unfoldAnimationBackgroundProgress = - properties.getBoolean(Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, true); - updateUserSwitcherBackground = - properties.getBoolean(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, true); - validateKeyboardShortcutHelperIconUri = - properties.getBoolean(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI, true); - visualInterruptionsRefactor = - properties.getBoolean(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace systemui " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - systemui_is_cached = true; - } - - @Override - + @Override + + public boolean activityTransitionUseLargestWindow() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return activityTransitionUseLargestWindow; + return true; + } + + @Override + + public boolean addBlackBackgroundForWindowMagnifier() { + return true; } @Override - - public boolean ambientTouchMonitorListenToDisplayChanges() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return ambientTouchMonitorListenToDisplayChanges; + + public boolean alwaysComposeQsUiFragment() { + return false; + } + + @Override + + + public boolean ambientTouchMonitorListenToDisplayChanges() { + return true; } @Override - + + public boolean appClipsBacklinks() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return appClipsBacklinks; + return true; + } + + @Override + + + public boolean appShortcutRemovalFix() { + return true; + } + + @Override + + public boolean avalancheReplaceHunWhenCritical() { + return false; } @Override - + + public boolean bindKeyguardMediaVisibility() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return bindKeyguardMediaVisibility; + return true; + } + + @Override + + + public boolean bouncerUiRevamp() { + return false; + } + @Override + + + public boolean bouncerUiRevamp2() { + return false; } @Override - - public boolean bpTalkback() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!biometrics_framework_is_cached) { - load_overrides_biometrics_framework(); - } - } - return bpTalkback; + + public boolean bpColors() { + return false; } @Override - + + public boolean brightnessSliderFocusState() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return brightnessSliderFocusState; + return false; + } + + @Override + + + public boolean checkLockscreenGoneTransition() { + return true; + } + @Override + + + public boolean classicFlagsMultiUser() { + return true; } @Override - - public boolean centralizedStatusBarHeightFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return centralizedStatusBarHeightFix; + + public boolean clipboardImageTimeout() { + return true; } @Override - + + public boolean clipboardNoninteractiveOnLockscreen() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return clipboardNoninteractiveOnLockscreen; + return true; + } + + @Override + + + public boolean clipboardOverlayMultiuser() { + return false; + } + + @Override + + + public boolean clipboardSharedTransitions() { + return true; + } + + @Override + + public boolean clipboardUseDescriptionMimetype() { + return true; } @Override - - public boolean clockReactiveVariants() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return clockReactiveVariants; + + public boolean clockFidgetAnimation() { + return false; } @Override - + + public boolean communalBouncerDoNotModifyPluginOpen() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return communalBouncerDoNotModifyPluginOpen; + return true; + } + @Override + + + public boolean communalEditWidgetsActivityFinishFix() { + return true; } @Override - + + public boolean communalHub() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!communal_is_cached) { - load_overrides_communal(); - } - } - return communalHub; + return true; + } + + @Override + + public boolean communalHubUseThreadPoolForWidgets() { + return true; } @Override - - public boolean composeBouncer() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return composeBouncer; + + public boolean communalResponsiveGrid() { + return false; } @Override - - public boolean composeLockscreen() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return composeLockscreen; + + public boolean communalSceneKtfRefactor() { + return true; } @Override - - public boolean confineNotificationTouchToViewWidth() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return confineNotificationTouchToViewWidth; + + public boolean communalStandaloneSupport() { + return false; } @Override - - public boolean constraintBp() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!biometrics_framework_is_cached) { - load_overrides_biometrics_framework(); - } - } - return constraintBp; + + public boolean communalTimerFlickerFix() { + return true; } @Override - - public boolean contextualTipsAssistantDismissFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return contextualTipsAssistantDismissFix; + + public boolean communalWidgetResizing() { + return true; } @Override - - public boolean coroutineTracing() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return coroutineTracing; + + public boolean communalWidgetTrampolineFix() { + return true; + } + + @Override + + + public boolean composeBouncer() { + return false; + } + + @Override + + + public boolean confineNotificationTouchToViewWidth() { + return false; + } + + @Override + + + public boolean contAuthPlugin() { + return false; + } + + @Override + + + public boolean contextualTipsAssistantDismissFix() { + return true; + } + + @Override + + + public boolean coroutineTracing() { + return false; } @Override - + + public boolean createWindowlessWindowMagnifier() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return createWindowlessWindowMagnifier; + return true; + } + + @Override + + public boolean debugLiveUpdatesPromoteAll() { + return false; } @Override - - public boolean dedicatedNotifInflationThread() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return dedicatedNotifInflationThread; + + public boolean decoupleViewControllerInAnimlib() { + return false; } @Override - - public boolean delayShowMagnificationButton() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return delayShowMagnificationButton; + + public boolean delayShowMagnificationButton() { + return true; } @Override - - public boolean delayedWakelockReleaseOnBackgroundThread() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return delayedWakelockReleaseOnBackgroundThread; + + public boolean desktopEffectsQsTile() { + return false; } @Override - + + public boolean deviceEntryUdfpsRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return deviceEntryUdfpsRefactor; + return true; + } + + @Override + + public boolean disableBlurredShadeVisible() { + return false; } @Override - - public boolean disableContextualTipsFrequencyCheck() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return disableContextualTipsFrequencyCheck; + + public boolean disableContextualTipsFrequencyCheck() { + return false; } @Override - + + public boolean disableContextualTipsIosSwitcherCheck() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return disableContextualTipsIosSwitcherCheck; + return false; + } + + @Override + + public boolean disableShadeTrackpadTwoFingerSwipe() { + return false; } @Override - - public boolean dozeuiSchedulingAlarmsBackgroundExecution() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return dozeuiSchedulingAlarmsBackgroundExecution; + + public boolean doubleTapToSleep() { + return false; } @Override - - public boolean dreamInputSessionPilferOnce() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return dreamInputSessionPilferOnce; + + public boolean dreamInputSessionPilferOnce() { + return true; } @Override - - public boolean dreamOverlayBouncerSwipeDirectionFiltering() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return dreamOverlayBouncerSwipeDirectionFiltering; + + public boolean dreamOverlayBouncerSwipeDirectionFiltering() { + return true; } @Override - - public boolean dualShade() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return dualShade; + + public boolean dreamOverlayUpdatedFont() { + return false; } @Override - + + public boolean edgeBackGestureHandlerThread() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return edgeBackGestureHandlerThread; + return false; + } + + @Override + + + public boolean edgebackGestureHandlerGetRunningTasksBackground() { + return true; + } + + @Override + + + public boolean enableBackgroundKeyguardOndrawnCallback() { + return true; + } + + @Override + + + public boolean enableContextualTipForMuteVolume() { + return true; + } + + @Override + + + public boolean enableContextualTipForPowerOff() { + return true; + } + + @Override + + + public boolean enableContextualTipForTakeScreenshot() { + return true; + } + + @Override + + + public boolean enableContextualTips() { + return true; + } + + @Override + + + public boolean enableEfficientDisplayRepository() { + return true; + } + + @Override + + + public boolean enableLayoutTracing() { + return false; + } + + @Override + + + public boolean enableUnderlay() { + return false; + } + + @Override + + + public boolean enableViewCaptureTracing() { + return false; + } + + @Override + + + public boolean enforceBrightnessBaseUserRestriction() { + return true; + } + + @Override + + + public boolean exampleFlag() { + return false; + } + + @Override + + + public boolean expandCollapsePrivacyDialog() { + return true; + } + + @Override + + + public boolean expandHeadsUpOnInlineReply() { + return true; + } + + @Override + + + public boolean expandedPrivacyIndicatorsOnLargeScreen() { + return false; + } + + @Override + + + public boolean extendedAppsShortcutCategory() { + return false; + } + + @Override + + + public boolean faceMessageDeferUpdate() { + return true; + } + + @Override + + + public boolean faceScanningAnimationNpeFix() { + return true; + } + + @Override + + + public boolean fasterUnlockTransition() { + return true; + } + + @Override + + + public boolean fetchBookmarksXmlKeyboardShortcuts() { + return true; + } + + @Override + + + public boolean fixImageWallpaperCrashSurfaceAlreadyReleased() { + return true; + } + + @Override + + + public boolean fixScreenshotActionDismissSystemWindows() { + return true; + } + + @Override + + + public boolean floatingMenuAnimatedTuck() { + return false; + } + + @Override + + + public boolean floatingMenuDisplayCutoutSupport() { + return true; + } + + @Override + + + public boolean floatingMenuDragToEdit() { + return true; + } + + @Override + + + public boolean floatingMenuDragToHide() { + return false; + } + + @Override + + + public boolean floatingMenuHearingDeviceStatusIcon() { + return false; + } + + @Override + + + public boolean floatingMenuImeDisplacementAnimation() { + return false; + } + + @Override + + + public boolean floatingMenuNarrowTargetContentObserver() { + return true; + } + + @Override + + + public boolean floatingMenuNotifyTargetsChangedOnStrictDiff() { + return true; + } + + @Override + + + public boolean floatingMenuOverlapsNavBarsFlag() { + return false; + } + + @Override + + + public boolean floatingMenuRadiiAnimation() { + return false; + } + + @Override + + + public boolean getConnectedDeviceNameUnsynchronized() { + return true; + } + + @Override + + + public boolean glanceableHubAllowKeyguardWhenDreaming() { + return false; + } + + @Override + + + public boolean glanceableHubBlurredBackground() { + return false; + } + + @Override + + + public boolean glanceableHubDirectEditMode() { + return false; + } + + @Override + + + public boolean glanceableHubV2() { + return false; + } + + @Override + + + public boolean glanceableHubV2Resources() { + return false; + } + + @Override + + + public boolean hapticsForComposeSliders() { + return true; + } + + @Override + + + public boolean hardwareColorStyles() { + return false; + } + + @Override + + + public boolean hearingAidsQsTileDialog() { + return true; + } + + @Override + + + public boolean hearingDevicesDialogRelatedTools() { + return true; + } + + @Override + + + public boolean hideRingerButtonInSingleVolumeMode() { + return false; + } + + @Override + + + public boolean homeControlsDreamHsum() { + return true; + } + + @Override + + + public boolean hubEditModeTouchAdjustments() { + return false; + } + + @Override + + + public boolean hubmodeFullscreenVerticalSwipe() { + return false; + } + + @Override + + + public boolean hubmodeFullscreenVerticalSwipeFix() { + return true; + } + + @Override + + + public boolean iconRefresh2025() { + return false; + } + + @Override + + + public boolean ignoreTouchesNextToNotificationShelf() { + return true; + } + + @Override + + + public boolean indicationTextA11yFix() { + return true; + } + + @Override + + + public boolean keyboardDockingIndicator() { + return false; + } + + @Override + + + public boolean keyboardShortcutHelperRewrite() { + return true; + } + + @Override + + + public boolean keyboardShortcutHelperShortcutCustomizer() { + return true; + } + + @Override + + + public boolean keyboardTouchpadContextualEducation() { + return true; + } + + @Override + + + public boolean keyguardTransitionForceFinishOnScreenOff() { + return false; + } + + @Override + + + public boolean keyguardWmReorderAtmsCalls() { + return true; + } + + @Override + + + public boolean keyguardWmStateRefactor() { + return false; + } + + @Override + + + public boolean lockscreenFont() { + return false; + } + + @Override + + + public boolean lowLightClockDream() { + return false; + } + + @Override + + + public boolean magneticNotificationSwipes() { + return false; + } + + @Override + + + public boolean mediaControlsA11yColors() { + return true; + } + + @Override + + + public boolean mediaControlsButtonMedia3() { + return false; + } + + @Override + + + public boolean mediaControlsButtonMedia3Placement() { + return false; + } + + @Override + + + public boolean mediaControlsDeviceManagerBackgroundExecution() { + return false; + } + + @Override + + + public boolean mediaControlsDrawablesReuseBugfix() { + return true; + } + + @Override + + + public boolean mediaControlsLockscreenShadeBugFix() { + return true; + } + + @Override + + + public boolean mediaControlsUiUpdate() { + return false; + } + + @Override + + + public boolean mediaControlsUmoInflationInBackground() { + return true; + } + + @Override + + + public boolean mediaControlsUserInitiatedDeleteintent() { + return true; + } + + @Override + + + public boolean mediaLoadMetadataViaMediaDataLoader() { + return true; + } + + @Override + + + public boolean mediaLockscreenLaunchAnimation() { + return true; + } + + @Override + + + public boolean mediaProjectionDialogBehindLockscreen() { + return true; + } + + @Override + + + public boolean mediaProjectionGreyErrorText() { + return true; + } + + @Override + + + public boolean mediaProjectionRequestAttributionFix() { + return false; + } + + @Override + + + public boolean modesUiDialogPaging() { + return false; + } + + @Override + + + public boolean moveTransitionAnimationLayer() { + return false; + } + + @Override + + + public boolean msdlFeedback() { + return false; + } + + @Override + + + public boolean multiuserWifiPickerTrackerSupport() { + return false; + } + + @Override + + + public boolean newAodTransition() { + return true; + } + + @Override + + + public boolean newVolumePanel() { + return true; + } + + @Override + + + public boolean nonTouchscreenDevicesBypassFalsing() { + return false; + } + + @Override + + + public boolean notesRoleQsTile() { + return false; + } + + @Override + + + public boolean notificationAddXOnHoverToDismiss() { + return false; + } + + @Override + + + public boolean notificationAmbientSuppressionAfterInflation() { + return false; + } + + @Override + + + public boolean notificationAnimatedActionsTreatment() { + return false; + } + + @Override + + + public boolean notificationAppearNonlinear() { + return true; + } + + @Override + + + public boolean notificationAsyncGroupHeaderInflation() { + return true; + } + + @Override + + + public boolean notificationAsyncHybridViewInflation() { + return true; + } + + @Override + + + public boolean notificationAvalancheSuppression() { + return true; + } + + @Override + + + public boolean notificationAvalancheThrottleHun() { + return true; + } + + @Override + + + public boolean notificationBackgroundTintOptimization() { + return true; + } + + @Override + + + public boolean notificationBundleUi() { + return false; + } + + @Override + + + public boolean notificationColorUpdateLogger() { + return false; + } + + @Override + + + public boolean notificationContentAlphaOptimization() { + return false; + } + + @Override + + + public boolean notificationFooterBackgroundTintOptimization() { + return false; + } + + @Override + + + public boolean notificationOverExpansionClippingFix() { + return true; + } + + @Override + + + public boolean notificationReentrantDismiss() { + return true; + } + + @Override + + + public boolean notificationRowAccessibilityExpanded() { + return true; + } + + @Override + + + public boolean notificationRowContentBinderRefactor() { + return true; + } + + @Override + + + public boolean notificationRowTransparency() { + return false; + } + + @Override + + + public boolean notificationRowUserContext() { + return true; + } + + @Override + + + public boolean notificationShadeBlur() { + return false; + } + + @Override + + + public boolean notificationShadeUiThread() { + return false; + } + + @Override + + + public boolean notificationSkipSilentUpdates() { + return false; + } + + @Override + + + public boolean notificationTransparentHeaderFix() { + return true; + } + + @Override + + + public boolean notificationViewFlipperPausingV2() { + return true; + } + + @Override + + + public boolean notificationsBackgroundIcons() { + return true; + } + + @Override + + public boolean notificationsFooterVisibilityFix() { + return true; } @Override - - public boolean edgebackGestureHandlerGetRunningTasksBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return edgebackGestureHandlerGetRunningTasksBackground; + + public boolean notificationsHideOnDisplaySwitch() { + return false; } @Override - - public boolean enableBackgroundKeyguardOndrawnCallback() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableBackgroundKeyguardOndrawnCallback; + + public boolean notificationsHunSharedAnimationValues() { + return false; } @Override - - public boolean enableContextualTipForMuteVolume() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableContextualTipForMuteVolume; + + public boolean notificationsIconContainerRefactor() { + return true; } @Override - - public boolean enableContextualTipForPowerOff() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableContextualTipForPowerOff; + + public boolean notificationsLaunchRadius() { + return false; } @Override - - public boolean enableContextualTipForTakeScreenshot() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableContextualTipForTakeScreenshot; + + public boolean notificationsLiveDataStoreRefactor() { + return true; } @Override - - public boolean enableContextualTips() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableContextualTips; + + public boolean notificationsPinnedHunInShade() { + return true; } @Override - - public boolean enableEfficientDisplayRepository() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableEfficientDisplayRepository; + + public boolean notificationsRedesignFooterView() { + return false; } @Override - - public boolean enableLayoutTracing() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableLayoutTracing; + + public boolean notificationsRedesignGuts() { + return false; } @Override - - public boolean enableViewCaptureTracing() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableViewCaptureTracing; + + public boolean notifyPasswordTextViewUserActivityInBackground() { + return true; } @Override - - public boolean enableWidgetPickerSizeFilter() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!communal_is_cached) { - load_overrides_communal(); - } - } - return enableWidgetPickerSizeFilter; + + public boolean notifyPowerManagerUserActivityBackground() { + return true; } @Override - - public boolean enforceBrightnessBaseUserRestriction() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enforceBrightnessBaseUserRestriction; + + public boolean onlyShowMediaStreamSliderInSingleVolumeMode() { + return true; } @Override - - public boolean exampleFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return exampleFlag; + + public boolean outputSwitcherRedesign() { + return false; } @Override - - public boolean fastUnlockTransition() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return fastUnlockTransition; + + public boolean overrideSuppressOverlayCondition() { + return false; } @Override - - public boolean fixImageWallpaperCrashSurfaceAlreadyReleased() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return fixImageWallpaperCrashSurfaceAlreadyReleased; + + public boolean permissionHelperInlineUiRichOngoing() { + return false; } @Override - - public boolean fixScreenshotActionDismissSystemWindows() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return fixScreenshotActionDismissSystemWindows; + + public boolean permissionHelperUiRichOngoing() { + return false; } @Override - - public boolean floatingMenuAnimatedTuck() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return floatingMenuAnimatedTuck; + + public boolean physicalNotificationMovement() { + return false; } @Override - - public boolean floatingMenuDragToEdit() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return floatingMenuDragToEdit; + + public boolean pinInputFieldStyledFocusState() { + return true; } @Override - - public boolean floatingMenuDragToHide() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return floatingMenuDragToHide; + + public boolean predictiveBackAnimateShade() { + return false; } @Override - - public boolean floatingMenuImeDisplacementAnimation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return floatingMenuImeDisplacementAnimation; + + public boolean predictiveBackDelayWmTransition() { + return false; } @Override - - public boolean floatingMenuNarrowTargetContentObserver() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return floatingMenuNarrowTargetContentObserver; + + public boolean priorityPeopleSection() { + return true; } @Override - - public boolean floatingMenuOverlapsNavBarsFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return floatingMenuOverlapsNavBarsFlag; + + public boolean promoteNotificationsAutomatically() { + return false; } @Override - - public boolean floatingMenuRadiiAnimation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return floatingMenuRadiiAnimation; - } - - @Override - - public boolean generatedPreviews() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return generatedPreviews; + public boolean pssAppSelectorRecentsSplitScreen() { + return true; } @Override - - public boolean getConnectedDeviceNameUnsynchronized() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return getConnectedDeviceNameUnsynchronized; + + public boolean pssTaskSwitcher() { + return false; } @Override - - public boolean glanceableHubAllowKeyguardWhenDreaming() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return glanceableHubAllowKeyguardWhenDreaming; + + public boolean qsCustomTileClickGuaranteedBugFix() { + return true; } @Override - - public boolean glanceableHubFullscreenSwipe() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return glanceableHubFullscreenSwipe; + + public boolean qsNewTiles() { + return false; } @Override - - public boolean glanceableHubGestureHandle() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return glanceableHubGestureHandle; + + public boolean qsNewTilesFuture() { + return false; } @Override - - public boolean glanceableHubShortcutButton() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return glanceableHubShortcutButton; + + public boolean qsQuickRebindActiveTiles() { + return false; } @Override - - public boolean hapticBrightnessSlider() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return hapticBrightnessSlider; + + public boolean qsRegisterSettingObserverOnBgThread() { + return true; } @Override - - public boolean hapticVolumeSlider() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return hapticVolumeSlider; + + public boolean qsTileDetailedView() { + return false; } @Override - - public boolean hearingAidsQsTileDialog() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return hearingAidsQsTileDialog; + + public boolean qsTileFocusState() { + return true; } @Override - - public boolean hearingDevicesDialogRelatedTools() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return hearingDevicesDialogRelatedTools; + + public boolean qsUiRefactor() { + return false; } @Override - - public boolean keyboardDockingIndicator() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return keyboardDockingIndicator; + + public boolean qsUiRefactorComposeFragment() { + return false; } @Override - - public boolean keyboardShortcutHelperRewrite() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return keyboardShortcutHelperRewrite; + + public boolean recordIssueQsTile() { + return true; } @Override - - public boolean keyguardBottomAreaRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return keyguardBottomAreaRefactor; + + public boolean redesignMagnificationWindowSize() { + return false; } @Override - - public boolean keyguardWmStateRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return keyguardWmStateRefactor; + + public boolean refactorGetCurrentUser() { + return true; } @Override - - public boolean lightRevealMigration() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return lightRevealMigration; + + public boolean registerBatteryControllerReceiversInCorestartable() { + return false; } @Override - - public boolean mediaControlsLockscreenShadeBugFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return mediaControlsLockscreenShadeBugFix; + + public boolean registerContentObserversAsync() { + return true; } @Override - - public boolean mediaControlsRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return mediaControlsRefactor; + + public boolean registerNewWalletCardInBackground() { + return true; } @Override - - public boolean mediaControlsUserInitiatedDeleteintent() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return mediaControlsUserInitiatedDeleteintent; + + public boolean registerWallpaperNotifierBackground() { + return true; } @Override - - public boolean migrateClocksToBlueprint() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return migrateClocksToBlueprint; + + public boolean relockWithPowerButtonImmediately() { + return true; } @Override - - public boolean newAodTransition() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return newAodTransition; + + public boolean removeDreamOverlayHideOnTouch() { + return true; } @Override - - public boolean newTouchpadGesturesTutorial() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return newTouchpadGesturesTutorial; + + public boolean removeUpdateListenerInQsIconViewImpl() { + return true; } @Override - - public boolean newVolumePanel() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return newVolumePanel; + + public boolean restToUnlock() { + return false; } @Override - - public boolean notificationAsyncGroupHeaderInflation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationAsyncGroupHeaderInflation; + + public boolean restartDreamOnUnocclude() { + return false; } @Override - - public boolean notificationAsyncHybridViewInflation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationAsyncHybridViewInflation; + + public boolean revampedBouncerMessages() { + return true; } @Override - - public boolean notificationAvalancheSuppression() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationAvalancheSuppression; + + public boolean runFingerprintDetectOnDismissibleKeyguard() { + return false; } @Override - - public boolean notificationAvalancheThrottleHun() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationAvalancheThrottleHun; + + public boolean saveAndRestoreMagnificationSettingsButtons() { + return true; } @Override - - public boolean notificationBackgroundTintOptimization() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationBackgroundTintOptimization; + + public boolean sceneContainer() { + return false; } @Override - - public boolean notificationColorUpdateLogger() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationColorUpdateLogger; + + public boolean screenshareNotificationHidingBugFix() { + return true; } @Override - - public boolean notificationContentAlphaOptimization() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationContentAlphaOptimization; + + public boolean screenshotActionDismissSystemWindows() { + return false; } @Override - - public boolean notificationFooterBackgroundTintOptimization() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationFooterBackgroundTintOptimization; + + public boolean screenshotMultidisplayFocusChange() { + return false; } @Override - - public boolean notificationMediaManagerBackgroundExecution() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationMediaManagerBackgroundExecution; + + public boolean screenshotPolicySplitAndDesktopMode() { + return true; } @Override - - public boolean notificationMinimalismPrototype() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationMinimalismPrototype; + + public boolean screenshotScrollCropViewCrashFix() { + return true; } @Override - - public boolean notificationOverExpansionClippingFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationOverExpansionClippingFix; + + public boolean screenshotUiControllerRefactor() { + return true; } @Override - - public boolean notificationPulsingFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationPulsingFix; + + public boolean secondaryUserWidgetHost() { + return false; } @Override - - public boolean notificationRowContentBinderRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationRowContentBinderRefactor; + + public boolean settingsExtRegisterContentObserverOnBgThread() { + return true; } @Override - - public boolean notificationRowUserContext() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationRowUserContext; + + public boolean shadeExpandsOnStatusBarLongPress() { + return true; } @Override - - public boolean notificationViewFlipperPausingV2() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationViewFlipperPausingV2; + + public boolean shadeHeaderFontUpdate() { + return false; } @Override - - public boolean notificationsBackgroundIcons() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationsBackgroundIcons; + + public boolean shadeLaunchAccessibility() { + return true; } @Override - - public boolean notificationsFooterViewRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationsFooterViewRefactor; + + public boolean shadeWindowGoesAround() { + return false; } @Override - - public boolean notificationsHeadsUpRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationsHeadsUpRefactor; + + public boolean shaderlibLoadingEffectRefactor() { + return true; } @Override - - public boolean notificationsHideOnDisplaySwitch() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationsHideOnDisplaySwitch; + + public boolean shortcutHelperKeyGlyph() { + return true; } @Override - - public boolean notificationsIconContainerRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationsIconContainerRefactor; + + public boolean showAudioSharingSliderInVolumePanel() { + return false; } @Override - - public boolean notificationsImprovedHunAnimation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationsImprovedHunAnimation; + + public boolean showClipboardIndication() { + return false; } @Override - - public boolean notificationsLiveDataStoreRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notificationsLiveDataStoreRefactor; + + public boolean showLockedByYourWatchKeyguardIndicator() { + return false; } @Override - - public boolean notifyPowerManagerUserActivityBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return notifyPowerManagerUserActivityBackground; + + public boolean showToastWhenAppControlBrightness() { + return true; } @Override - - public boolean pinInputFieldStyledFocusState() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return pinInputFieldStyledFocusState; + + public boolean simPinBouncerReset() { + return true; } @Override - - public boolean predictiveBackAnimateBouncer() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return predictiveBackAnimateBouncer; + + public boolean simPinRaceConditionOnRestart() { + return true; } @Override - - public boolean predictiveBackAnimateDialogs() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return predictiveBackAnimateDialogs; + + public boolean simPinUseSlotId() { + return true; } @Override - - public boolean predictiveBackAnimateShade() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return predictiveBackAnimateShade; + + public boolean skipHideSensitiveNotifAnimation() { + return true; } @Override - - public boolean predictiveBackSysui() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return predictiveBackSysui; + + public boolean sliceBroadcastRelayInBackground() { + return true; } @Override - - public boolean priorityPeopleSection() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return priorityPeopleSection; + + public boolean sliceManagerBinderCallBackground() { + return true; } @Override - - public boolean privacyDotUnfoldWrongCornerFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return privacyDotUnfoldWrongCornerFix; + + public boolean smartspaceLockscreenViewmodel() { + return true; } @Override - - public boolean pssAppSelectorAbruptExitFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return pssAppSelectorAbruptExitFix; + + public boolean smartspaceRelocateToBottom() { + return false; } @Override - - public boolean pssAppSelectorRecentsSplitScreen() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return pssAppSelectorRecentsSplitScreen; + + public boolean smartspaceRemoteviewsRenderingFix() { + return true; } @Override - - public boolean pssTaskSwitcher() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return pssTaskSwitcher; + + public boolean smartspaceSwipeEventLoggingFix() { + return true; } @Override - - public boolean qsCustomTileClickGuaranteedBugFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return qsCustomTileClickGuaranteedBugFix; + + public boolean smartspaceViewpager2() { + return false; } @Override - - public boolean qsNewPipeline() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return qsNewPipeline; + + public boolean sounddoseCustomization() { + return true; } @Override - - public boolean qsNewTiles() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return qsNewTiles; + + public boolean spatialModelAppPushback() { + return false; } @Override - - public boolean qsNewTilesFuture() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return qsNewTilesFuture; + + public boolean stabilizeHeadsUpGroupV2() { + return true; } @Override - - public boolean qsTileFocusState() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return qsTileFocusState; + + public boolean statusBarAlwaysCheckUnderlyingNetworks() { + return true; } @Override - - public boolean qsUiRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return qsUiRefactor; + + public boolean statusBarAutoStartScreenRecordChip() { + return true; } @Override - - public boolean quickSettingsVisualHapticsLongpress() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return quickSettingsVisualHapticsLongpress; + + public boolean statusBarChipsModernization() { + return false; } @Override - - public boolean recordIssueQsTile() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return recordIssueQsTile; + + public boolean statusBarChipsReturnAnimations() { + return false; } @Override - - public boolean refactorGetCurrentUser() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return refactorGetCurrentUser; + + public boolean statusBarFontUpdates() { + return false; } @Override - - public boolean registerBatteryControllerReceiversInCorestartable() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return registerBatteryControllerReceiversInCorestartable; + + public boolean statusBarMobileIconKairos() { + return false; } @Override - - public boolean registerNewWalletCardInBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return registerNewWalletCardInBackground; + + public boolean statusBarMonochromeIconsFix() { + return true; } @Override - - public boolean registerWallpaperNotifierBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return registerWallpaperNotifierBackground; + + public boolean statusBarNoHunBehavior() { + return false; } @Override - - public boolean registerZenModeContentObserverBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return registerZenModeContentObserverBackground; + + public boolean statusBarPopupChips() { + return false; } @Override - - public boolean removeDreamOverlayHideOnTouch() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return removeDreamOverlayHideOnTouch; + + public boolean statusBarRootModernization() { + return false; } @Override - - public boolean restToUnlock() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return restToUnlock; + + public boolean statusBarShowAudioOnlyProjectionChip() { + return true; } @Override - - public boolean restartDreamOnUnocclude() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return restartDreamOnUnocclude; + + public boolean statusBarSignalPolicyRefactor() { + return true; } @Override - - public boolean revampedBouncerMessages() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return revampedBouncerMessages; + + public boolean statusBarSignalPolicyRefactorEthernet() { + return true; } @Override - - public boolean runFingerprintDetectOnDismissibleKeyguard() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return runFingerprintDetectOnDismissibleKeyguard; + + public boolean statusBarStaticInoutIndicators() { + return false; } @Override - - public boolean saveAndRestoreMagnificationSettingsButtons() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return saveAndRestoreMagnificationSettingsButtons; + + public boolean statusBarStopUpdatingWindowHeight() { + return false; } @Override - - public boolean sceneContainer() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return sceneContainer; + + public boolean statusBarSwipeOverChip() { + return false; } @Override - - public boolean screenshareNotificationHidingBugFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return screenshareNotificationHidingBugFix; + + public boolean statusBarSwitchToSpnFromDataSpn() { + return true; } @Override - - public boolean screenshotActionDismissSystemWindows() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return screenshotActionDismissSystemWindows; + + public boolean statusBarUiThread() { + return false; } @Override - - public boolean screenshotPrivateProfileAccessibilityAnnouncementFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return screenshotPrivateProfileAccessibilityAnnouncementFix; + + public boolean statusBarWindowNoCustomTouch() { + return false; } @Override - - public boolean screenshotPrivateProfileBehaviorFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return screenshotPrivateProfileBehaviorFix; + + public boolean stoppableFgsSystemApp() { + return true; } @Override - - public boolean screenshotScrollCropViewCrashFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return screenshotScrollCropViewCrashFix; + + public boolean switchUserOnBg() { + return true; } @Override - - public boolean screenshotShelfUi2() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return screenshotShelfUi2; + + public boolean sysuiTeamfood() { + return false; } @Override - - public boolean shadeCollapseActivityLaunchFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return shadeCollapseActivityLaunchFix; + + public boolean themeOverlayControllerWakefulnessDeprecation() { + return false; } @Override - - public boolean shaderlibLoadingEffectRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return shaderlibLoadingEffectRefactor; + + public boolean transitionRaceCondition() { + return true; } @Override - - public boolean sliceBroadcastRelayInBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return sliceBroadcastRelayInBackground; + + public boolean translucentOccludingActivityFix() { + return true; } @Override - - public boolean sliceManagerBinderCallBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return sliceManagerBinderCallBackground; + + public boolean tvGlobalActionsFocus() { + return false; } @Override - - public boolean smartspaceLockscreenViewmodel() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return smartspaceLockscreenViewmodel; + + public boolean udfpsViewPerformance() { + return true; } @Override - - public boolean smartspaceRelocateToBottom() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return smartspaceRelocateToBottom; + + public boolean unfoldAnimationBackgroundProgress() { + return true; } @Override - - public boolean smartspaceRemoteviewsRendering() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return smartspaceRemoteviewsRendering; + + public boolean unfoldLatencyTrackingFix() { + return false; } @Override - - public boolean statusBarMonochromeIconsFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return statusBarMonochromeIconsFix; + + public boolean updateCornerRadiusOnDisplayChanged() { + return true; } @Override - - public boolean statusBarScreenSharingChips() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return statusBarScreenSharingChips; + + public boolean updateUserSwitcherBackground() { + return true; } @Override - - public boolean statusBarStaticInoutIndicators() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return statusBarStaticInoutIndicators; + + public boolean updateWindowMagnifierBottomBoundary() { + return false; } @Override - - public boolean switchUserOnBg() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return switchUserOnBg; + + public boolean useAadProxSensor() { + return false; } @Override - - public boolean sysuiTeamfood() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return sysuiTeamfood; + + public boolean useNotifInflationThreadForFooter() { + return true; } @Override - - public boolean themeOverlayControllerWakefulnessDeprecation() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return themeOverlayControllerWakefulnessDeprecation; + + public boolean useNotifInflationThreadForRow() { + return true; } @Override - - public boolean translucentOccludingActivityFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return translucentOccludingActivityFix; + + public boolean useTransitionsForKeyguardOccluded() { + return true; } @Override - - public boolean truncatedStatusBarIconsFix() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return truncatedStatusBarIconsFix; + + public boolean useVolumeController() { + return true; } @Override - - public boolean udfpsViewPerformance() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return udfpsViewPerformance; + + public boolean userAwareSettingsRepositories() { + return true; } @Override - - public boolean unfoldAnimationBackgroundProgress() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return unfoldAnimationBackgroundProgress; + + public boolean userEncryptedSource() { + return true; } @Override - - public boolean updateUserSwitcherBackground() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return updateUserSwitcherBackground; + + public boolean userSwitcherAddSignOutOption() { + return false; } @Override - - public boolean validateKeyboardShortcutHelperIconUri() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return validateKeyboardShortcutHelperIconUri; + + public boolean visualInterruptionsRefactor() { + return true; } @Override - - public boolean visualInterruptionsRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return visualInterruptionsRefactor; + + public boolean volumeRedesign() { + return false; } } - diff --git a/flags/src/com/android/systemui/Flags.java b/flags/src/com/android/systemui/Flags.java index dd9dc1fb734..ca51b086d1a 100644 --- a/flags/src/com/android/systemui/Flags.java +++ b/flags/src/com/android/systemui/Flags.java @@ -1,37 +1,74 @@ package com.android.systemui; // TODO(b/303773055): Remove the annotation after access issue is resolved. + /** @hide */ public final class Flags { /** @hide */ public static final String FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW = "com.android.systemui.activity_transition_use_largest_window"; /** @hide */ + public static final String FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER = "com.android.systemui.add_black_background_for_window_magnifier"; + /** @hide */ + public static final String FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT = "com.android.systemui.always_compose_qs_ui_fragment"; + /** @hide */ public static final String FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES = "com.android.systemui.ambient_touch_monitor_listen_to_display_changes"; /** @hide */ public static final String FLAG_APP_CLIPS_BACKLINKS = "com.android.systemui.app_clips_backlinks"; /** @hide */ + public static final String FLAG_APP_SHORTCUT_REMOVAL_FIX = "com.android.systemui.app_shortcut_removal_fix"; + /** @hide */ + public static final String FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL = "com.android.systemui.avalanche_replace_hun_when_critical"; + /** @hide */ public static final String FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY = "com.android.systemui.bind_keyguard_media_visibility"; /** @hide */ - public static final String FLAG_BP_TALKBACK = "com.android.systemui.bp_talkback"; + public static final String FLAG_BOUNCER_UI_REVAMP = "com.android.systemui.bouncer_ui_revamp"; + /** @hide */ + public static final String FLAG_BOUNCER_UI_REVAMP_2 = "com.android.systemui.bouncer_ui_revamp_2"; + /** @hide */ + public static final String FLAG_BP_COLORS = "com.android.systemui.bp_colors"; /** @hide */ public static final String FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE = "com.android.systemui.brightness_slider_focus_state"; /** @hide */ - public static final String FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX = "com.android.systemui.centralized_status_bar_height_fix"; + public static final String FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION = "com.android.systemui.check_lockscreen_gone_transition"; + /** @hide */ + public static final String FLAG_CLASSIC_FLAGS_MULTI_USER = "com.android.systemui.classic_flags_multi_user"; + /** @hide */ + public static final String FLAG_CLIPBOARD_IMAGE_TIMEOUT = "com.android.systemui.clipboard_image_timeout"; /** @hide */ public static final String FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN = "com.android.systemui.clipboard_noninteractive_on_lockscreen"; /** @hide */ - public static final String FLAG_CLOCK_REACTIVE_VARIANTS = "com.android.systemui.clock_reactive_variants"; + public static final String FLAG_CLIPBOARD_OVERLAY_MULTIUSER = "com.android.systemui.clipboard_overlay_multiuser"; + /** @hide */ + public static final String FLAG_CLIPBOARD_SHARED_TRANSITIONS = "com.android.systemui.clipboard_shared_transitions"; + /** @hide */ + public static final String FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE = "com.android.systemui.clipboard_use_description_mimetype"; + /** @hide */ + public static final String FLAG_CLOCK_FIDGET_ANIMATION = "com.android.systemui.clock_fidget_animation"; /** @hide */ public static final String FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN = "com.android.systemui.communal_bouncer_do_not_modify_plugin_open"; /** @hide */ + public static final String FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX = "com.android.systemui.communal_edit_widgets_activity_finish_fix"; + /** @hide */ public static final String FLAG_COMMUNAL_HUB = "com.android.systemui.communal_hub"; /** @hide */ - public static final String FLAG_COMPOSE_BOUNCER = "com.android.systemui.compose_bouncer"; + public static final String FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS = "com.android.systemui.communal_hub_use_thread_pool_for_widgets"; + /** @hide */ + public static final String FLAG_COMMUNAL_RESPONSIVE_GRID = "com.android.systemui.communal_responsive_grid"; + /** @hide */ + public static final String FLAG_COMMUNAL_SCENE_KTF_REFACTOR = "com.android.systemui.communal_scene_ktf_refactor"; /** @hide */ - public static final String FLAG_COMPOSE_LOCKSCREEN = "com.android.systemui.compose_lockscreen"; + public static final String FLAG_COMMUNAL_STANDALONE_SUPPORT = "com.android.systemui.communal_standalone_support"; + /** @hide */ + public static final String FLAG_COMMUNAL_TIMER_FLICKER_FIX = "com.android.systemui.communal_timer_flicker_fix"; + /** @hide */ + public static final String FLAG_COMMUNAL_WIDGET_RESIZING = "com.android.systemui.communal_widget_resizing"; + /** @hide */ + public static final String FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX = "com.android.systemui.communal_widget_trampoline_fix"; + /** @hide */ + public static final String FLAG_COMPOSE_BOUNCER = "com.android.systemui.compose_bouncer"; /** @hide */ public static final String FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH = "com.android.systemui.confine_notification_touch_to_view_width"; /** @hide */ - public static final String FLAG_CONSTRAINT_BP = "com.android.systemui.constraint_bp"; + public static final String FLAG_CONT_AUTH_PLUGIN = "com.android.systemui.cont_auth_plugin"; /** @hide */ public static final String FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX = "com.android.systemui.contextual_tips_assistant_dismiss_fix"; /** @hide */ @@ -39,25 +76,31 @@ public final class Flags { /** @hide */ public static final String FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER = "com.android.systemui.create_windowless_window_magnifier"; /** @hide */ - public static final String FLAG_DEDICATED_NOTIF_INFLATION_THREAD = "com.android.systemui.dedicated_notif_inflation_thread"; + public static final String FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL = "com.android.systemui.debug_live_updates_promote_all"; + /** @hide */ + public static final String FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB = "com.android.systemui.decouple_view_controller_in_animlib"; /** @hide */ public static final String FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON = "com.android.systemui.delay_show_magnification_button"; /** @hide */ - public static final String FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD = "com.android.systemui.delayed_wakelock_release_on_background_thread"; + public static final String FLAG_DESKTOP_EFFECTS_QS_TILE = "com.android.systemui.desktop_effects_qs_tile"; /** @hide */ public static final String FLAG_DEVICE_ENTRY_UDFPS_REFACTOR = "com.android.systemui.device_entry_udfps_refactor"; /** @hide */ + public static final String FLAG_DISABLE_BLURRED_SHADE_VISIBLE = "com.android.systemui.disable_blurred_shade_visible"; + /** @hide */ public static final String FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK = "com.android.systemui.disable_contextual_tips_frequency_check"; /** @hide */ public static final String FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK = "com.android.systemui.disable_contextual_tips_ios_switcher_check"; /** @hide */ - public static final String FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION = "com.android.systemui.dozeui_scheduling_alarms_background_execution"; + public static final String FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE = "com.android.systemui.disable_shade_trackpad_two_finger_swipe"; + /** @hide */ + public static final String FLAG_DOUBLE_TAP_TO_SLEEP = "com.android.systemui.double_tap_to_sleep"; /** @hide */ public static final String FLAG_DREAM_INPUT_SESSION_PILFER_ONCE = "com.android.systemui.dream_input_session_pilfer_once"; /** @hide */ public static final String FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING = "com.android.systemui.dream_overlay_bouncer_swipe_direction_filtering"; /** @hide */ - public static final String FLAG_DUAL_SHADE = "com.android.systemui.dual_shade"; + public static final String FLAG_DREAM_OVERLAY_UPDATED_FONT = "com.android.systemui.dream_overlay_updated_font"; /** @hide */ public static final String FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD = "com.android.systemui.edge_back_gesture_handler_thread"; /** @hide */ @@ -77,15 +120,29 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_LAYOUT_TRACING = "com.android.systemui.enable_layout_tracing"; /** @hide */ - public static final String FLAG_ENABLE_VIEW_CAPTURE_TRACING = "com.android.systemui.enable_view_capture_tracing"; + public static final String FLAG_ENABLE_UNDERLAY = "com.android.systemui.enable_underlay"; /** @hide */ - public static final String FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER = "com.android.systemui.enable_widget_picker_size_filter"; + public static final String FLAG_ENABLE_VIEW_CAPTURE_TRACING = "com.android.systemui.enable_view_capture_tracing"; /** @hide */ public static final String FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION = "com.android.systemui.enforce_brightness_base_user_restriction"; /** @hide */ public static final String FLAG_EXAMPLE_FLAG = "com.android.systemui.example_flag"; /** @hide */ - public static final String FLAG_FAST_UNLOCK_TRANSITION = "com.android.systemui.fast_unlock_transition"; + public static final String FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG = "com.android.systemui.expand_collapse_privacy_dialog"; + /** @hide */ + public static final String FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY = "com.android.systemui.expand_heads_up_on_inline_reply"; + /** @hide */ + public static final String FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN = "com.android.systemui.expanded_privacy_indicators_on_large_screen"; + /** @hide */ + public static final String FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY = "com.android.systemui.extended_apps_shortcut_category"; + /** @hide */ + public static final String FLAG_FACE_MESSAGE_DEFER_UPDATE = "com.android.systemui.face_message_defer_update"; + /** @hide */ + public static final String FLAG_FACE_SCANNING_ANIMATION_NPE_FIX = "com.android.systemui.face_scanning_animation_npe_fix"; + /** @hide */ + public static final String FLAG_FASTER_UNLOCK_TRANSITION = "com.android.systemui.faster_unlock_transition"; + /** @hide */ + public static final String FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS = "com.android.systemui.fetch_bookmarks_xml_keyboard_shortcuts"; /** @hide */ public static final String FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED = "com.android.systemui.fix_image_wallpaper_crash_surface_already_released"; /** @hide */ @@ -93,62 +150,132 @@ public final class Flags { /** @hide */ public static final String FLAG_FLOATING_MENU_ANIMATED_TUCK = "com.android.systemui.floating_menu_animated_tuck"; /** @hide */ + public static final String FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT = "com.android.systemui.floating_menu_display_cutout_support"; + /** @hide */ public static final String FLAG_FLOATING_MENU_DRAG_TO_EDIT = "com.android.systemui.floating_menu_drag_to_edit"; /** @hide */ public static final String FLAG_FLOATING_MENU_DRAG_TO_HIDE = "com.android.systemui.floating_menu_drag_to_hide"; /** @hide */ + public static final String FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON = "com.android.systemui.floating_menu_hearing_device_status_icon"; + /** @hide */ public static final String FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION = "com.android.systemui.floating_menu_ime_displacement_animation"; /** @hide */ public static final String FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER = "com.android.systemui.floating_menu_narrow_target_content_observer"; /** @hide */ + public static final String FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF = "com.android.systemui.floating_menu_notify_targets_changed_on_strict_diff"; + /** @hide */ public static final String FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG = "com.android.systemui.floating_menu_overlaps_nav_bars_flag"; /** @hide */ public static final String FLAG_FLOATING_MENU_RADII_ANIMATION = "com.android.systemui.floating_menu_radii_animation"; /** @hide */ - public static final String FLAG_GENERATED_PREVIEWS = "android.appwidget.flags.generated_previews"; - /** @hide */ public static final String FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED = "com.android.systemui.get_connected_device_name_unsynchronized"; /** @hide */ public static final String FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING = "com.android.systemui.glanceable_hub_allow_keyguard_when_dreaming"; /** @hide */ - public static final String FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE = "com.android.systemui.glanceable_hub_fullscreen_swipe"; + public static final String FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND = "com.android.systemui.glanceable_hub_blurred_background"; + /** @hide */ + public static final String FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE = "com.android.systemui.glanceable_hub_direct_edit_mode"; /** @hide */ - public static final String FLAG_GLANCEABLE_HUB_GESTURE_HANDLE = "com.android.systemui.glanceable_hub_gesture_handle"; + public static final String FLAG_GLANCEABLE_HUB_V2 = "com.android.systemui.glanceable_hub_v2"; /** @hide */ - public static final String FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON = "com.android.systemui.glanceable_hub_shortcut_button"; + public static final String FLAG_GLANCEABLE_HUB_V2_RESOURCES = "com.android.systemui.glanceable_hub_v2_resources"; /** @hide */ - public static final String FLAG_HAPTIC_BRIGHTNESS_SLIDER = "com.android.systemui.haptic_brightness_slider"; + public static final String FLAG_HAPTICS_FOR_COMPOSE_SLIDERS = "com.android.systemui.haptics_for_compose_sliders"; /** @hide */ - public static final String FLAG_HAPTIC_VOLUME_SLIDER = "com.android.systemui.haptic_volume_slider"; + public static final String FLAG_HARDWARE_COLOR_STYLES = "com.android.systemui.hardware_color_styles"; /** @hide */ public static final String FLAG_HEARING_AIDS_QS_TILE_DIALOG = "com.android.systemui.hearing_aids_qs_tile_dialog"; /** @hide */ public static final String FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS = "com.android.systemui.hearing_devices_dialog_related_tools"; /** @hide */ + public static final String FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE = "com.android.systemui.hide_ringer_button_in_single_volume_mode"; + /** @hide */ + public static final String FLAG_HOME_CONTROLS_DREAM_HSUM = "com.android.systemui.home_controls_dream_hsum"; + /** @hide */ + public static final String FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS = "com.android.systemui.hub_edit_mode_touch_adjustments"; + /** @hide */ + public static final String FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE = "com.android.systemui.hubmode_fullscreen_vertical_swipe"; + /** @hide */ + public static final String FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX = "com.android.systemui.hubmode_fullscreen_vertical_swipe_fix"; + /** @hide */ + public static final String FLAG_ICON_REFRESH_2025 = "com.android.systemui.icon_refresh_2025"; + /** @hide */ + public static final String FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF = "com.android.systemui.ignore_touches_next_to_notification_shelf"; + /** @hide */ + public static final String FLAG_INDICATION_TEXT_A11Y_FIX = "com.android.systemui.indication_text_a11y_fix"; + /** @hide */ public static final String FLAG_KEYBOARD_DOCKING_INDICATOR = "com.android.systemui.keyboard_docking_indicator"; /** @hide */ public static final String FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE = "com.android.systemui.keyboard_shortcut_helper_rewrite"; /** @hide */ - public static final String FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR = "com.android.systemui.keyguard_bottom_area_refactor"; + public static final String FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER = "com.android.systemui.keyboard_shortcut_helper_shortcut_customizer"; + /** @hide */ + public static final String FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION = "com.android.systemui.keyboard_touchpad_contextual_education"; + /** @hide */ + public static final String FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF = "com.android.systemui.keyguard_transition_force_finish_on_screen_off"; + /** @hide */ + public static final String FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS = "com.android.systemui.keyguard_wm_reorder_atms_calls"; /** @hide */ public static final String FLAG_KEYGUARD_WM_STATE_REFACTOR = "com.android.systemui.keyguard_wm_state_refactor"; /** @hide */ - public static final String FLAG_LIGHT_REVEAL_MIGRATION = "com.android.systemui.light_reveal_migration"; + public static final String FLAG_LOCKSCREEN_FONT = "com.android.systemui.lockscreen_font"; + /** @hide */ + public static final String FLAG_LOW_LIGHT_CLOCK_DREAM = "com.android.systemui.low_light_clock_dream"; + /** @hide */ + public static final String FLAG_MAGNETIC_NOTIFICATION_SWIPES = "com.android.systemui.magnetic_notification_swipes"; + /** @hide */ + public static final String FLAG_MEDIA_CONTROLS_A11Y_COLORS = "com.android.systemui.media_controls_a11y_colors"; + /** @hide */ + public static final String FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3 = "com.android.systemui.media_controls_button_media3"; + /** @hide */ + public static final String FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT = "com.android.systemui.media_controls_button_media3_placement"; + /** @hide */ + public static final String FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION = "com.android.systemui.media_controls_device_manager_background_execution"; + /** @hide */ + public static final String FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX = "com.android.systemui.media_controls_drawables_reuse_bugfix"; /** @hide */ public static final String FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX = "com.android.systemui.media_controls_lockscreen_shade_bug_fix"; /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_REFACTOR = "com.android.systemui.media_controls_refactor"; + public static final String FLAG_MEDIA_CONTROLS_UI_UPDATE = "com.android.systemui.media_controls_ui_update"; + /** @hide */ + public static final String FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND = "com.android.systemui.media_controls_umo_inflation_in_background"; /** @hide */ public static final String FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT = "com.android.systemui.media_controls_user_initiated_deleteintent"; /** @hide */ - public static final String FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT = "com.android.systemui.migrate_clocks_to_blueprint"; + public static final String FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER = "com.android.systemui.media_load_metadata_via_media_data_loader"; /** @hide */ - public static final String FLAG_NEW_AOD_TRANSITION = "com.android.systemui.new_aod_transition"; + public static final String FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION = "com.android.systemui.media_lockscreen_launch_animation"; + /** @hide */ + public static final String FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN = "com.android.systemui.media_projection_dialog_behind_lockscreen"; + /** @hide */ + public static final String FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT = "com.android.systemui.media_projection_grey_error_text"; + /** @hide */ + public static final String FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX = "com.android.systemui.media_projection_request_attribution_fix"; + /** @hide */ + public static final String FLAG_MODES_UI_DIALOG_PAGING = "com.android.systemui.modes_ui_dialog_paging"; + /** @hide */ + public static final String FLAG_MOVE_TRANSITION_ANIMATION_LAYER = "com.android.systemui.move_transition_animation_layer"; + /** @hide */ + public static final String FLAG_MSDL_FEEDBACK = "com.android.systemui.msdl_feedback"; /** @hide */ - public static final String FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL = "com.android.systemui.new_touchpad_gestures_tutorial"; + public static final String FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT = "com.android.systemui.multiuser_wifi_picker_tracker_support"; + /** @hide */ + public static final String FLAG_NEW_AOD_TRANSITION = "com.android.systemui.new_aod_transition"; /** @hide */ public static final String FLAG_NEW_VOLUME_PANEL = "com.android.systemui.new_volume_panel"; /** @hide */ + public static final String FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING = "com.android.systemui.non_touchscreen_devices_bypass_falsing"; + /** @hide */ + public static final String FLAG_NOTES_ROLE_QS_TILE = "com.android.systemui.notes_role_qs_tile"; + /** @hide */ + public static final String FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS = "com.android.systemui.notification_add_x_on_hover_to_dismiss"; + /** @hide */ + public static final String FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION = "com.android.systemui.notification_ambient_suppression_after_inflation"; + /** @hide */ + public static final String FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT = "com.android.systemui.notification_animated_actions_treatment"; + /** @hide */ + public static final String FLAG_NOTIFICATION_APPEAR_NONLINEAR = "com.android.systemui.notification_appear_nonlinear"; + /** @hide */ public static final String FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION = "com.android.systemui.notification_async_group_header_inflation"; /** @hide */ public static final String FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = "com.android.systemui.notification_async_hybrid_view_inflation"; @@ -159,57 +286,81 @@ public final class Flags { /** @hide */ public static final String FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION = "com.android.systemui.notification_background_tint_optimization"; /** @hide */ + public static final String FLAG_NOTIFICATION_BUNDLE_UI = "com.android.systemui.notification_bundle_ui"; + /** @hide */ public static final String FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER = "com.android.systemui.notification_color_update_logger"; /** @hide */ public static final String FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION = "com.android.systemui.notification_content_alpha_optimization"; /** @hide */ public static final String FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION = "com.android.systemui.notification_footer_background_tint_optimization"; /** @hide */ - public static final String FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION = "com.android.systemui.notification_media_manager_background_execution"; - /** @hide */ - public static final String FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE = "com.android.systemui.notification_minimalism_prototype"; - /** @hide */ public static final String FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX = "com.android.systemui.notification_over_expansion_clipping_fix"; /** @hide */ - public static final String FLAG_NOTIFICATION_PULSING_FIX = "com.android.systemui.notification_pulsing_fix"; + public static final String FLAG_NOTIFICATION_REENTRANT_DISMISS = "com.android.systemui.notification_reentrant_dismiss"; + /** @hide */ + public static final String FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED = "com.android.systemui.notification_row_accessibility_expanded"; /** @hide */ public static final String FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR = "com.android.systemui.notification_row_content_binder_refactor"; /** @hide */ + public static final String FLAG_NOTIFICATION_ROW_TRANSPARENCY = "com.android.systemui.notification_row_transparency"; + /** @hide */ public static final String FLAG_NOTIFICATION_ROW_USER_CONTEXT = "com.android.systemui.notification_row_user_context"; /** @hide */ + public static final String FLAG_NOTIFICATION_SHADE_BLUR = "com.android.systemui.notification_shade_blur"; + /** @hide */ + public static final String FLAG_NOTIFICATION_SHADE_UI_THREAD = "com.android.systemui.notification_shade_ui_thread"; + /** @hide */ + public static final String FLAG_NOTIFICATION_SKIP_SILENT_UPDATES = "com.android.systemui.notification_skip_silent_updates"; + /** @hide */ + public static final String FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX = "com.android.systemui.notification_transparent_header_fix"; + /** @hide */ public static final String FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2 = "com.android.systemui.notification_view_flipper_pausing_v2"; /** @hide */ public static final String FLAG_NOTIFICATIONS_BACKGROUND_ICONS = "com.android.systemui.notifications_background_icons"; /** @hide */ - public static final String FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR = "com.android.systemui.notifications_footer_view_refactor"; - /** @hide */ - public static final String FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR = "com.android.systemui.notifications_heads_up_refactor"; + public static final String FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX = "com.android.systemui.notifications_footer_visibility_fix"; /** @hide */ public static final String FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH = "com.android.systemui.notifications_hide_on_display_switch"; /** @hide */ + public static final String FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES = "com.android.systemui.notifications_hun_shared_animation_values"; + /** @hide */ public static final String FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR = "com.android.systemui.notifications_icon_container_refactor"; /** @hide */ - public static final String FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION = "com.android.systemui.notifications_improved_hun_animation"; + public static final String FLAG_NOTIFICATIONS_LAUNCH_RADIUS = "com.android.systemui.notifications_launch_radius"; /** @hide */ public static final String FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR = "com.android.systemui.notifications_live_data_store_refactor"; /** @hide */ + public static final String FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE = "com.android.systemui.notifications_pinned_hun_in_shade"; + /** @hide */ + public static final String FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW = "com.android.systemui.notifications_redesign_footer_view"; + /** @hide */ + public static final String FLAG_NOTIFICATIONS_REDESIGN_GUTS = "com.android.systemui.notifications_redesign_guts"; + /** @hide */ + public static final String FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND = "com.android.systemui.notify_password_text_view_user_activity_in_background"; + /** @hide */ public static final String FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND = "com.android.systemui.notify_power_manager_user_activity_background"; /** @hide */ - public static final String FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE = "com.android.systemui.pin_input_field_styled_focus_state"; + public static final String FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE = "com.android.systemui.only_show_media_stream_slider_in_single_volume_mode"; /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER = "com.android.systemui.predictive_back_animate_bouncer"; + public static final String FLAG_OUTPUT_SWITCHER_REDESIGN = "com.android.systemui.output_switcher_redesign"; /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS = "com.android.systemui.predictive_back_animate_dialogs"; + public static final String FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION = "com.android.systemui.override_suppress_overlay_condition"; + /** @hide */ + public static final String FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING = "com.android.systemui.permission_helper_inline_ui_rich_ongoing"; + /** @hide */ + public static final String FLAG_PERMISSION_HELPER_UI_RICH_ONGOING = "com.android.systemui.permission_helper_ui_rich_ongoing"; + /** @hide */ + public static final String FLAG_PHYSICAL_NOTIFICATION_MOVEMENT = "com.android.systemui.physical_notification_movement"; + /** @hide */ + public static final String FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE = "com.android.systemui.pin_input_field_styled_focus_state"; /** @hide */ public static final String FLAG_PREDICTIVE_BACK_ANIMATE_SHADE = "com.android.systemui.predictive_back_animate_shade"; /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_SYSUI = "com.android.systemui.predictive_back_sysui"; + public static final String FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION = "com.android.systemui.predictive_back_delay_wm_transition"; /** @hide */ public static final String FLAG_PRIORITY_PEOPLE_SECTION = "com.android.systemui.priority_people_section"; /** @hide */ - public static final String FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX = "com.android.systemui.privacy_dot_unfold_wrong_corner_fix"; - /** @hide */ - public static final String FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX = "com.android.systemui.pss_app_selector_abrupt_exit_fix"; + public static final String FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY = "com.android.systemui.promote_notifications_automatically"; /** @hide */ public static final String FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN = "com.android.systemui.pss_app_selector_recents_split_screen"; /** @hide */ @@ -217,32 +368,42 @@ public final class Flags { /** @hide */ public static final String FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX = "com.android.systemui.qs_custom_tile_click_guaranteed_bug_fix"; /** @hide */ - public static final String FLAG_QS_NEW_PIPELINE = "com.android.systemui.qs_new_pipeline"; - /** @hide */ public static final String FLAG_QS_NEW_TILES = "com.android.systemui.qs_new_tiles"; /** @hide */ public static final String FLAG_QS_NEW_TILES_FUTURE = "com.android.systemui.qs_new_tiles_future"; /** @hide */ + public static final String FLAG_QS_QUICK_REBIND_ACTIVE_TILES = "com.android.systemui.qs_quick_rebind_active_tiles"; + /** @hide */ + public static final String FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD = "com.android.systemui.qs_register_setting_observer_on_bg_thread"; + /** @hide */ + public static final String FLAG_QS_TILE_DETAILED_VIEW = "com.android.systemui.qs_tile_detailed_view"; + /** @hide */ public static final String FLAG_QS_TILE_FOCUS_STATE = "com.android.systemui.qs_tile_focus_state"; /** @hide */ public static final String FLAG_QS_UI_REFACTOR = "com.android.systemui.qs_ui_refactor"; /** @hide */ - public static final String FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS = "com.android.systemui.quick_settings_visual_haptics_longpress"; + public static final String FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT = "com.android.systemui.qs_ui_refactor_compose_fragment"; /** @hide */ public static final String FLAG_RECORD_ISSUE_QS_TILE = "com.android.systemui.record_issue_qs_tile"; /** @hide */ + public static final String FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE = "com.android.systemui.redesign_magnification_window_size"; + /** @hide */ public static final String FLAG_REFACTOR_GET_CURRENT_USER = "com.android.systemui.refactor_get_current_user"; /** @hide */ public static final String FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE = "com.android.systemui.register_battery_controller_receivers_in_corestartable"; /** @hide */ + public static final String FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC = "com.android.systemui.register_content_observers_async"; + /** @hide */ public static final String FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND = "com.android.systemui.register_new_wallet_card_in_background"; /** @hide */ public static final String FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND = "com.android.systemui.register_wallpaper_notifier_background"; /** @hide */ - public static final String FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND = "com.android.systemui.register_zen_mode_content_observer_background"; + public static final String FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY = "com.android.systemui.relock_with_power_button_immediately"; /** @hide */ public static final String FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH = "com.android.systemui.remove_dream_overlay_hide_on_touch"; /** @hide */ + public static final String FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL = "com.android.systemui.remove_update_listener_in_qs_icon_view_impl"; + /** @hide */ public static final String FLAG_REST_TO_UNLOCK = "com.android.systemui.rest_to_unlock"; /** @hide */ public static final String FLAG_RESTART_DREAM_ON_UNOCCLUDE = "com.android.systemui.restart_dream_on_unocclude"; @@ -259,18 +420,46 @@ public final class Flags { /** @hide */ public static final String FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS = "com.android.systemui.screenshot_action_dismiss_system_windows"; /** @hide */ - public static final String FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX = "com.android.systemui.screenshot_private_profile_accessibility_announcement_fix"; + public static final String FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE = "com.android.systemui.screenshot_multidisplay_focus_change"; /** @hide */ - public static final String FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX = "com.android.systemui.screenshot_private_profile_behavior_fix"; + public static final String FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE = "com.android.systemui.screenshot_policy_split_and_desktop_mode"; /** @hide */ public static final String FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX = "com.android.systemui.screenshot_scroll_crop_view_crash_fix"; /** @hide */ - public static final String FLAG_SCREENSHOT_SHELF_UI2 = "com.android.systemui.screenshot_shelf_ui2"; + public static final String FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR = "com.android.systemui.screenshot_ui_controller_refactor"; + /** @hide */ + public static final String FLAG_SECONDARY_USER_WIDGET_HOST = "com.android.systemui.secondary_user_widget_host"; + /** @hide */ + public static final String FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD = "com.android.systemui.settings_ext_register_content_observer_on_bg_thread"; + /** @hide */ + public static final String FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS = "com.android.systemui.shade_expands_on_status_bar_long_press"; /** @hide */ - public static final String FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX = "com.android.systemui.shade_collapse_activity_launch_fix"; + public static final String FLAG_SHADE_HEADER_FONT_UPDATE = "com.android.systemui.shade_header_font_update"; + /** @hide */ + public static final String FLAG_SHADE_LAUNCH_ACCESSIBILITY = "com.android.systemui.shade_launch_accessibility"; + /** @hide */ + public static final String FLAG_SHADE_WINDOW_GOES_AROUND = "com.android.systemui.shade_window_goes_around"; /** @hide */ public static final String FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR = "com.android.systemui.shaderlib_loading_effect_refactor"; /** @hide */ + public static final String FLAG_SHORTCUT_HELPER_KEY_GLYPH = "com.android.systemui.shortcut_helper_key_glyph"; + /** @hide */ + public static final String FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL = "com.android.systemui.show_audio_sharing_slider_in_volume_panel"; + /** @hide */ + public static final String FLAG_SHOW_CLIPBOARD_INDICATION = "com.android.systemui.show_clipboard_indication"; + /** @hide */ + public static final String FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR = "com.android.systemui.show_locked_by_your_watch_keyguard_indicator"; + /** @hide */ + public static final String FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS = "com.android.systemui.show_toast_when_app_control_brightness"; + /** @hide */ + public static final String FLAG_SIM_PIN_BOUNCER_RESET = "com.android.systemui.sim_pin_bouncer_reset"; + /** @hide */ + public static final String FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART = "com.android.systemui.sim_pin_race_condition_on_restart"; + /** @hide */ + public static final String FLAG_SIM_PIN_USE_SLOT_ID = "com.android.systemui.sim_pin_use_slot_id"; + /** @hide */ + public static final String FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION = "com.android.systemui.skip_hide_sensitive_notif_animation"; + /** @hide */ public static final String FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND = "com.android.systemui.slice_broadcast_relay_in_background"; /** @hide */ public static final String FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND = "com.android.systemui.slice_manager_binder_call_background"; @@ -279,642 +468,2062 @@ public final class Flags { /** @hide */ public static final String FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM = "com.android.systemui.smartspace_relocate_to_bottom"; /** @hide */ - public static final String FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING = "com.android.systemui.smartspace_remoteviews_rendering"; + public static final String FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX = "com.android.systemui.smartspace_remoteviews_rendering_fix"; + /** @hide */ + public static final String FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX = "com.android.systemui.smartspace_swipe_event_logging_fix"; + /** @hide */ + public static final String FLAG_SMARTSPACE_VIEWPAGER2 = "com.android.systemui.smartspace_viewpager2"; + /** @hide */ + public static final String FLAG_SOUNDDOSE_CUSTOMIZATION = "com.android.systemui.sounddose_customization"; + /** @hide */ + public static final String FLAG_SPATIAL_MODEL_APP_PUSHBACK = "com.android.systemui.spatial_model_app_pushback"; + /** @hide */ + public static final String FLAG_STABILIZE_HEADS_UP_GROUP_V2 = "com.android.systemui.stabilize_heads_up_group_v2"; + /** @hide */ + public static final String FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS = "com.android.systemui.status_bar_always_check_underlying_networks"; + /** @hide */ + public static final String FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP = "com.android.systemui.status_bar_auto_start_screen_record_chip"; + /** @hide */ + public static final String FLAG_STATUS_BAR_CHIPS_MODERNIZATION = "com.android.systemui.status_bar_chips_modernization"; + /** @hide */ + public static final String FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS = "com.android.systemui.status_bar_chips_return_animations"; + /** @hide */ + public static final String FLAG_STATUS_BAR_FONT_UPDATES = "com.android.systemui.status_bar_font_updates"; + /** @hide */ + public static final String FLAG_STATUS_BAR_MOBILE_ICON_KAIROS = "com.android.systemui.status_bar_mobile_icon_kairos"; /** @hide */ public static final String FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX = "com.android.systemui.status_bar_monochrome_icons_fix"; /** @hide */ - public static final String FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS = "com.android.systemui.status_bar_screen_sharing_chips"; + public static final String FLAG_STATUS_BAR_NO_HUN_BEHAVIOR = "com.android.systemui.status_bar_no_hun_behavior"; + /** @hide */ + public static final String FLAG_STATUS_BAR_POPUP_CHIPS = "com.android.systemui.status_bar_popup_chips"; + /** @hide */ + public static final String FLAG_STATUS_BAR_ROOT_MODERNIZATION = "com.android.systemui.status_bar_root_modernization"; + /** @hide */ + public static final String FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP = "com.android.systemui.status_bar_show_audio_only_projection_chip"; + /** @hide */ + public static final String FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR = "com.android.systemui.status_bar_signal_policy_refactor"; + /** @hide */ + public static final String FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET = "com.android.systemui.status_bar_signal_policy_refactor_ethernet"; /** @hide */ public static final String FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS = "com.android.systemui.status_bar_static_inout_indicators"; /** @hide */ + public static final String FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT = "com.android.systemui.status_bar_stop_updating_window_height"; + /** @hide */ + public static final String FLAG_STATUS_BAR_SWIPE_OVER_CHIP = "com.android.systemui.status_bar_swipe_over_chip"; + /** @hide */ + public static final String FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN = "com.android.systemui.status_bar_switch_to_spn_from_data_spn"; + /** @hide */ + public static final String FLAG_STATUS_BAR_UI_THREAD = "com.android.systemui.status_bar_ui_thread"; + /** @hide */ + public static final String FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH = "com.android.systemui.status_bar_window_no_custom_touch"; + /** @hide */ + public static final String FLAG_STOPPABLE_FGS_SYSTEM_APP = "com.android.systemui.stoppable_fgs_system_app"; + /** @hide */ public static final String FLAG_SWITCH_USER_ON_BG = "com.android.systemui.switch_user_on_bg"; /** @hide */ public static final String FLAG_SYSUI_TEAMFOOD = "com.android.systemui.sysui_teamfood"; /** @hide */ public static final String FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION = "com.android.systemui.theme_overlay_controller_wakefulness_deprecation"; /** @hide */ + public static final String FLAG_TRANSITION_RACE_CONDITION = "com.android.systemui.transition_race_condition"; + /** @hide */ public static final String FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX = "com.android.systemui.translucent_occluding_activity_fix"; /** @hide */ - public static final String FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX = "com.android.systemui.truncated_status_bar_icons_fix"; + public static final String FLAG_TV_GLOBAL_ACTIONS_FOCUS = "com.android.systemui.tv_global_actions_focus"; /** @hide */ public static final String FLAG_UDFPS_VIEW_PERFORMANCE = "com.android.systemui.udfps_view_performance"; /** @hide */ public static final String FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS = "com.android.systemui.unfold_animation_background_progress"; /** @hide */ + public static final String FLAG_UNFOLD_LATENCY_TRACKING_FIX = "com.android.systemui.unfold_latency_tracking_fix"; + /** @hide */ + public static final String FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED = "com.android.systemui.update_corner_radius_on_display_changed"; + /** @hide */ public static final String FLAG_UPDATE_USER_SWITCHER_BACKGROUND = "com.android.systemui.update_user_switcher_background"; /** @hide */ - public static final String FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI = "com.android.systemui.validate_keyboard_shortcut_helper_icon_uri"; + public static final String FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY = "com.android.systemui.update_window_magnifier_bottom_boundary"; + /** @hide */ + public static final String FLAG_USE_AAD_PROX_SENSOR = "com.android.systemui.use_aad_prox_sensor"; + /** @hide */ + public static final String FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER = "com.android.systemui.use_notif_inflation_thread_for_footer"; + /** @hide */ + public static final String FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW = "com.android.systemui.use_notif_inflation_thread_for_row"; + /** @hide */ + public static final String FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED = "com.android.systemui.use_transitions_for_keyguard_occluded"; + /** @hide */ + public static final String FLAG_USE_VOLUME_CONTROLLER = "com.android.systemui.use_volume_controller"; + /** @hide */ + public static final String FLAG_USER_AWARE_SETTINGS_REPOSITORIES = "com.android.systemui.user_aware_settings_repositories"; + /** @hide */ + public static final String FLAG_USER_ENCRYPTED_SOURCE = "com.android.systemui.user_encrypted_source"; + /** @hide */ + public static final String FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION = "com.android.systemui.user_switcher_add_sign_out_option"; /** @hide */ public static final String FLAG_VISUAL_INTERRUPTIONS_REFACTOR = "com.android.systemui.visual_interruptions_refactor"; - + /** @hide */ + public static final String FLAG_VOLUME_REDESIGN = "com.android.systemui.volume_redesign"; + + + public static boolean activityTransitionUseLargestWindow() { + return FEATURE_FLAGS.activityTransitionUseLargestWindow(); } - + + + + public static boolean addBlackBackgroundForWindowMagnifier() { + + return FEATURE_FLAGS.addBlackBackgroundForWindowMagnifier(); + } + + + + public static boolean alwaysComposeQsUiFragment() { + + return FEATURE_FLAGS.alwaysComposeQsUiFragment(); + } + + + public static boolean ambientTouchMonitorListenToDisplayChanges() { + return FEATURE_FLAGS.ambientTouchMonitorListenToDisplayChanges(); } - + + + public static boolean appClipsBacklinks() { + return FEATURE_FLAGS.appClipsBacklinks(); } - + + + + public static boolean appShortcutRemovalFix() { + + return FEATURE_FLAGS.appShortcutRemovalFix(); + } + + + + public static boolean avalancheReplaceHunWhenCritical() { + + return FEATURE_FLAGS.avalancheReplaceHunWhenCritical(); + } + + + public static boolean bindKeyguardMediaVisibility() { + return FEATURE_FLAGS.bindKeyguardMediaVisibility(); } - - public static boolean bpTalkback() { - return FEATURE_FLAGS.bpTalkback(); + + + + public static boolean bouncerUiRevamp() { + + return FEATURE_FLAGS.bouncerUiRevamp(); + } + + + + public static boolean bouncerUiRevamp2() { + + return FEATURE_FLAGS.bouncerUiRevamp2(); + } + + + + public static boolean bpColors() { + + return FEATURE_FLAGS.bpColors(); } - + + + public static boolean brightnessSliderFocusState() { + return FEATURE_FLAGS.brightnessSliderFocusState(); } - - public static boolean centralizedStatusBarHeightFix() { - return FEATURE_FLAGS.centralizedStatusBarHeightFix(); + + + + public static boolean checkLockscreenGoneTransition() { + + return FEATURE_FLAGS.checkLockscreenGoneTransition(); + } + + + + public static boolean classicFlagsMultiUser() { + + return FEATURE_FLAGS.classicFlagsMultiUser(); + } + + + + public static boolean clipboardImageTimeout() { + + return FEATURE_FLAGS.clipboardImageTimeout(); } - + + + public static boolean clipboardNoninteractiveOnLockscreen() { + return FEATURE_FLAGS.clipboardNoninteractiveOnLockscreen(); } - - public static boolean clockReactiveVariants() { - return FEATURE_FLAGS.clockReactiveVariants(); + + + + public static boolean clipboardOverlayMultiuser() { + + return FEATURE_FLAGS.clipboardOverlayMultiuser(); + } + + + + public static boolean clipboardSharedTransitions() { + + return FEATURE_FLAGS.clipboardSharedTransitions(); + } + + + + public static boolean clipboardUseDescriptionMimetype() { + + return FEATURE_FLAGS.clipboardUseDescriptionMimetype(); + } + + + + public static boolean clockFidgetAnimation() { + + return FEATURE_FLAGS.clockFidgetAnimation(); } - + + + public static boolean communalBouncerDoNotModifyPluginOpen() { + return FEATURE_FLAGS.communalBouncerDoNotModifyPluginOpen(); } - + + + + public static boolean communalEditWidgetsActivityFinishFix() { + + return FEATURE_FLAGS.communalEditWidgetsActivityFinishFix(); + } + + + public static boolean communalHub() { + return FEATURE_FLAGS.communalHub(); } - - public static boolean composeBouncer() { - return FEATURE_FLAGS.composeBouncer(); + + + + public static boolean communalHubUseThreadPoolForWidgets() { + + return FEATURE_FLAGS.communalHubUseThreadPoolForWidgets(); } - - public static boolean composeLockscreen() { - return FEATURE_FLAGS.composeLockscreen(); + + + + public static boolean communalResponsiveGrid() { + + return FEATURE_FLAGS.communalResponsiveGrid(); } - - public static boolean confineNotificationTouchToViewWidth() { - return FEATURE_FLAGS.confineNotificationTouchToViewWidth(); + + + + public static boolean communalSceneKtfRefactor() { + + return FEATURE_FLAGS.communalSceneKtfRefactor(); } - - public static boolean constraintBp() { - return FEATURE_FLAGS.constraintBp(); + + + + public static boolean communalStandaloneSupport() { + + return FEATURE_FLAGS.communalStandaloneSupport(); } - - public static boolean contextualTipsAssistantDismissFix() { - return FEATURE_FLAGS.contextualTipsAssistantDismissFix(); + + + + public static boolean communalTimerFlickerFix() { + + return FEATURE_FLAGS.communalTimerFlickerFix(); } - - public static boolean coroutineTracing() { - return FEATURE_FLAGS.coroutineTracing(); + + + + public static boolean communalWidgetResizing() { + + return FEATURE_FLAGS.communalWidgetResizing(); } - + + + + public static boolean communalWidgetTrampolineFix() { + + return FEATURE_FLAGS.communalWidgetTrampolineFix(); + } + + + + public static boolean composeBouncer() { + + return FEATURE_FLAGS.composeBouncer(); + } + + + + public static boolean confineNotificationTouchToViewWidth() { + + return FEATURE_FLAGS.confineNotificationTouchToViewWidth(); + } + + + + public static boolean contAuthPlugin() { + + return FEATURE_FLAGS.contAuthPlugin(); + } + + + + public static boolean contextualTipsAssistantDismissFix() { + + return FEATURE_FLAGS.contextualTipsAssistantDismissFix(); + } + + + + public static boolean coroutineTracing() { + + return FEATURE_FLAGS.coroutineTracing(); + } + + + public static boolean createWindowlessWindowMagnifier() { + return FEATURE_FLAGS.createWindowlessWindowMagnifier(); } - - public static boolean dedicatedNotifInflationThread() { - return FEATURE_FLAGS.dedicatedNotifInflationThread(); + + + + public static boolean debugLiveUpdatesPromoteAll() { + + return FEATURE_FLAGS.debugLiveUpdatesPromoteAll(); + } + + + + public static boolean decoupleViewControllerInAnimlib() { + + return FEATURE_FLAGS.decoupleViewControllerInAnimlib(); } - + + + public static boolean delayShowMagnificationButton() { + return FEATURE_FLAGS.delayShowMagnificationButton(); } - - public static boolean delayedWakelockReleaseOnBackgroundThread() { - return FEATURE_FLAGS.delayedWakelockReleaseOnBackgroundThread(); + + + + public static boolean desktopEffectsQsTile() { + + return FEATURE_FLAGS.desktopEffectsQsTile(); } - + + + public static boolean deviceEntryUdfpsRefactor() { + return FEATURE_FLAGS.deviceEntryUdfpsRefactor(); } - + + + + public static boolean disableBlurredShadeVisible() { + + return FEATURE_FLAGS.disableBlurredShadeVisible(); + } + + + public static boolean disableContextualTipsFrequencyCheck() { + return FEATURE_FLAGS.disableContextualTipsFrequencyCheck(); } - + + + public static boolean disableContextualTipsIosSwitcherCheck() { + return FEATURE_FLAGS.disableContextualTipsIosSwitcherCheck(); } - - public static boolean dozeuiSchedulingAlarmsBackgroundExecution() { - return FEATURE_FLAGS.dozeuiSchedulingAlarmsBackgroundExecution(); + + + + public static boolean disableShadeTrackpadTwoFingerSwipe() { + + return FEATURE_FLAGS.disableShadeTrackpadTwoFingerSwipe(); + } + + + + public static boolean doubleTapToSleep() { + + return FEATURE_FLAGS.doubleTapToSleep(); } - + + + public static boolean dreamInputSessionPilferOnce() { + return FEATURE_FLAGS.dreamInputSessionPilferOnce(); } - + + + public static boolean dreamOverlayBouncerSwipeDirectionFiltering() { + return FEATURE_FLAGS.dreamOverlayBouncerSwipeDirectionFiltering(); } - - public static boolean dualShade() { - return FEATURE_FLAGS.dualShade(); + + + + public static boolean dreamOverlayUpdatedFont() { + + return FEATURE_FLAGS.dreamOverlayUpdatedFont(); } - + + + public static boolean edgeBackGestureHandlerThread() { + return FEATURE_FLAGS.edgeBackGestureHandlerThread(); } - + + + public static boolean edgebackGestureHandlerGetRunningTasksBackground() { + return FEATURE_FLAGS.edgebackGestureHandlerGetRunningTasksBackground(); } - + + + public static boolean enableBackgroundKeyguardOndrawnCallback() { + return FEATURE_FLAGS.enableBackgroundKeyguardOndrawnCallback(); } - + + + public static boolean enableContextualTipForMuteVolume() { + return FEATURE_FLAGS.enableContextualTipForMuteVolume(); } - + + + public static boolean enableContextualTipForPowerOff() { + return FEATURE_FLAGS.enableContextualTipForPowerOff(); } - + + + public static boolean enableContextualTipForTakeScreenshot() { + return FEATURE_FLAGS.enableContextualTipForTakeScreenshot(); } - + + + public static boolean enableContextualTips() { + return FEATURE_FLAGS.enableContextualTips(); } - + + + public static boolean enableEfficientDisplayRepository() { + return FEATURE_FLAGS.enableEfficientDisplayRepository(); } - + + + public static boolean enableLayoutTracing() { + return FEATURE_FLAGS.enableLayoutTracing(); } - + + + + public static boolean enableUnderlay() { + + return FEATURE_FLAGS.enableUnderlay(); + } + + + public static boolean enableViewCaptureTracing() { + return FEATURE_FLAGS.enableViewCaptureTracing(); } - - public static boolean enableWidgetPickerSizeFilter() { - return FEATURE_FLAGS.enableWidgetPickerSizeFilter(); - } - + + + public static boolean enforceBrightnessBaseUserRestriction() { + return FEATURE_FLAGS.enforceBrightnessBaseUserRestriction(); } - + + + public static boolean exampleFlag() { + return FEATURE_FLAGS.exampleFlag(); } - - public static boolean fastUnlockTransition() { - return FEATURE_FLAGS.fastUnlockTransition(); + + + + public static boolean expandCollapsePrivacyDialog() { + + return FEATURE_FLAGS.expandCollapsePrivacyDialog(); + } + + + + public static boolean expandHeadsUpOnInlineReply() { + + return FEATURE_FLAGS.expandHeadsUpOnInlineReply(); + } + + + + public static boolean expandedPrivacyIndicatorsOnLargeScreen() { + + return FEATURE_FLAGS.expandedPrivacyIndicatorsOnLargeScreen(); + } + + + + public static boolean extendedAppsShortcutCategory() { + + return FEATURE_FLAGS.extendedAppsShortcutCategory(); + } + + + + public static boolean faceMessageDeferUpdate() { + + return FEATURE_FLAGS.faceMessageDeferUpdate(); + } + + + + public static boolean faceScanningAnimationNpeFix() { + + return FEATURE_FLAGS.faceScanningAnimationNpeFix(); + } + + + + public static boolean fasterUnlockTransition() { + + return FEATURE_FLAGS.fasterUnlockTransition(); + } + + + + public static boolean fetchBookmarksXmlKeyboardShortcuts() { + + return FEATURE_FLAGS.fetchBookmarksXmlKeyboardShortcuts(); } - + + + public static boolean fixImageWallpaperCrashSurfaceAlreadyReleased() { + return FEATURE_FLAGS.fixImageWallpaperCrashSurfaceAlreadyReleased(); } - + + + public static boolean fixScreenshotActionDismissSystemWindows() { + return FEATURE_FLAGS.fixScreenshotActionDismissSystemWindows(); } - + + + public static boolean floatingMenuAnimatedTuck() { + return FEATURE_FLAGS.floatingMenuAnimatedTuck(); } - + + + + public static boolean floatingMenuDisplayCutoutSupport() { + + return FEATURE_FLAGS.floatingMenuDisplayCutoutSupport(); + } + + + public static boolean floatingMenuDragToEdit() { + return FEATURE_FLAGS.floatingMenuDragToEdit(); } - + + + public static boolean floatingMenuDragToHide() { + return FEATURE_FLAGS.floatingMenuDragToHide(); } - + + + + public static boolean floatingMenuHearingDeviceStatusIcon() { + + return FEATURE_FLAGS.floatingMenuHearingDeviceStatusIcon(); + } + + + public static boolean floatingMenuImeDisplacementAnimation() { + return FEATURE_FLAGS.floatingMenuImeDisplacementAnimation(); } - + + + public static boolean floatingMenuNarrowTargetContentObserver() { + return FEATURE_FLAGS.floatingMenuNarrowTargetContentObserver(); } - + + + + public static boolean floatingMenuNotifyTargetsChangedOnStrictDiff() { + + return FEATURE_FLAGS.floatingMenuNotifyTargetsChangedOnStrictDiff(); + } + + + public static boolean floatingMenuOverlapsNavBarsFlag() { + return FEATURE_FLAGS.floatingMenuOverlapsNavBarsFlag(); } - + + + public static boolean floatingMenuRadiiAnimation() { + return FEATURE_FLAGS.floatingMenuRadiiAnimation(); } - public static boolean generatedPreviews() { - return FEATURE_FLAGS.generatedPreviews(); - } - + + public static boolean getConnectedDeviceNameUnsynchronized() { + return FEATURE_FLAGS.getConnectedDeviceNameUnsynchronized(); } - + + + public static boolean glanceableHubAllowKeyguardWhenDreaming() { + return FEATURE_FLAGS.glanceableHubAllowKeyguardWhenDreaming(); } - - public static boolean glanceableHubFullscreenSwipe() { - return FEATURE_FLAGS.glanceableHubFullscreenSwipe(); + + + + public static boolean glanceableHubBlurredBackground() { + + return FEATURE_FLAGS.glanceableHubBlurredBackground(); + } + + + + public static boolean glanceableHubDirectEditMode() { + + return FEATURE_FLAGS.glanceableHubDirectEditMode(); } - - public static boolean glanceableHubGestureHandle() { - return FEATURE_FLAGS.glanceableHubGestureHandle(); + + + + public static boolean glanceableHubV2() { + + return FEATURE_FLAGS.glanceableHubV2(); } - - public static boolean glanceableHubShortcutButton() { - return FEATURE_FLAGS.glanceableHubShortcutButton(); + + + + public static boolean glanceableHubV2Resources() { + + return FEATURE_FLAGS.glanceableHubV2Resources(); } - - public static boolean hapticBrightnessSlider() { - return FEATURE_FLAGS.hapticBrightnessSlider(); + + + + public static boolean hapticsForComposeSliders() { + + return FEATURE_FLAGS.hapticsForComposeSliders(); } - - public static boolean hapticVolumeSlider() { - return FEATURE_FLAGS.hapticVolumeSlider(); + + + + public static boolean hardwareColorStyles() { + + return FEATURE_FLAGS.hardwareColorStyles(); } - + + + public static boolean hearingAidsQsTileDialog() { + return FEATURE_FLAGS.hearingAidsQsTileDialog(); } - + + + public static boolean hearingDevicesDialogRelatedTools() { + return FEATURE_FLAGS.hearingDevicesDialogRelatedTools(); } - - public static boolean keyboardDockingIndicator() { - return FEATURE_FLAGS.keyboardDockingIndicator(); - } - - public static boolean keyboardShortcutHelperRewrite() { - return FEATURE_FLAGS.keyboardShortcutHelperRewrite(); + + + + public static boolean hideRingerButtonInSingleVolumeMode() { + + return FEATURE_FLAGS.hideRingerButtonInSingleVolumeMode(); } - - public static boolean keyguardBottomAreaRefactor() { - return FEATURE_FLAGS.keyguardBottomAreaRefactor(); + + + + public static boolean homeControlsDreamHsum() { + + return FEATURE_FLAGS.homeControlsDreamHsum(); } - - public static boolean keyguardWmStateRefactor() { - return FEATURE_FLAGS.keyguardWmStateRefactor(); + + + + public static boolean hubEditModeTouchAdjustments() { + + return FEATURE_FLAGS.hubEditModeTouchAdjustments(); } - - public static boolean lightRevealMigration() { - return FEATURE_FLAGS.lightRevealMigration(); + + + + public static boolean hubmodeFullscreenVerticalSwipe() { + + return FEATURE_FLAGS.hubmodeFullscreenVerticalSwipe(); } - - public static boolean mediaControlsLockscreenShadeBugFix() { - return FEATURE_FLAGS.mediaControlsLockscreenShadeBugFix(); + + + + public static boolean hubmodeFullscreenVerticalSwipeFix() { + + return FEATURE_FLAGS.hubmodeFullscreenVerticalSwipeFix(); } - - public static boolean mediaControlsRefactor() { - return FEATURE_FLAGS.mediaControlsRefactor(); + + + + public static boolean iconRefresh2025() { + + return FEATURE_FLAGS.iconRefresh2025(); } - - public static boolean mediaControlsUserInitiatedDeleteintent() { - return FEATURE_FLAGS.mediaControlsUserInitiatedDeleteintent(); + + + + public static boolean ignoreTouchesNextToNotificationShelf() { + + return FEATURE_FLAGS.ignoreTouchesNextToNotificationShelf(); } - - public static boolean migrateClocksToBlueprint() { - return FEATURE_FLAGS.migrateClocksToBlueprint(); + + + + public static boolean indicationTextA11yFix() { + + return FEATURE_FLAGS.indicationTextA11yFix(); } - - public static boolean newAodTransition() { - return FEATURE_FLAGS.newAodTransition(); + + + + public static boolean keyboardDockingIndicator() { + + return FEATURE_FLAGS.keyboardDockingIndicator(); + } + + + + public static boolean keyboardShortcutHelperRewrite() { + + return FEATURE_FLAGS.keyboardShortcutHelperRewrite(); + } + + + + public static boolean keyboardShortcutHelperShortcutCustomizer() { + + return FEATURE_FLAGS.keyboardShortcutHelperShortcutCustomizer(); + } + + + + public static boolean keyboardTouchpadContextualEducation() { + + return FEATURE_FLAGS.keyboardTouchpadContextualEducation(); + } + + + + public static boolean keyguardTransitionForceFinishOnScreenOff() { + + return FEATURE_FLAGS.keyguardTransitionForceFinishOnScreenOff(); + } + + + + public static boolean keyguardWmReorderAtmsCalls() { + + return FEATURE_FLAGS.keyguardWmReorderAtmsCalls(); + } + + + + public static boolean keyguardWmStateRefactor() { + + return FEATURE_FLAGS.keyguardWmStateRefactor(); + } + + + + public static boolean lockscreenFont() { + + return FEATURE_FLAGS.lockscreenFont(); + } + + + + public static boolean lowLightClockDream() { + + return FEATURE_FLAGS.lowLightClockDream(); + } + + + + public static boolean magneticNotificationSwipes() { + + return FEATURE_FLAGS.magneticNotificationSwipes(); + } + + + + public static boolean mediaControlsA11yColors() { + + return FEATURE_FLAGS.mediaControlsA11yColors(); + } + + + + public static boolean mediaControlsButtonMedia3() { + + return FEATURE_FLAGS.mediaControlsButtonMedia3(); + } + + + + public static boolean mediaControlsButtonMedia3Placement() { + + return FEATURE_FLAGS.mediaControlsButtonMedia3Placement(); + } + + + + public static boolean mediaControlsDeviceManagerBackgroundExecution() { + + return FEATURE_FLAGS.mediaControlsDeviceManagerBackgroundExecution(); + } + + + + public static boolean mediaControlsDrawablesReuseBugfix() { + + return FEATURE_FLAGS.mediaControlsDrawablesReuseBugfix(); + } + + + + public static boolean mediaControlsLockscreenShadeBugFix() { + + return FEATURE_FLAGS.mediaControlsLockscreenShadeBugFix(); + } + + + + public static boolean mediaControlsUiUpdate() { + + return FEATURE_FLAGS.mediaControlsUiUpdate(); + } + + + + public static boolean mediaControlsUmoInflationInBackground() { + + return FEATURE_FLAGS.mediaControlsUmoInflationInBackground(); + } + + + + public static boolean mediaControlsUserInitiatedDeleteintent() { + + return FEATURE_FLAGS.mediaControlsUserInitiatedDeleteintent(); + } + + + + public static boolean mediaLoadMetadataViaMediaDataLoader() { + + return FEATURE_FLAGS.mediaLoadMetadataViaMediaDataLoader(); + } + + + + public static boolean mediaLockscreenLaunchAnimation() { + + return FEATURE_FLAGS.mediaLockscreenLaunchAnimation(); + } + + + + public static boolean mediaProjectionDialogBehindLockscreen() { + + return FEATURE_FLAGS.mediaProjectionDialogBehindLockscreen(); + } + + + + public static boolean mediaProjectionGreyErrorText() { + + return FEATURE_FLAGS.mediaProjectionGreyErrorText(); + } + + + + public static boolean mediaProjectionRequestAttributionFix() { + + return FEATURE_FLAGS.mediaProjectionRequestAttributionFix(); + } + + + + public static boolean modesUiDialogPaging() { + + return FEATURE_FLAGS.modesUiDialogPaging(); + } + + + + public static boolean moveTransitionAnimationLayer() { + + return FEATURE_FLAGS.moveTransitionAnimationLayer(); + } + + + + public static boolean msdlFeedback() { + + return FEATURE_FLAGS.msdlFeedback(); + } + + + + public static boolean multiuserWifiPickerTrackerSupport() { + + return FEATURE_FLAGS.multiuserWifiPickerTrackerSupport(); } - - public static boolean newTouchpadGesturesTutorial() { - return FEATURE_FLAGS.newTouchpadGesturesTutorial(); + + + + public static boolean newAodTransition() { + + return FEATURE_FLAGS.newAodTransition(); } - + + + public static boolean newVolumePanel() { + return FEATURE_FLAGS.newVolumePanel(); } - + + + + public static boolean nonTouchscreenDevicesBypassFalsing() { + + return FEATURE_FLAGS.nonTouchscreenDevicesBypassFalsing(); + } + + + + public static boolean notesRoleQsTile() { + + return FEATURE_FLAGS.notesRoleQsTile(); + } + + + + public static boolean notificationAddXOnHoverToDismiss() { + + return FEATURE_FLAGS.notificationAddXOnHoverToDismiss(); + } + + + + public static boolean notificationAmbientSuppressionAfterInflation() { + + return FEATURE_FLAGS.notificationAmbientSuppressionAfterInflation(); + } + + + + public static boolean notificationAnimatedActionsTreatment() { + + return FEATURE_FLAGS.notificationAnimatedActionsTreatment(); + } + + + + public static boolean notificationAppearNonlinear() { + + return FEATURE_FLAGS.notificationAppearNonlinear(); + } + + + public static boolean notificationAsyncGroupHeaderInflation() { + return FEATURE_FLAGS.notificationAsyncGroupHeaderInflation(); } - + + + public static boolean notificationAsyncHybridViewInflation() { + return FEATURE_FLAGS.notificationAsyncHybridViewInflation(); } - + + + public static boolean notificationAvalancheSuppression() { + return FEATURE_FLAGS.notificationAvalancheSuppression(); } - + + + public static boolean notificationAvalancheThrottleHun() { + return FEATURE_FLAGS.notificationAvalancheThrottleHun(); } - + + + public static boolean notificationBackgroundTintOptimization() { + return FEATURE_FLAGS.notificationBackgroundTintOptimization(); } - + + + + public static boolean notificationBundleUi() { + + return FEATURE_FLAGS.notificationBundleUi(); + } + + + public static boolean notificationColorUpdateLogger() { + return FEATURE_FLAGS.notificationColorUpdateLogger(); } - + + + public static boolean notificationContentAlphaOptimization() { + return FEATURE_FLAGS.notificationContentAlphaOptimization(); } - + + + public static boolean notificationFooterBackgroundTintOptimization() { + return FEATURE_FLAGS.notificationFooterBackgroundTintOptimization(); } - - public static boolean notificationMediaManagerBackgroundExecution() { - return FEATURE_FLAGS.notificationMediaManagerBackgroundExecution(); - } - - public static boolean notificationMinimalismPrototype() { - return FEATURE_FLAGS.notificationMinimalismPrototype(); - } - + + + public static boolean notificationOverExpansionClippingFix() { + return FEATURE_FLAGS.notificationOverExpansionClippingFix(); } - - public static boolean notificationPulsingFix() { - return FEATURE_FLAGS.notificationPulsingFix(); + + + + public static boolean notificationReentrantDismiss() { + + return FEATURE_FLAGS.notificationReentrantDismiss(); + } + + + + public static boolean notificationRowAccessibilityExpanded() { + + return FEATURE_FLAGS.notificationRowAccessibilityExpanded(); } - + + + public static boolean notificationRowContentBinderRefactor() { + return FEATURE_FLAGS.notificationRowContentBinderRefactor(); } - + + + + public static boolean notificationRowTransparency() { + + return FEATURE_FLAGS.notificationRowTransparency(); + } + + + public static boolean notificationRowUserContext() { + return FEATURE_FLAGS.notificationRowUserContext(); } - + + + + public static boolean notificationShadeBlur() { + + return FEATURE_FLAGS.notificationShadeBlur(); + } + + + + public static boolean notificationShadeUiThread() { + + return FEATURE_FLAGS.notificationShadeUiThread(); + } + + + + public static boolean notificationSkipSilentUpdates() { + + return FEATURE_FLAGS.notificationSkipSilentUpdates(); + } + + + + public static boolean notificationTransparentHeaderFix() { + + return FEATURE_FLAGS.notificationTransparentHeaderFix(); + } + + + public static boolean notificationViewFlipperPausingV2() { + return FEATURE_FLAGS.notificationViewFlipperPausingV2(); } - + + + public static boolean notificationsBackgroundIcons() { + return FEATURE_FLAGS.notificationsBackgroundIcons(); } - - public static boolean notificationsFooterViewRefactor() { - return FEATURE_FLAGS.notificationsFooterViewRefactor(); - } - - public static boolean notificationsHeadsUpRefactor() { - return FEATURE_FLAGS.notificationsHeadsUpRefactor(); + + + + public static boolean notificationsFooterVisibilityFix() { + + return FEATURE_FLAGS.notificationsFooterVisibilityFix(); } - + + + public static boolean notificationsHideOnDisplaySwitch() { + return FEATURE_FLAGS.notificationsHideOnDisplaySwitch(); } - + + + + public static boolean notificationsHunSharedAnimationValues() { + + return FEATURE_FLAGS.notificationsHunSharedAnimationValues(); + } + + + public static boolean notificationsIconContainerRefactor() { + return FEATURE_FLAGS.notificationsIconContainerRefactor(); } - - public static boolean notificationsImprovedHunAnimation() { - return FEATURE_FLAGS.notificationsImprovedHunAnimation(); + + + + public static boolean notificationsLaunchRadius() { + + return FEATURE_FLAGS.notificationsLaunchRadius(); } - + + + public static boolean notificationsLiveDataStoreRefactor() { + return FEATURE_FLAGS.notificationsLiveDataStoreRefactor(); } - - public static boolean notifyPowerManagerUserActivityBackground() { - return FEATURE_FLAGS.notifyPowerManagerUserActivityBackground(); - } - - public static boolean pinInputFieldStyledFocusState() { - return FEATURE_FLAGS.pinInputFieldStyledFocusState(); + + + + public static boolean notificationsPinnedHunInShade() { + + return FEATURE_FLAGS.notificationsPinnedHunInShade(); } - - public static boolean predictiveBackAnimateBouncer() { - return FEATURE_FLAGS.predictiveBackAnimateBouncer(); + + + + public static boolean notificationsRedesignFooterView() { + + return FEATURE_FLAGS.notificationsRedesignFooterView(); } - - public static boolean predictiveBackAnimateDialogs() { - return FEATURE_FLAGS.predictiveBackAnimateDialogs(); + + + + public static boolean notificationsRedesignGuts() { + + return FEATURE_FLAGS.notificationsRedesignGuts(); + } + + + + public static boolean notifyPasswordTextViewUserActivityInBackground() { + + return FEATURE_FLAGS.notifyPasswordTextViewUserActivityInBackground(); + } + + + + public static boolean notifyPowerManagerUserActivityBackground() { + + return FEATURE_FLAGS.notifyPowerManagerUserActivityBackground(); + } + + + + public static boolean onlyShowMediaStreamSliderInSingleVolumeMode() { + + return FEATURE_FLAGS.onlyShowMediaStreamSliderInSingleVolumeMode(); + } + + + + public static boolean outputSwitcherRedesign() { + + return FEATURE_FLAGS.outputSwitcherRedesign(); + } + + + + public static boolean overrideSuppressOverlayCondition() { + + return FEATURE_FLAGS.overrideSuppressOverlayCondition(); + } + + + + public static boolean permissionHelperInlineUiRichOngoing() { + + return FEATURE_FLAGS.permissionHelperInlineUiRichOngoing(); + } + + + + public static boolean permissionHelperUiRichOngoing() { + + return FEATURE_FLAGS.permissionHelperUiRichOngoing(); } - + + + + public static boolean physicalNotificationMovement() { + + return FEATURE_FLAGS.physicalNotificationMovement(); + } + + + + public static boolean pinInputFieldStyledFocusState() { + + return FEATURE_FLAGS.pinInputFieldStyledFocusState(); + } + + + public static boolean predictiveBackAnimateShade() { + return FEATURE_FLAGS.predictiveBackAnimateShade(); } - - public static boolean predictiveBackSysui() { - return FEATURE_FLAGS.predictiveBackSysui(); + + + + public static boolean predictiveBackDelayWmTransition() { + + return FEATURE_FLAGS.predictiveBackDelayWmTransition(); } - + + + public static boolean priorityPeopleSection() { + return FEATURE_FLAGS.priorityPeopleSection(); } - - public static boolean privacyDotUnfoldWrongCornerFix() { - return FEATURE_FLAGS.privacyDotUnfoldWrongCornerFix(); - } - - public static boolean pssAppSelectorAbruptExitFix() { - return FEATURE_FLAGS.pssAppSelectorAbruptExitFix(); + + + + public static boolean promoteNotificationsAutomatically() { + + return FEATURE_FLAGS.promoteNotificationsAutomatically(); } - + + + public static boolean pssAppSelectorRecentsSplitScreen() { + return FEATURE_FLAGS.pssAppSelectorRecentsSplitScreen(); } - + + + public static boolean pssTaskSwitcher() { + return FEATURE_FLAGS.pssTaskSwitcher(); } - - public static boolean qsCustomTileClickGuaranteedBugFix() { - return FEATURE_FLAGS.qsCustomTileClickGuaranteedBugFix(); + + + + public static boolean qsCustomTileClickGuaranteedBugFix() { + + return FEATURE_FLAGS.qsCustomTileClickGuaranteedBugFix(); + } + + + + public static boolean qsNewTiles() { + + return FEATURE_FLAGS.qsNewTiles(); + } + + + + public static boolean qsNewTilesFuture() { + + return FEATURE_FLAGS.qsNewTilesFuture(); + } + + + + public static boolean qsQuickRebindActiveTiles() { + + return FEATURE_FLAGS.qsQuickRebindActiveTiles(); + } + + + + public static boolean qsRegisterSettingObserverOnBgThread() { + + return FEATURE_FLAGS.qsRegisterSettingObserverOnBgThread(); + } + + + + public static boolean qsTileDetailedView() { + + return FEATURE_FLAGS.qsTileDetailedView(); + } + + + + public static boolean qsTileFocusState() { + + return FEATURE_FLAGS.qsTileFocusState(); + } + + + + public static boolean qsUiRefactor() { + + return FEATURE_FLAGS.qsUiRefactor(); + } + + + + public static boolean qsUiRefactorComposeFragment() { + + return FEATURE_FLAGS.qsUiRefactorComposeFragment(); + } + + + + public static boolean recordIssueQsTile() { + + return FEATURE_FLAGS.recordIssueQsTile(); + } + + + + public static boolean redesignMagnificationWindowSize() { + + return FEATURE_FLAGS.redesignMagnificationWindowSize(); + } + + + + public static boolean refactorGetCurrentUser() { + + return FEATURE_FLAGS.refactorGetCurrentUser(); + } + + + + public static boolean registerBatteryControllerReceiversInCorestartable() { + + return FEATURE_FLAGS.registerBatteryControllerReceiversInCorestartable(); + } + + + + public static boolean registerContentObserversAsync() { + + return FEATURE_FLAGS.registerContentObserversAsync(); + } + + + + public static boolean registerNewWalletCardInBackground() { + + return FEATURE_FLAGS.registerNewWalletCardInBackground(); + } + + + + public static boolean registerWallpaperNotifierBackground() { + + return FEATURE_FLAGS.registerWallpaperNotifierBackground(); + } + + + + public static boolean relockWithPowerButtonImmediately() { + + return FEATURE_FLAGS.relockWithPowerButtonImmediately(); + } + + + + public static boolean removeDreamOverlayHideOnTouch() { + + return FEATURE_FLAGS.removeDreamOverlayHideOnTouch(); + } + + + + public static boolean removeUpdateListenerInQsIconViewImpl() { + + return FEATURE_FLAGS.removeUpdateListenerInQsIconViewImpl(); + } + + + + public static boolean restToUnlock() { + + return FEATURE_FLAGS.restToUnlock(); + } + + + + public static boolean restartDreamOnUnocclude() { + + return FEATURE_FLAGS.restartDreamOnUnocclude(); + } + + + + public static boolean revampedBouncerMessages() { + + return FEATURE_FLAGS.revampedBouncerMessages(); + } + + + + public static boolean runFingerprintDetectOnDismissibleKeyguard() { + + return FEATURE_FLAGS.runFingerprintDetectOnDismissibleKeyguard(); + } + + + + public static boolean saveAndRestoreMagnificationSettingsButtons() { + + return FEATURE_FLAGS.saveAndRestoreMagnificationSettingsButtons(); + } + + + + public static boolean sceneContainer() { + + return FEATURE_FLAGS.sceneContainer(); + } + + + + public static boolean screenshareNotificationHidingBugFix() { + + return FEATURE_FLAGS.screenshareNotificationHidingBugFix(); + } + + + + public static boolean screenshotActionDismissSystemWindows() { + + return FEATURE_FLAGS.screenshotActionDismissSystemWindows(); + } + + + + public static boolean screenshotMultidisplayFocusChange() { + + return FEATURE_FLAGS.screenshotMultidisplayFocusChange(); + } + + + + public static boolean screenshotPolicySplitAndDesktopMode() { + + return FEATURE_FLAGS.screenshotPolicySplitAndDesktopMode(); + } + + + + public static boolean screenshotScrollCropViewCrashFix() { + + return FEATURE_FLAGS.screenshotScrollCropViewCrashFix(); + } + + + + public static boolean screenshotUiControllerRefactor() { + + return FEATURE_FLAGS.screenshotUiControllerRefactor(); + } + + + + public static boolean secondaryUserWidgetHost() { + + return FEATURE_FLAGS.secondaryUserWidgetHost(); + } + + + + public static boolean settingsExtRegisterContentObserverOnBgThread() { + + return FEATURE_FLAGS.settingsExtRegisterContentObserverOnBgThread(); + } + + + + public static boolean shadeExpandsOnStatusBarLongPress() { + + return FEATURE_FLAGS.shadeExpandsOnStatusBarLongPress(); + } + + + + public static boolean shadeHeaderFontUpdate() { + + return FEATURE_FLAGS.shadeHeaderFontUpdate(); + } + + + + public static boolean shadeLaunchAccessibility() { + + return FEATURE_FLAGS.shadeLaunchAccessibility(); + } + + + + public static boolean shadeWindowGoesAround() { + + return FEATURE_FLAGS.shadeWindowGoesAround(); + } + + + + public static boolean shaderlibLoadingEffectRefactor() { + + return FEATURE_FLAGS.shaderlibLoadingEffectRefactor(); + } + + + + public static boolean shortcutHelperKeyGlyph() { + + return FEATURE_FLAGS.shortcutHelperKeyGlyph(); + } + + + + public static boolean showAudioSharingSliderInVolumePanel() { + + return FEATURE_FLAGS.showAudioSharingSliderInVolumePanel(); + } + + + + public static boolean showClipboardIndication() { + + return FEATURE_FLAGS.showClipboardIndication(); } - - public static boolean qsNewPipeline() { - return FEATURE_FLAGS.qsNewPipeline(); + + + + public static boolean showLockedByYourWatchKeyguardIndicator() { + + return FEATURE_FLAGS.showLockedByYourWatchKeyguardIndicator(); } - - public static boolean qsNewTiles() { - return FEATURE_FLAGS.qsNewTiles(); + + + + public static boolean showToastWhenAppControlBrightness() { + + return FEATURE_FLAGS.showToastWhenAppControlBrightness(); } - - public static boolean qsNewTilesFuture() { - return FEATURE_FLAGS.qsNewTilesFuture(); + + + + public static boolean simPinBouncerReset() { + + return FEATURE_FLAGS.simPinBouncerReset(); } - - public static boolean qsTileFocusState() { - return FEATURE_FLAGS.qsTileFocusState(); + + + + public static boolean simPinRaceConditionOnRestart() { + + return FEATURE_FLAGS.simPinRaceConditionOnRestart(); } - - public static boolean qsUiRefactor() { - return FEATURE_FLAGS.qsUiRefactor(); + + + + public static boolean simPinUseSlotId() { + + return FEATURE_FLAGS.simPinUseSlotId(); } - - public static boolean quickSettingsVisualHapticsLongpress() { - return FEATURE_FLAGS.quickSettingsVisualHapticsLongpress(); + + + + public static boolean skipHideSensitiveNotifAnimation() { + + return FEATURE_FLAGS.skipHideSensitiveNotifAnimation(); } - - public static boolean recordIssueQsTile() { - return FEATURE_FLAGS.recordIssueQsTile(); + + + + public static boolean sliceBroadcastRelayInBackground() { + + return FEATURE_FLAGS.sliceBroadcastRelayInBackground(); } - - public static boolean refactorGetCurrentUser() { - return FEATURE_FLAGS.refactorGetCurrentUser(); + + + + public static boolean sliceManagerBinderCallBackground() { + + return FEATURE_FLAGS.sliceManagerBinderCallBackground(); } - - public static boolean registerBatteryControllerReceiversInCorestartable() { - return FEATURE_FLAGS.registerBatteryControllerReceiversInCorestartable(); + + + + public static boolean smartspaceLockscreenViewmodel() { + + return FEATURE_FLAGS.smartspaceLockscreenViewmodel(); } - - public static boolean registerNewWalletCardInBackground() { - return FEATURE_FLAGS.registerNewWalletCardInBackground(); + + + + public static boolean smartspaceRelocateToBottom() { + + return FEATURE_FLAGS.smartspaceRelocateToBottom(); } - - public static boolean registerWallpaperNotifierBackground() { - return FEATURE_FLAGS.registerWallpaperNotifierBackground(); + + + + public static boolean smartspaceRemoteviewsRenderingFix() { + + return FEATURE_FLAGS.smartspaceRemoteviewsRenderingFix(); } - - public static boolean registerZenModeContentObserverBackground() { - return FEATURE_FLAGS.registerZenModeContentObserverBackground(); + + + + public static boolean smartspaceSwipeEventLoggingFix() { + + return FEATURE_FLAGS.smartspaceSwipeEventLoggingFix(); } - - public static boolean removeDreamOverlayHideOnTouch() { - return FEATURE_FLAGS.removeDreamOverlayHideOnTouch(); + + + + public static boolean smartspaceViewpager2() { + + return FEATURE_FLAGS.smartspaceViewpager2(); } - - public static boolean restToUnlock() { - return FEATURE_FLAGS.restToUnlock(); + + + + public static boolean sounddoseCustomization() { + + return FEATURE_FLAGS.sounddoseCustomization(); } - - public static boolean restartDreamOnUnocclude() { - return FEATURE_FLAGS.restartDreamOnUnocclude(); + + + + public static boolean spatialModelAppPushback() { + + return FEATURE_FLAGS.spatialModelAppPushback(); } - - public static boolean revampedBouncerMessages() { - return FEATURE_FLAGS.revampedBouncerMessages(); + + + + public static boolean stabilizeHeadsUpGroupV2() { + + return FEATURE_FLAGS.stabilizeHeadsUpGroupV2(); } - - public static boolean runFingerprintDetectOnDismissibleKeyguard() { - return FEATURE_FLAGS.runFingerprintDetectOnDismissibleKeyguard(); + + + + public static boolean statusBarAlwaysCheckUnderlyingNetworks() { + + return FEATURE_FLAGS.statusBarAlwaysCheckUnderlyingNetworks(); } - - public static boolean saveAndRestoreMagnificationSettingsButtons() { - return FEATURE_FLAGS.saveAndRestoreMagnificationSettingsButtons(); + + + + public static boolean statusBarAutoStartScreenRecordChip() { + + return FEATURE_FLAGS.statusBarAutoStartScreenRecordChip(); } - - public static boolean sceneContainer() { - return FEATURE_FLAGS.sceneContainer(); + + + + public static boolean statusBarChipsModernization() { + + return FEATURE_FLAGS.statusBarChipsModernization(); } - - public static boolean screenshareNotificationHidingBugFix() { - return FEATURE_FLAGS.screenshareNotificationHidingBugFix(); + + + + public static boolean statusBarChipsReturnAnimations() { + + return FEATURE_FLAGS.statusBarChipsReturnAnimations(); } - - public static boolean screenshotActionDismissSystemWindows() { - return FEATURE_FLAGS.screenshotActionDismissSystemWindows(); + + + + public static boolean statusBarFontUpdates() { + + return FEATURE_FLAGS.statusBarFontUpdates(); } - - public static boolean screenshotPrivateProfileAccessibilityAnnouncementFix() { - return FEATURE_FLAGS.screenshotPrivateProfileAccessibilityAnnouncementFix(); + + + + public static boolean statusBarMobileIconKairos() { + + return FEATURE_FLAGS.statusBarMobileIconKairos(); + } + + + + public static boolean statusBarMonochromeIconsFix() { + + return FEATURE_FLAGS.statusBarMonochromeIconsFix(); } - - public static boolean screenshotPrivateProfileBehaviorFix() { - return FEATURE_FLAGS.screenshotPrivateProfileBehaviorFix(); + + + + public static boolean statusBarNoHunBehavior() { + + return FEATURE_FLAGS.statusBarNoHunBehavior(); } - - public static boolean screenshotScrollCropViewCrashFix() { - return FEATURE_FLAGS.screenshotScrollCropViewCrashFix(); + + + + public static boolean statusBarPopupChips() { + + return FEATURE_FLAGS.statusBarPopupChips(); } - - public static boolean screenshotShelfUi2() { - return FEATURE_FLAGS.screenshotShelfUi2(); + + + + public static boolean statusBarRootModernization() { + + return FEATURE_FLAGS.statusBarRootModernization(); } - - public static boolean shadeCollapseActivityLaunchFix() { - return FEATURE_FLAGS.shadeCollapseActivityLaunchFix(); + + + + public static boolean statusBarShowAudioOnlyProjectionChip() { + + return FEATURE_FLAGS.statusBarShowAudioOnlyProjectionChip(); } - - public static boolean shaderlibLoadingEffectRefactor() { - return FEATURE_FLAGS.shaderlibLoadingEffectRefactor(); + + + + public static boolean statusBarSignalPolicyRefactor() { + + return FEATURE_FLAGS.statusBarSignalPolicyRefactor(); } - - public static boolean sliceBroadcastRelayInBackground() { - return FEATURE_FLAGS.sliceBroadcastRelayInBackground(); + + + + public static boolean statusBarSignalPolicyRefactorEthernet() { + + return FEATURE_FLAGS.statusBarSignalPolicyRefactorEthernet(); } - - public static boolean sliceManagerBinderCallBackground() { - return FEATURE_FLAGS.sliceManagerBinderCallBackground(); + + + + public static boolean statusBarStaticInoutIndicators() { + + return FEATURE_FLAGS.statusBarStaticInoutIndicators(); } - - public static boolean smartspaceLockscreenViewmodel() { - return FEATURE_FLAGS.smartspaceLockscreenViewmodel(); + + + + public static boolean statusBarStopUpdatingWindowHeight() { + + return FEATURE_FLAGS.statusBarStopUpdatingWindowHeight(); } - - public static boolean smartspaceRelocateToBottom() { - return FEATURE_FLAGS.smartspaceRelocateToBottom(); + + + + public static boolean statusBarSwipeOverChip() { + + return FEATURE_FLAGS.statusBarSwipeOverChip(); } - - public static boolean smartspaceRemoteviewsRendering() { - return FEATURE_FLAGS.smartspaceRemoteviewsRendering(); + + + + public static boolean statusBarSwitchToSpnFromDataSpn() { + + return FEATURE_FLAGS.statusBarSwitchToSpnFromDataSpn(); } - - public static boolean statusBarMonochromeIconsFix() { - return FEATURE_FLAGS.statusBarMonochromeIconsFix(); + + + + public static boolean statusBarUiThread() { + + return FEATURE_FLAGS.statusBarUiThread(); } - - public static boolean statusBarScreenSharingChips() { - return FEATURE_FLAGS.statusBarScreenSharingChips(); + + + + public static boolean statusBarWindowNoCustomTouch() { + + return FEATURE_FLAGS.statusBarWindowNoCustomTouch(); } - - public static boolean statusBarStaticInoutIndicators() { - return FEATURE_FLAGS.statusBarStaticInoutIndicators(); + + + + public static boolean stoppableFgsSystemApp() { + + return FEATURE_FLAGS.stoppableFgsSystemApp(); } - + + + public static boolean switchUserOnBg() { + return FEATURE_FLAGS.switchUserOnBg(); } - + + + public static boolean sysuiTeamfood() { + return FEATURE_FLAGS.sysuiTeamfood(); } - + + + public static boolean themeOverlayControllerWakefulnessDeprecation() { + return FEATURE_FLAGS.themeOverlayControllerWakefulnessDeprecation(); } - + + + + public static boolean transitionRaceCondition() { + + return FEATURE_FLAGS.transitionRaceCondition(); + } + + + public static boolean translucentOccludingActivityFix() { + return FEATURE_FLAGS.translucentOccludingActivityFix(); } - - public static boolean truncatedStatusBarIconsFix() { - return FEATURE_FLAGS.truncatedStatusBarIconsFix(); + + + + public static boolean tvGlobalActionsFocus() { + + return FEATURE_FLAGS.tvGlobalActionsFocus(); } - + + + public static boolean udfpsViewPerformance() { + return FEATURE_FLAGS.udfpsViewPerformance(); } - + + + public static boolean unfoldAnimationBackgroundProgress() { + return FEATURE_FLAGS.unfoldAnimationBackgroundProgress(); } - + + + + public static boolean unfoldLatencyTrackingFix() { + + return FEATURE_FLAGS.unfoldLatencyTrackingFix(); + } + + + + public static boolean updateCornerRadiusOnDisplayChanged() { + + return FEATURE_FLAGS.updateCornerRadiusOnDisplayChanged(); + } + + + public static boolean updateUserSwitcherBackground() { + return FEATURE_FLAGS.updateUserSwitcherBackground(); } - - public static boolean validateKeyboardShortcutHelperIconUri() { - return FEATURE_FLAGS.validateKeyboardShortcutHelperIconUri(); + + + + public static boolean updateWindowMagnifierBottomBoundary() { + + return FEATURE_FLAGS.updateWindowMagnifierBottomBoundary(); + } + + + + public static boolean useAadProxSensor() { + + return FEATURE_FLAGS.useAadProxSensor(); + } + + + + public static boolean useNotifInflationThreadForFooter() { + + return FEATURE_FLAGS.useNotifInflationThreadForFooter(); + } + + + + public static boolean useNotifInflationThreadForRow() { + + return FEATURE_FLAGS.useNotifInflationThreadForRow(); + } + + + + public static boolean useTransitionsForKeyguardOccluded() { + + return FEATURE_FLAGS.useTransitionsForKeyguardOccluded(); + } + + + + public static boolean useVolumeController() { + + return FEATURE_FLAGS.useVolumeController(); + } + + + + public static boolean userAwareSettingsRepositories() { + + return FEATURE_FLAGS.userAwareSettingsRepositories(); + } + + + + public static boolean userEncryptedSource() { + + return FEATURE_FLAGS.userEncryptedSource(); + } + + + + public static boolean userSwitcherAddSignOutOption() { + + return FEATURE_FLAGS.userSwitcherAddSignOutOption(); } - + + + public static boolean visualInterruptionsRefactor() { + return FEATURE_FLAGS.visualInterruptionsRefactor(); } + + + public static boolean volumeRedesign() { + + return FEATURE_FLAGS.volumeRedesign(); + } + private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } diff --git a/flags/src/com/android/systemui/shared/CustomFeatureFlags.java b/flags/src/com/android/systemui/shared/CustomFeatureFlags.java index b301340b4a1..77bd3c38380 100644 --- a/flags/src/com/android/systemui/shared/CustomFeatureFlags.java +++ b/flags/src/com/android/systemui/shared/CustomFeatureFlags.java @@ -1,13 +1,13 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. + import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; - /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -17,56 +17,189 @@ public CustomFeatureFlags(BiPredicate> getValueI mGetValueImpl = getValueImpl; } @Override - + + public boolean ambientAod() { + return getValue(Flags.FLAG_AMBIENT_AOD, + FeatureFlags::ambientAod); + } + + @Override + public boolean bouncerAreaExclusion() { return getValue(Flags.FLAG_BOUNCER_AREA_EXCLUSION, - FeatureFlags::bouncerAreaExclusion); + FeatureFlags::bouncerAreaExclusion); + } + + @Override + + public boolean clockReactiveSmartspaceLayout() { + return getValue(Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT, + FeatureFlags::clockReactiveSmartspaceLayout); } @Override - + + public boolean clockReactiveVariants() { + return getValue(Flags.FLAG_CLOCK_REACTIVE_VARIANTS, + FeatureFlags::clockReactiveVariants); + } + + @Override + + public boolean cursorHotCorner() { + return getValue(Flags.FLAG_CURSOR_HOT_CORNER, + FeatureFlags::cursorHotCorner); + } + + @Override + public boolean enableHomeDelay() { return getValue(Flags.FLAG_ENABLE_HOME_DELAY, - FeatureFlags::enableHomeDelay); + FeatureFlags::enableHomeDelay); } @Override - + + public boolean enableLppSqueezeEffect() { + return getValue(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT, + FeatureFlags::enableLppSqueezeEffect); + } + + @Override + public boolean exampleSharedFlag() { return getValue(Flags.FLAG_EXAMPLE_SHARED_FLAG, - FeatureFlags::exampleSharedFlag); + FeatureFlags::exampleSharedFlag); + } + + @Override + + public boolean extendibleThemeManager() { + return getValue(Flags.FLAG_EXTENDIBLE_THEME_MANAGER, + FeatureFlags::extendibleThemeManager); + } + + @Override + + public boolean extendedWallpaperEffects() { + return getValue(Flags.FLAG_EXTENDED_WALLPAPER_EFFECTS, + FeatureFlags::extendedWallpaperEffects); } @Override - + + public boolean lockscreenCustomClocks() { + return getValue(Flags.FLAG_LOCKSCREEN_CUSTOM_CLOCKS, + FeatureFlags::lockscreenCustomClocks); + } + + @Override + + public boolean newCustomizationPickerUi() { + return getValue(Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI, + FeatureFlags::newCustomizationPickerUi); + } + + @Override + + public boolean newTouchpadGesturesTutorial() { + return getValue(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, + FeatureFlags::newTouchpadGesturesTutorial); + } + + @Override + public boolean returnAnimationFrameworkLibrary() { return getValue(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, - FeatureFlags::returnAnimationFrameworkLibrary); + FeatureFlags::returnAnimationFrameworkLibrary); + } + + @Override + + public boolean returnAnimationFrameworkLongLived() { + return getValue(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, + FeatureFlags::returnAnimationFrameworkLongLived); + } + + @Override + + public boolean screenshotContextUrl() { + return getValue(Flags.FLAG_SCREENSHOT_CONTEXT_URL, + FeatureFlags::screenshotContextUrl); } @Override - + public boolean shadeAllowBackGesture() { return getValue(Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, - FeatureFlags::shadeAllowBackGesture); + FeatureFlags::shadeAllowBackGesture); } @Override - + public boolean sidefpsControllerRefactor() { return getValue(Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, - FeatureFlags::sidefpsControllerRefactor); + FeatureFlags::sidefpsControllerRefactor); + } + + @Override + + public boolean smartspaceRemoteviewsIntentHandler() { + return getValue(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER, + FeatureFlags::smartspaceRemoteviewsIntentHandler); + } + + @Override + + public boolean smartspaceSportsCardBackground() { + return getValue(Flags.FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND, + FeatureFlags::smartspaceSportsCardBackground); + } + + @Override + + public boolean smartspaceUiUpdate() { + return getValue(Flags.FLAG_SMARTSPACE_UI_UPDATE, + FeatureFlags::smartspaceUiUpdate); + } + + @Override + + public boolean smartspaceUiUpdateResources() { + return getValue(Flags.FLAG_SMARTSPACE_UI_UPDATE_RESOURCES, + FeatureFlags::smartspaceUiUpdateResources); + } + + @Override + + public boolean statusBarConnectedDisplays() { + return getValue(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS, + FeatureFlags::statusBarConnectedDisplays); + } + + @Override + + public boolean threeButtonCornerSwipe() { + return getValue(Flags.FLAG_THREE_BUTTON_CORNER_SWIPE, + FeatureFlags::threeButtonCornerSwipe); + } + + @Override + + public boolean usePreferredImageEditor() { + return getValue(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR, + FeatureFlags::usePreferredImageEditor); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } - + private boolean isOptimizationEnabled() { return false; } @@ -77,18 +210,62 @@ protected boolean getValue(String flagName, Predicate getter) { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_BOUNCER_AREA_EXCLUSION, - Flags.FLAG_ENABLE_HOME_DELAY, - Flags.FLAG_EXAMPLE_SHARED_FLAG, - Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, - Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, - Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR + Flags.FLAG_AMBIENT_AOD, + Flags.FLAG_BOUNCER_AREA_EXCLUSION, + Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT, + Flags.FLAG_CLOCK_REACTIVE_VARIANTS, + Flags.FLAG_CURSOR_HOT_CORNER, + Flags.FLAG_ENABLE_HOME_DELAY, + Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT, + Flags.FLAG_EXAMPLE_SHARED_FLAG, + Flags.FLAG_EXTENDIBLE_THEME_MANAGER, + Flags.FLAG_EXTENDED_WALLPAPER_EFFECTS, + Flags.FLAG_LOCKSCREEN_CUSTOM_CLOCKS, + Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI, + Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, + Flags.FLAG_SCREENSHOT_CONTEXT_URL, + Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, + Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, + Flags.FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER, + Flags.FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND, + Flags.FLAG_SMARTSPACE_UI_UPDATE, + Flags.FLAG_SMARTSPACE_UI_UPDATE_RESOURCES, + Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS, + Flags.FLAG_THREE_BUTTON_CORNER_SWIPE, + Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - "" - ) + Arrays.asList( + Flags.FLAG_AMBIENT_AOD, + Flags.FLAG_BOUNCER_AREA_EXCLUSION, + Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT, + Flags.FLAG_CLOCK_REACTIVE_VARIANTS, + Flags.FLAG_CURSOR_HOT_CORNER, + Flags.FLAG_ENABLE_HOME_DELAY, + Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT, + Flags.FLAG_EXAMPLE_SHARED_FLAG, + Flags.FLAG_EXTENDIBLE_THEME_MANAGER, + Flags.FLAG_EXTENDED_WALLPAPER_EFFECTS, + Flags.FLAG_LOCKSCREEN_CUSTOM_CLOCKS, + Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI, + Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, + Flags.FLAG_SCREENSHOT_CONTEXT_URL, + Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, + Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, + Flags.FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER, + Flags.FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND, + Flags.FLAG_SMARTSPACE_UI_UPDATE, + Flags.FLAG_SMARTSPACE_UI_UPDATE_RESOURCES, + Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS, + Flags.FLAG_THREE_BUTTON_CORNER_SWIPE, + Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR, + "" + ) ); } diff --git a/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java b/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java index c223248bbfa..226db519961 100644 --- a/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; - /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/systemui/shared/FeatureFlags.java b/flags/src/com/android/systemui/shared/FeatureFlags.java index e9fe9c71446..176d5eec96a 100644 --- a/flags/src/com/android/systemui/shared/FeatureFlags.java +++ b/flags/src/com/android/systemui/shared/FeatureFlags.java @@ -1,24 +1,107 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. + /** @hide */ public interface FeatureFlags { - - + + + + boolean ambientAod(); + + + boolean bouncerAreaExclusion(); - - + + + + boolean clockReactiveSmartspaceLayout(); + + + + boolean clockReactiveVariants(); + + + + boolean cursorHotCorner(); + + + boolean enableHomeDelay(); - - + + + + boolean enableLppSqueezeEffect(); + + + boolean exampleSharedFlag(); - - + + + + boolean extendibleThemeManager(); + + + + boolean extendedWallpaperEffects(); + + + + boolean lockscreenCustomClocks(); + + + + boolean newCustomizationPickerUi(); + + + + boolean newTouchpadGesturesTutorial(); + + + boolean returnAnimationFrameworkLibrary(); - - + + + + boolean returnAnimationFrameworkLongLived(); + + + + boolean screenshotContextUrl(); + + + boolean shadeAllowBackGesture(); - - + + + boolean sidefpsControllerRefactor(); + + + + boolean smartspaceRemoteviewsIntentHandler(); + + + + boolean smartspaceSportsCardBackground(); + + + + boolean smartspaceUiUpdate(); + + + + boolean smartspaceUiUpdateResources(); + + + + boolean statusBarConnectedDisplays(); + + + + boolean threeButtonCornerSwipe(); + + + + boolean usePreferredImageEditor(); } diff --git a/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java b/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java index 39a1f0ee3ac..befb050c7ad 100644 --- a/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java @@ -1,195 +1,181 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. -import com.android.quickstep.util.DeviceConfigHelper; - -import java.nio.file.Files; -import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); - private static volatile boolean isCached = false; - private static volatile boolean biometrics_framework_is_cached = false; - private static volatile boolean systemui_is_cached = false; - private static boolean bouncerAreaExclusion = true; - private static boolean enableHomeDelay = false; - private static boolean exampleSharedFlag = false; - private static boolean returnAnimationFrameworkLibrary = false; - private static boolean shadeAllowBackGesture = false; - private static boolean sidefpsControllerRefactor = true; + @Override - private void init() { - boolean foundPackage = true; + public boolean ambientAod() { + return false; + } - sidefpsControllerRefactor = foundPackage; + @Override - bouncerAreaExclusion = foundPackage; + public boolean bouncerAreaExclusion() { + return true; + } + @Override - enableHomeDelay = foundPackage; + public boolean clockReactiveSmartspaceLayout() { + return false; + } - exampleSharedFlag = foundPackage; + @Override - returnAnimationFrameworkLibrary = foundPackage ; + public boolean clockReactiveVariants() { + return false; + } + @Override - shadeAllowBackGesture = foundPackage; - isCached = true; + public boolean cursorHotCorner() { + return false; } + @Override + + + public boolean enableHomeDelay() { + return false; + } + @Override - private void load_overrides_biometrics_framework() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - sidefpsControllerRefactor = - properties.getBoolean(Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace biometrics_framework " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - biometrics_framework_is_cached = true; + public boolean enableLppSqueezeEffect() { + return false; } - private void load_overrides_systemui() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - bouncerAreaExclusion = - properties.getBoolean(Flags.FLAG_BOUNCER_AREA_EXCLUSION, true); - enableHomeDelay = - properties.getBoolean(Flags.FLAG_ENABLE_HOME_DELAY, false); - exampleSharedFlag = - properties.getBoolean(Flags.FLAG_EXAMPLE_SHARED_FLAG, false); - returnAnimationFrameworkLibrary = - properties.getBoolean(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, false); - shadeAllowBackGesture = - properties.getBoolean(Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, false); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace systemui " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - systemui_is_cached = true; + @Override + + + public boolean exampleSharedFlag() { + return false; } @Override - - - public boolean bouncerAreaExclusion() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return bouncerAreaExclusion; + + public boolean extendibleThemeManager() { + return true; } @Override - - - public boolean enableHomeDelay() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return enableHomeDelay; + + public boolean extendedWallpaperEffects() { + return false; + } + + @Override + + + public boolean lockscreenCustomClocks() { + return false; + } + + @Override + + + public boolean newCustomizationPickerUi() { + return false; } @Override - - - public boolean exampleSharedFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return exampleSharedFlag; + + public boolean newTouchpadGesturesTutorial() { + return true; } @Override - - + + public boolean returnAnimationFrameworkLibrary() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return returnAnimationFrameworkLibrary; + return true; + } + + @Override + + public boolean returnAnimationFrameworkLongLived() { + return true; } @Override - - - public boolean shadeAllowBackGesture() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return shadeAllowBackGesture; + + public boolean screenshotContextUrl() { + return false; } @Override - - + + + public boolean shadeAllowBackGesture() { + return false; + } + + @Override + + public boolean sidefpsControllerRefactor() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!biometrics_framework_is_cached) { - load_overrides_biometrics_framework(); - } - } - return sidefpsControllerRefactor; + return true; + } + + @Override + + public boolean smartspaceRemoteviewsIntentHandler() { + return true; } -} + @Override + + + public boolean smartspaceSportsCardBackground() { + return false; + } + + @Override + + + public boolean smartspaceUiUpdate() { + return false; + } + + @Override + + + public boolean smartspaceUiUpdateResources() { + return false; + } + + @Override + + + public boolean statusBarConnectedDisplays() { + return false; + } + @Override + + + public boolean threeButtonCornerSwipe() { + return false; + } + + @Override + + + public boolean usePreferredImageEditor() { + return false; + } + +} diff --git a/flags/src/com/android/systemui/shared/Flags.java b/flags/src/com/android/systemui/shared/Flags.java index 4b817be4ef3..30e281af168 100644 --- a/flags/src/com/android/systemui/shared/Flags.java +++ b/flags/src/com/android/systemui/shared/Flags.java @@ -1,50 +1,235 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. + + /** @hide */ public final class Flags { + /** @hide */ + public static final String FLAG_AMBIENT_AOD = "com.android.systemui.shared.ambient_aod"; /** @hide */ public static final String FLAG_BOUNCER_AREA_EXCLUSION = "com.android.systemui.shared.bouncer_area_exclusion"; /** @hide */ + public static final String FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT = "com.android.systemui.shared.clock_reactive_smartspace_layout"; + /** @hide */ + public static final String FLAG_CLOCK_REACTIVE_VARIANTS = "com.android.systemui.shared.clock_reactive_variants"; + /** @hide */ + public static final String FLAG_CURSOR_HOT_CORNER = "com.android.systemui.shared.cursor_hot_corner"; + /** @hide */ public static final String FLAG_ENABLE_HOME_DELAY = "com.android.systemui.shared.enable_home_delay"; /** @hide */ + public static final String FLAG_ENABLE_LPP_SQUEEZE_EFFECT = "com.android.systemui.shared.enable_lpp_squeeze_effect"; + /** @hide */ public static final String FLAG_EXAMPLE_SHARED_FLAG = "com.android.systemui.shared.example_shared_flag"; /** @hide */ + public static final String FLAG_EXTENDIBLE_THEME_MANAGER = "com.android.systemui.shared.extendible_theme_manager"; + /** @hide */ + public static final String FLAG_EXTENDED_WALLPAPER_EFFECTS = "com.android.systemui.shared.extended_wallpaper_effects"; + /** @hide */ + public static final String FLAG_LOCKSCREEN_CUSTOM_CLOCKS = "com.android.systemui.shared.lockscreen_custom_clocks"; + /** @hide */ + public static final String FLAG_NEW_CUSTOMIZATION_PICKER_UI = "com.android.systemui.shared.new_customization_picker_ui"; + /** @hide */ + public static final String FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL = "com.android.systemui.shared.new_touchpad_gestures_tutorial"; + /** @hide */ public static final String FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY = "com.android.systemui.shared.return_animation_framework_library"; /** @hide */ + public static final String FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED = "com.android.systemui.shared.return_animation_framework_long_lived"; + /** @hide */ + public static final String FLAG_SCREENSHOT_CONTEXT_URL = "com.android.systemui.shared.screenshot_context_url"; + /** @hide */ public static final String FLAG_SHADE_ALLOW_BACK_GESTURE = "com.android.systemui.shared.shade_allow_back_gesture"; /** @hide */ public static final String FLAG_SIDEFPS_CONTROLLER_REFACTOR = "com.android.systemui.shared.sidefps_controller_refactor"; - - + /** @hide */ + public static final String FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER = "com.android.systemui.shared.smartspace_remoteviews_intent_handler"; + /** @hide */ + public static final String FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND = "com.android.systemui.shared.smartspace_sports_card_background"; + /** @hide */ + public static final String FLAG_SMARTSPACE_UI_UPDATE = "com.android.systemui.shared.smartspace_ui_update"; + /** @hide */ + public static final String FLAG_SMARTSPACE_UI_UPDATE_RESOURCES = "com.android.systemui.shared.smartspace_ui_update_resources"; + /** @hide */ + public static final String FLAG_STATUS_BAR_CONNECTED_DISPLAYS = "com.android.systemui.shared.status_bar_connected_displays"; + /** @hide */ + public static final String FLAG_THREE_BUTTON_CORNER_SWIPE = "com.android.systemui.shared.three_button_corner_swipe"; + /** @hide */ + public static final String FLAG_USE_PREFERRED_IMAGE_EDITOR = "com.android.systemui.shared.use_preferred_image_editor"; + + + + public static boolean ambientAod() { + + return FEATURE_FLAGS.ambientAod(); + } + + + public static boolean bouncerAreaExclusion() { + return FEATURE_FLAGS.bouncerAreaExclusion(); } - - + + + + public static boolean clockReactiveSmartspaceLayout() { + + return FEATURE_FLAGS.clockReactiveSmartspaceLayout(); + } + + + + public static boolean clockReactiveVariants() { + + return FEATURE_FLAGS.clockReactiveVariants(); + } + + + + public static boolean cursorHotCorner() { + + return FEATURE_FLAGS.cursorHotCorner(); + } + + + public static boolean enableHomeDelay() { + return FEATURE_FLAGS.enableHomeDelay(); } - - + + + + public static boolean enableLppSqueezeEffect() { + + return FEATURE_FLAGS.enableLppSqueezeEffect(); + } + + + public static boolean exampleSharedFlag() { + return FEATURE_FLAGS.exampleSharedFlag(); } - - + + + + public static boolean extendibleThemeManager() { + + return FEATURE_FLAGS.extendibleThemeManager(); + } + + + + public static boolean extendedWallpaperEffects() { + + return FEATURE_FLAGS.extendedWallpaperEffects(); + } + + + + public static boolean lockscreenCustomClocks() { + + return FEATURE_FLAGS.lockscreenCustomClocks(); + } + + + + public static boolean newCustomizationPickerUi() { + + return FEATURE_FLAGS.newCustomizationPickerUi(); + } + + + + public static boolean newTouchpadGesturesTutorial() { + + return FEATURE_FLAGS.newTouchpadGesturesTutorial(); + } + + + public static boolean returnAnimationFrameworkLibrary() { + return FEATURE_FLAGS.returnAnimationFrameworkLibrary(); } - - + + + + public static boolean returnAnimationFrameworkLongLived() { + + return FEATURE_FLAGS.returnAnimationFrameworkLongLived(); + } + + + + public static boolean screenshotContextUrl() { + + return FEATURE_FLAGS.screenshotContextUrl(); + } + + + public static boolean shadeAllowBackGesture() { + return FEATURE_FLAGS.shadeAllowBackGesture(); } - - + + + public static boolean sidefpsControllerRefactor() { + return FEATURE_FLAGS.sidefpsControllerRefactor(); } + + + public static boolean smartspaceRemoteviewsIntentHandler() { + + return FEATURE_FLAGS.smartspaceRemoteviewsIntentHandler(); + } + + + + public static boolean smartspaceSportsCardBackground() { + + return FEATURE_FLAGS.smartspaceSportsCardBackground(); + } + + + + public static boolean smartspaceUiUpdate() { + + return FEATURE_FLAGS.smartspaceUiUpdate(); + } + + + + public static boolean smartspaceUiUpdateResources() { + + return FEATURE_FLAGS.smartspaceUiUpdateResources(); + } + + + + public static boolean statusBarConnectedDisplays() { + + return FEATURE_FLAGS.statusBarConnectedDisplays(); + } + + + + public static boolean threeButtonCornerSwipe() { + + return FEATURE_FLAGS.threeButtonCornerSwipe(); + } + + + + public static boolean usePreferredImageEditor() { + + return FEATURE_FLAGS.usePreferredImageEditor(); + } + private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } diff --git a/flags/src/com/android/window/flags2/CustomFeatureFlags.java b/flags/src/com/android/window/flags2/CustomFeatureFlags.java index c52b5a4bb9a..3591e0549ac 100644 --- a/flags/src/com/android/window/flags2/CustomFeatureFlags.java +++ b/flags/src/com/android/window/flags2/CustomFeatureFlags.java @@ -7,7 +7,6 @@ import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; - /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -17,742 +16,1870 @@ public CustomFeatureFlags(BiPredicate> getValueI mGetValueImpl = getValueImpl; } @Override - + + public boolean actionModeEdgeToEdge() { + return getValue(Flags.FLAG_ACTION_MODE_EDGE_TO_EDGE, + FeatureFlags::actionModeEdgeToEdge); + } + + @Override + public boolean activityEmbeddingAnimationCustomizationFlag() { return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, - FeatureFlags::activityEmbeddingAnimationCustomizationFlag); + FeatureFlags::activityEmbeddingAnimationCustomizationFlag); } @Override - - public boolean activityEmbeddingInteractiveDividerFlag() { - return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, - FeatureFlags::activityEmbeddingInteractiveDividerFlag); + + public boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch() { + return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH, + FeatureFlags::activityEmbeddingDelayTaskFragmentFinishForActivityLaunch); } @Override - - public boolean activityEmbeddingOverlayPresentationFlag() { - return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG, - FeatureFlags::activityEmbeddingOverlayPresentationFlag); + + public boolean activityEmbeddingInteractiveDividerFlag() { + return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, + FeatureFlags::activityEmbeddingInteractiveDividerFlag); } @Override - - public boolean activitySnapshotByDefault() { - return getValue(Flags.FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT, - FeatureFlags::activitySnapshotByDefault); + + public boolean activityEmbeddingMetrics() { + return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_METRICS, + FeatureFlags::activityEmbeddingMetrics); } @Override - - public boolean activityWindowInfoFlag() { - return getValue(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG, - FeatureFlags::activityWindowInfoFlag); + + public boolean activityEmbeddingSupportForConnectedDisplays() { + return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, + FeatureFlags::activityEmbeddingSupportForConnectedDisplays); } @Override - + public boolean allowDisableActivityRecordInputSink() { return getValue(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, - FeatureFlags::allowDisableActivityRecordInputSink); + FeatureFlags::allowDisableActivityRecordInputSink); } @Override - + public boolean allowHideScmButton() { return getValue(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, - FeatureFlags::allowHideScmButton); + FeatureFlags::allowHideScmButton); } @Override - + public boolean allowsScreenSizeDecoupledFromStatusBarAndCutout() { return getValue(Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, - FeatureFlags::allowsScreenSizeDecoupledFromStatusBarAndCutout); + FeatureFlags::allowsScreenSizeDecoupledFromStatusBarAndCutout); } @Override - - public boolean alwaysDeferTransitionWhenApplyWct() { - return getValue(Flags.FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT, - FeatureFlags::alwaysDeferTransitionWhenApplyWct); - } - @Override - public boolean alwaysDrawMagnificationFullscreenBorder() { return getValue(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, - FeatureFlags::alwaysDrawMagnificationFullscreenBorder); + FeatureFlags::alwaysDrawMagnificationFullscreenBorder); } @Override - + public boolean alwaysUpdateWallpaperPermission() { return getValue(Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, - FeatureFlags::alwaysUpdateWallpaperPermission); + FeatureFlags::alwaysUpdateWallpaperPermission); + } + + @Override + + public boolean aodTransition() { + return getValue(Flags.FLAG_AOD_TRANSITION, + FeatureFlags::aodTransition); } @Override - + + public boolean appCompatAsyncRelayout() { + return getValue(Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT, + FeatureFlags::appCompatAsyncRelayout); + } + + @Override + public boolean appCompatPropertiesApi() { return getValue(Flags.FLAG_APP_COMPAT_PROPERTIES_API, - FeatureFlags::appCompatPropertiesApi); + FeatureFlags::appCompatPropertiesApi); } @Override - + public boolean appCompatRefactoring() { return getValue(Flags.FLAG_APP_COMPAT_REFACTORING, - FeatureFlags::appCompatRefactoring); + FeatureFlags::appCompatRefactoring); + } + + @Override + + public boolean appCompatUiFramework() { + return getValue(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK, + FeatureFlags::appCompatUiFramework); + } + + @Override + + public boolean appHandleNoRelayoutOnExclusionChange() { + return getValue(Flags.FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE, + FeatureFlags::appHandleNoRelayoutOnExclusionChange); + } + + @Override + + public boolean applyLifecycleOnPipChange() { + return getValue(Flags.FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE, + FeatureFlags::applyLifecycleOnPipChange); + } + + @Override + + public boolean avoidRebindingIntentionallyDisconnectedWallpaper() { + return getValue(Flags.FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER, + FeatureFlags::avoidRebindingIntentionallyDisconnectedWallpaper); + } + + @Override + + public boolean backupAndRestoreForUserAspectRatioSettings() { + return getValue(Flags.FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS, + FeatureFlags::backupAndRestoreForUserAspectRatioSettings); + } + + @Override + + public boolean balAdditionalLogging() { + return getValue(Flags.FLAG_BAL_ADDITIONAL_LOGGING, + FeatureFlags::balAdditionalLogging); + } + + @Override + + public boolean balAdditionalStartModes() { + return getValue(Flags.FLAG_BAL_ADDITIONAL_START_MODES, + FeatureFlags::balAdditionalStartModes); + } + + @Override + + public boolean balClearAllowlistDuration() { + return getValue(Flags.FLAG_BAL_CLEAR_ALLOWLIST_DURATION, + FeatureFlags::balClearAllowlistDuration); } @Override - + public boolean balDontBringExistingBackgroundTaskStackToFg() { return getValue(Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, - FeatureFlags::balDontBringExistingBackgroundTaskStackToFg); + FeatureFlags::balDontBringExistingBackgroundTaskStackToFg); } @Override - + public boolean balImproveRealCallerVisibilityCheck() { return getValue(Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, - FeatureFlags::balImproveRealCallerVisibilityCheck); + FeatureFlags::balImproveRealCallerVisibilityCheck); } @Override - + public boolean balImprovedMetrics() { return getValue(Flags.FLAG_BAL_IMPROVED_METRICS, - FeatureFlags::balImprovedMetrics); + FeatureFlags::balImprovedMetrics); } @Override - - public boolean balRequireOptInByPendingIntentCreator() { - return getValue(Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, - FeatureFlags::balRequireOptInByPendingIntentCreator); + + public boolean balReduceGracePeriod() { + return getValue(Flags.FLAG_BAL_REDUCE_GRACE_PERIOD, + FeatureFlags::balReduceGracePeriod); } @Override - - public boolean balRequireOptInSameUid() { - return getValue(Flags.FLAG_BAL_REQUIRE_OPT_IN_SAME_UID, - FeatureFlags::balRequireOptInSameUid); + + public boolean balRequireOptInByPendingIntentCreator() { + return getValue(Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, + FeatureFlags::balRequireOptInByPendingIntentCreator); } @Override - + public boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { return getValue(Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, - FeatureFlags::balRespectAppSwitchStateWhenCheckBoundByForegroundUid); + FeatureFlags::balRespectAppSwitchStateWhenCheckBoundByForegroundUid); } @Override - - public boolean balShowToasts() { - return getValue(Flags.FLAG_BAL_SHOW_TOASTS, - FeatureFlags::balShowToasts); + + public boolean balSendIntentWithOptions() { + return getValue(Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS, + FeatureFlags::balSendIntentWithOptions); } @Override - + public boolean balShowToastsBlocked() { return getValue(Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, - FeatureFlags::balShowToastsBlocked); + FeatureFlags::balShowToastsBlocked); + } + + @Override + + public boolean balStrictModeGracePeriod() { + return getValue(Flags.FLAG_BAL_STRICT_MODE_GRACE_PERIOD, + FeatureFlags::balStrictModeGracePeriod); + } + + @Override + + public boolean balStrictModeRo() { + return getValue(Flags.FLAG_BAL_STRICT_MODE_RO, + FeatureFlags::balStrictModeRo); } @Override - - public boolean blastSyncNotificationShadeOnDisplaySwitch() { - return getValue(Flags.FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH, - FeatureFlags::blastSyncNotificationShadeOnDisplaySwitch); + + public boolean betterSupportNonMatchParentActivity() { + return getValue(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY, + FeatureFlags::betterSupportNonMatchParentActivity); } @Override - - public boolean bundleClientTransactionFlag() { - return getValue(Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG, - FeatureFlags::bundleClientTransactionFlag); + + public boolean cacheWindowStyle() { + return getValue(Flags.FLAG_CACHE_WINDOW_STYLE, + FeatureFlags::cacheWindowStyle); } @Override - + public boolean cameraCompatForFreeform() { return getValue(Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, - FeatureFlags::cameraCompatForFreeform); + FeatureFlags::cameraCompatForFreeform); + } + + @Override + + public boolean cameraCompatFullscreenPickSameTaskActivity() { + return getValue(Flags.FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY, + FeatureFlags::cameraCompatFullscreenPickSameTaskActivity); + } + + @Override + + public boolean checkDisabledSnapshotsInTaskPersister() { + return getValue(Flags.FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER, + FeatureFlags::checkDisabledSnapshotsInTaskPersister); + } + + @Override + + public boolean cleanupDispatchPendingTransactionsRemoteException() { + return getValue(Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION, + FeatureFlags::cleanupDispatchPendingTransactionsRemoteException); + } + + @Override + + public boolean clearSystemVibrator() { + return getValue(Flags.FLAG_CLEAR_SYSTEM_VIBRATOR, + FeatureFlags::clearSystemVibrator); } @Override - + public boolean closeToSquareConfigIncludesStatusBar() { return getValue(Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, - FeatureFlags::closeToSquareConfigIncludesStatusBar); + FeatureFlags::closeToSquareConfigIncludesStatusBar); + } + + @Override + + public boolean condenseConfigurationChangeForSimpleMode() { + return getValue(Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE, + FeatureFlags::condenseConfigurationChangeForSimpleMode); } @Override - + public boolean configurableFontScaleDefault() { return getValue(Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, - FeatureFlags::configurableFontScaleDefault); + FeatureFlags::configurableFontScaleDefault); } @Override - + public boolean coverDisplayOptIn() { return getValue(Flags.FLAG_COVER_DISPLAY_OPT_IN, - FeatureFlags::coverDisplayOptIn); + FeatureFlags::coverDisplayOptIn); } @Override - - public boolean deferDisplayUpdates() { - return getValue(Flags.FLAG_DEFER_DISPLAY_UPDATES, - FeatureFlags::deferDisplayUpdates); - } - @Override - public boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { return getValue(Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, - FeatureFlags::delayNotificationToMagnificationWhenRecentsWindowToFrontTransition); + FeatureFlags::delayNotificationToMagnificationWhenRecentsWindowToFrontTransition); + } + + @Override + + public boolean delegateBackGestureToShell() { + return getValue(Flags.FLAG_DELEGATE_BACK_GESTURE_TO_SHELL, + FeatureFlags::delegateBackGestureToShell); } @Override - + public boolean delegateUnhandledDrags() { return getValue(Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, - FeatureFlags::delegateUnhandledDrags); + FeatureFlags::delegateUnhandledDrags); } @Override - + public boolean deleteCaptureDisplay() { return getValue(Flags.FLAG_DELETE_CAPTURE_DISPLAY, - FeatureFlags::deleteCaptureDisplay); + FeatureFlags::deleteCaptureDisplay); } @Override - + public boolean density390Api() { return getValue(Flags.FLAG_DENSITY_390_API, - FeatureFlags::density390Api); + FeatureFlags::density390Api); + } + + @Override + + public boolean disableDesktopLaunchParamsOutsideDesktopBugFix() { + return getValue(Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX, + FeatureFlags::disableDesktopLaunchParamsOutsideDesktopBugFix); } @Override - - public boolean disableObjectPool() { - return getValue(Flags.FLAG_DISABLE_OBJECT_POOL, - FeatureFlags::disableObjectPool); + + public boolean disableNonResizableAppSnapResizing() { + return getValue(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, + FeatureFlags::disableNonResizableAppSnapResizing); } @Override - - public boolean disableThinLetterboxingPolicy() { - return getValue(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY, - FeatureFlags::disableThinLetterboxingPolicy); + + public boolean disableOptOutEdgeToEdge() { + return getValue(Flags.FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE, + FeatureFlags::disableOptOutEdgeToEdge); } @Override - + public boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions() { return getValue(Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, - FeatureFlags::doNotCheckIntersectionWhenNonMagnifiableWindowTransitions); + FeatureFlags::doNotCheckIntersectionWhenNonMagnifiableWindowTransitions); } @Override - - public boolean drawSnapshotAspectRatioMatch() { - return getValue(Flags.FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH, - FeatureFlags::drawSnapshotAspectRatioMatch); + + public boolean earlyLaunchHint() { + return getValue(Flags.FLAG_EARLY_LAUNCH_HINT, + FeatureFlags::earlyLaunchHint); } @Override - + public boolean edgeToEdgeByDefault() { return getValue(Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, - FeatureFlags::edgeToEdgeByDefault); + FeatureFlags::edgeToEdgeByDefault); } @Override - - public boolean embeddedActivityBackNavFlag() { - return getValue(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG, - FeatureFlags::embeddedActivityBackNavFlag); + + public boolean enableAccessibleCustomHeaders() { + return getValue(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS, + FeatureFlags::enableAccessibleCustomHeaders); } @Override - - public boolean enableAdditionalWindowsAboveStatusBar() { - return getValue(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR, - FeatureFlags::enableAdditionalWindowsAboveStatusBar); + + public boolean enableActivityEmbeddingSupportForConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, + FeatureFlags::enableActivityEmbeddingSupportForConnectedDisplays); } @Override - + public boolean enableAppHeaderWithTaskDensity() { return getValue(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, - FeatureFlags::enableAppHeaderWithTaskDensity); + FeatureFlags::enableAppHeaderWithTaskDensity); + } + + @Override + + public boolean enableBorderSettings() { + return getValue(Flags.FLAG_ENABLE_BORDER_SETTINGS, + FeatureFlags::enableBorderSettings); } @Override - + public boolean enableBufferTransformHintFromDisplay() { return getValue(Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, - FeatureFlags::enableBufferTransformHintFromDisplay); + FeatureFlags::enableBufferTransformHintFromDisplay); + } + + @Override + + public boolean enableBugFixesForSecondaryDisplay() { + return getValue(Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY, + FeatureFlags::enableBugFixesForSecondaryDisplay); } @Override - + public boolean enableCameraCompatForDesktopWindowing() { return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, - FeatureFlags::enableCameraCompatForDesktopWindowing); + FeatureFlags::enableCameraCompatForDesktopWindowing); } @Override - - public boolean enableCompatuiSysuiLauncher() { - return getValue(Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, - FeatureFlags::enableCompatuiSysuiLauncher); + + public boolean enableCameraCompatForDesktopWindowingOptOut() { + return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT, + FeatureFlags::enableCameraCompatForDesktopWindowingOptOut); } @Override - - public boolean enableDesktopWindowingImmersiveHandleHiding() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, - FeatureFlags::enableDesktopWindowingImmersiveHandleHiding); + + public boolean enableCameraCompatForDesktopWindowingOptOutApi() { + return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API, + FeatureFlags::enableCameraCompatForDesktopWindowingOptOutApi); } @Override - - public boolean enableDesktopWindowingModalsPolicy() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, - FeatureFlags::enableDesktopWindowingModalsPolicy); + + public boolean enableCameraCompatTrackTaskAndAppBugfix() { + return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX, + FeatureFlags::enableCameraCompatTrackTaskAndAppBugfix); } @Override - - public boolean enableDesktopWindowingMode() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FeatureFlags::enableDesktopWindowingMode); + + public boolean enableCaptionCompatInsetConversion() { + return getValue(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION, + FeatureFlags::enableCaptionCompatInsetConversion); } @Override - - public boolean enableDesktopWindowingQuickSwitch() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, - FeatureFlags::enableDesktopWindowingQuickSwitch); + + public boolean enableCaptionCompatInsetForceConsumption() { + return getValue(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION, + FeatureFlags::enableCaptionCompatInsetForceConsumption); } @Override - - public boolean enableDesktopWindowingScvhCache() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE, - FeatureFlags::enableDesktopWindowingScvhCache); + + public boolean enableCaptionCompatInsetForceConsumptionAlways() { + return getValue(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS, + FeatureFlags::enableCaptionCompatInsetForceConsumptionAlways); } @Override - - public boolean enableDesktopWindowingSizeConstraints() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, - FeatureFlags::enableDesktopWindowingSizeConstraints); + + public boolean enableCascadingWindows() { + return getValue(Flags.FLAG_ENABLE_CASCADING_WINDOWS, + FeatureFlags::enableCascadingWindows); } @Override - - public boolean enableDesktopWindowingTaskLimit() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, - FeatureFlags::enableDesktopWindowingTaskLimit); + + public boolean enableCompatUiVisibilityStatus() { + return getValue(Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS, + FeatureFlags::enableCompatUiVisibilityStatus); } @Override - - public boolean enableDesktopWindowingTaskbarRunningApps() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, - FeatureFlags::enableDesktopWindowingTaskbarRunningApps); + + public boolean enableCompatuiSysuiLauncher() { + return getValue(Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, + FeatureFlags::enableCompatuiSysuiLauncher); } @Override - - public boolean enableDesktopWindowingWallpaperActivity() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - FeatureFlags::enableDesktopWindowingWallpaperActivity); + + public boolean enableConnectedDisplaysDnd() { + return getValue(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND, + FeatureFlags::enableConnectedDisplaysDnd); } @Override - - public boolean enableScaledResizing() { - return getValue(Flags.FLAG_ENABLE_SCALED_RESIZING, - FeatureFlags::enableScaledResizing); + + public boolean enableConnectedDisplaysPip() { + return getValue(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, + FeatureFlags::enableConnectedDisplaysPip); } @Override - - public boolean enableTaskStackObserverInShell() { - return getValue(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, - FeatureFlags::enableTaskStackObserverInShell); + + public boolean enableConnectedDisplaysWindowDrag() { + return getValue(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG, + FeatureFlags::enableConnectedDisplaysWindowDrag); } @Override - - public boolean enableThemedAppHeaders() { - return getValue(Flags.FLAG_ENABLE_THEMED_APP_HEADERS, - FeatureFlags::enableThemedAppHeaders); + + public boolean enableDesktopAppHandleAnimation() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION, + FeatureFlags::enableDesktopAppHandleAnimation); } @Override - - public boolean enableWindowingDynamicInitialBounds() { - return getValue(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, - FeatureFlags::enableWindowingDynamicInitialBounds); + + public boolean enableDesktopAppLaunchAlttabTransitions() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS, + FeatureFlags::enableDesktopAppLaunchAlttabTransitions); } @Override - - public boolean enableWindowingEdgeDragResize() { - return getValue(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, - FeatureFlags::enableWindowingEdgeDragResize); + + public boolean enableDesktopAppLaunchAlttabTransitionsBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX, + FeatureFlags::enableDesktopAppLaunchAlttabTransitionsBugfix); } @Override - - public boolean enableWmExtensionsForAllFlag() { - return getValue(Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, - FeatureFlags::enableWmExtensionsForAllFlag); + + public boolean enableDesktopAppLaunchTransitions() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + FeatureFlags::enableDesktopAppLaunchTransitions); } @Override - - public boolean enforceEdgeToEdge() { - return getValue(Flags.FLAG_ENFORCE_EDGE_TO_EDGE, - FeatureFlags::enforceEdgeToEdge); + + public boolean enableDesktopAppLaunchTransitionsBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + FeatureFlags::enableDesktopAppLaunchTransitionsBugfix); } @Override - - public boolean ensureWallpaperInTransitions() { - return getValue(Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, - FeatureFlags::ensureWallpaperInTransitions); + + public boolean enableDesktopCloseShortcutBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX, + FeatureFlags::enableDesktopCloseShortcutBugfix); } @Override - - public boolean explicitRefreshRateHints() { - return getValue(Flags.FLAG_EXPLICIT_REFRESH_RATE_HINTS, - FeatureFlags::explicitRefreshRateHints); + + public boolean enableDesktopCloseTaskAnimationInDtcBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX, + FeatureFlags::enableDesktopCloseTaskAnimationInDtcBugfix); } @Override - - public boolean fifoPriorityForMajorUiProcesses() { - return getValue(Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, - FeatureFlags::fifoPriorityForMajorUiProcesses); + + public boolean enableDesktopImeBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, + FeatureFlags::enableDesktopImeBugfix); } @Override - - public boolean fixNoContainerUpdateWithoutResize() { - return getValue(Flags.FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE, - FeatureFlags::fixNoContainerUpdateWithoutResize); + + public boolean enableDesktopImmersiveDragBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX, + FeatureFlags::enableDesktopImmersiveDragBugfix); } @Override - - public boolean fixPipRestoreToOverlay() { - return getValue(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY, - FeatureFlags::fixPipRestoreToOverlay); + + public boolean enableDesktopIndicatorInSeparateThreadBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX, + FeatureFlags::enableDesktopIndicatorInSeparateThreadBugfix); } @Override - - public boolean fullscreenDimFlag() { - return getValue(Flags.FLAG_FULLSCREEN_DIM_FLAG, - FeatureFlags::fullscreenDimFlag); + + public boolean enableDesktopModeThroughDevOption() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION, + FeatureFlags::enableDesktopModeThroughDevOption); } @Override - - public boolean getDimmerOnClosing() { - return getValue(Flags.FLAG_GET_DIMMER_ON_CLOSING, - FeatureFlags::getDimmerOnClosing); + + public boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX, + FeatureFlags::enableDesktopOpeningDeeplinkMinimizeAnimationBugfix); } @Override - - public boolean immersiveAppRepositioning() { - return getValue(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING, - FeatureFlags::immersiveAppRepositioning); + + public boolean enableDesktopRecentsTransitionsCornersBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX, + FeatureFlags::enableDesktopRecentsTransitionsCornersBugfix); } @Override - - public boolean insetsControlChangedItem() { - return getValue(Flags.FLAG_INSETS_CONTROL_CHANGED_ITEM, - FeatureFlags::insetsControlChangedItem); + + public boolean enableDesktopSwipeBackMinimizeAnimationBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX, + FeatureFlags::enableDesktopSwipeBackMinimizeAnimationBugfix); } @Override - - public boolean insetsControlSeq() { - return getValue(Flags.FLAG_INSETS_CONTROL_SEQ, - FeatureFlags::insetsControlSeq); + + public boolean enableDesktopSystemDialogsTransitions() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS, + FeatureFlags::enableDesktopSystemDialogsTransitions); } @Override - - public boolean insetsDecoupledConfiguration() { - return getValue(Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, - FeatureFlags::insetsDecoupledConfiguration); + + public boolean enableDesktopTabTearingMinimizeAnimationBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, + FeatureFlags::enableDesktopTabTearingMinimizeAnimationBugfix); } @Override - - public boolean introduceSmootherDimmer() { - return getValue(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER, - FeatureFlags::introduceSmootherDimmer); + + public boolean enableDesktopTaskbarOnFreeformDisplays() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS, + FeatureFlags::enableDesktopTaskbarOnFreeformDisplays); } @Override - - public boolean keyguardAppearTransition() { - return getValue(Flags.FLAG_KEYGUARD_APPEAR_TRANSITION, - FeatureFlags::keyguardAppearTransition); + + public boolean enableDesktopTrampolineCloseAnimationBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX, + FeatureFlags::enableDesktopTrampolineCloseAnimationBugfix); } @Override - - public boolean letterboxBackgroundWallpaper() { - return getValue(Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, - FeatureFlags::letterboxBackgroundWallpaper); + + public boolean enableDesktopWallpaperActivityForSystemUser() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + FeatureFlags::enableDesktopWallpaperActivityForSystemUser); } @Override - - public boolean movableCutoutConfiguration() { - return getValue(Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, - FeatureFlags::movableCutoutConfiguration); + + public boolean enableDesktopWindowingAppHandleEducation() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, + FeatureFlags::enableDesktopWindowingAppHandleEducation); } @Override - - public boolean moveAnimationOptionsToChange() { - return getValue(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE, - FeatureFlags::moveAnimationOptionsToChange); + + public boolean enableDesktopWindowingAppToWeb() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB, + FeatureFlags::enableDesktopWindowingAppToWeb); } @Override - - public boolean multiCrop() { - return getValue(Flags.FLAG_MULTI_CROP, - FeatureFlags::multiCrop); + + public boolean enableDesktopWindowingAppToWebEducation() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION, + FeatureFlags::enableDesktopWindowingAppToWebEducation); } @Override - - public boolean navBarTransparentByDefault() { - return getValue(Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, - FeatureFlags::navBarTransparentByDefault); + + public boolean enableDesktopWindowingAppToWebEducationIntegration() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION, + FeatureFlags::enableDesktopWindowingAppToWebEducationIntegration); } @Override - - public boolean noConsecutiveVisibilityEvents() { - return getValue(Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, - FeatureFlags::noConsecutiveVisibilityEvents); + + public boolean enableDesktopWindowingBackNavigation() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + FeatureFlags::enableDesktopWindowingBackNavigation); } @Override - - public boolean noVisibilityEventOnDisplayStateChange() { - return getValue(Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, - FeatureFlags::noVisibilityEventOnDisplayStateChange); + + public boolean enableDesktopWindowingEnterTransitionBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX, + FeatureFlags::enableDesktopWindowingEnterTransitionBugfix); } @Override - - public boolean offloadColorExtraction() { - return getValue(Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, - FeatureFlags::offloadColorExtraction); + + public boolean enableDesktopWindowingEnterTransitions() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS, + FeatureFlags::enableDesktopWindowingEnterTransitions); } @Override - - public boolean predictiveBackSystemAnims() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS, - FeatureFlags::predictiveBackSystemAnims); + + public boolean enableDesktopWindowingExitByMinimizeTransitionBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX, + FeatureFlags::enableDesktopWindowingExitByMinimizeTransitionBugfix); } @Override - - public boolean rearDisplayDisableForceDesktopSystemDecorations() { - return getValue(Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, - FeatureFlags::rearDisplayDisableForceDesktopSystemDecorations); + + public boolean enableDesktopWindowingExitTransitions() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, + FeatureFlags::enableDesktopWindowingExitTransitions); } @Override - - public boolean releaseSnapshotAggressively() { - return getValue(Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, - FeatureFlags::releaseSnapshotAggressively); + + public boolean enableDesktopWindowingExitTransitionsBugfix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, + FeatureFlags::enableDesktopWindowingExitTransitionsBugfix); } @Override - - public boolean removePrepareSurfaceInPlacement() { - return getValue(Flags.FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT, - FeatureFlags::removePrepareSurfaceInPlacement); + + public boolean enableDesktopWindowingHsum() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, + FeatureFlags::enableDesktopWindowingHsum); } @Override - - public boolean screenRecordingCallbacks() { - return getValue(Flags.FLAG_SCREEN_RECORDING_CALLBACKS, - FeatureFlags::screenRecordingCallbacks); + + public boolean enableDesktopWindowingImmersiveHandleHiding() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, + FeatureFlags::enableDesktopWindowingImmersiveHandleHiding); } @Override - - public boolean sdkDesiredPresentTime() { - return getValue(Flags.FLAG_SDK_DESIRED_PRESENT_TIME, - FeatureFlags::sdkDesiredPresentTime); + + public boolean enableDesktopWindowingModalsPolicy() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + FeatureFlags::enableDesktopWindowingModalsPolicy); } @Override - - public boolean secureWindowState() { - return getValue(Flags.FLAG_SECURE_WINDOW_STATE, - FeatureFlags::secureWindowState); + + public boolean enableDesktopWindowingMode() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + FeatureFlags::enableDesktopWindowingMode); } @Override - - public boolean setScPropertiesInClient() { - return getValue(Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, - FeatureFlags::setScPropertiesInClient); + + public boolean enableDesktopWindowingMultiInstanceFeatures() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, + FeatureFlags::enableDesktopWindowingMultiInstanceFeatures); } @Override - - public boolean skipSleepingWhenSwitchingDisplay() { - return getValue(Flags.FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY, - FeatureFlags::skipSleepingWhenSwitchingDisplay); + + public boolean enableDesktopWindowingPersistence() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, + FeatureFlags::enableDesktopWindowingPersistence); } @Override - - public boolean supportsMultiInstanceSystemUi() { - return getValue(Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, - FeatureFlags::supportsMultiInstanceSystemUi); + + public boolean enableDesktopWindowingPip() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, + FeatureFlags::enableDesktopWindowingPip); + } + + @Override + + public boolean enableDesktopWindowingQuickSwitch() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, + FeatureFlags::enableDesktopWindowingQuickSwitch); + } + + @Override + + public boolean enableDesktopWindowingScvhCacheBugFix() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX, + FeatureFlags::enableDesktopWindowingScvhCacheBugFix); + } + + @Override + + public boolean enableDesktopWindowingSizeConstraints() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, + FeatureFlags::enableDesktopWindowingSizeConstraints); + } + + @Override + + public boolean enableDesktopWindowingTaskLimit() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, + FeatureFlags::enableDesktopWindowingTaskLimit); + } + + @Override + + public boolean enableDesktopWindowingTaskbarRunningApps() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, + FeatureFlags::enableDesktopWindowingTaskbarRunningApps); + } + + @Override + + public boolean enableDesktopWindowingTransitions() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS, + FeatureFlags::enableDesktopWindowingTransitions); + } + + @Override + + public boolean enableDesktopWindowingWallpaperActivity() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + FeatureFlags::enableDesktopWindowingWallpaperActivity); + } + + @Override + + public boolean enableDeviceStateAutoRotateSettingLogging() { + return getValue(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING, + FeatureFlags::enableDeviceStateAutoRotateSettingLogging); + } + + @Override + + public boolean enableDeviceStateAutoRotateSettingRefactor() { + return getValue(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR, + FeatureFlags::enableDeviceStateAutoRotateSettingRefactor); + } + + @Override + + public boolean enableDisplayDisconnectInteraction() { + return getValue(Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, + FeatureFlags::enableDisplayDisconnectInteraction); + } + + @Override + + public boolean enableDisplayFocusInShellTransitions() { + return getValue(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, + FeatureFlags::enableDisplayFocusInShellTransitions); + } + + @Override + + public boolean enableDisplayReconnectInteraction() { + return getValue(Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION, + FeatureFlags::enableDisplayReconnectInteraction); + } + + @Override + + public boolean enableDisplayWindowingModeSwitching() { + return getValue(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, + FeatureFlags::enableDisplayWindowingModeSwitching); + } + + @Override + + public boolean enableDragResizeSetUpInBgThread() { + return getValue(Flags.FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD, + FeatureFlags::enableDragResizeSetUpInBgThread); + } + + @Override + + public boolean enableDragToDesktopIncomingTransitionsBugfix() { + return getValue(Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX, + FeatureFlags::enableDragToDesktopIncomingTransitionsBugfix); + } + + @Override + + public boolean enableDragToMaximize() { + return getValue(Flags.FLAG_ENABLE_DRAG_TO_MAXIMIZE, + FeatureFlags::enableDragToMaximize); + } + + @Override + + public boolean enableDynamicRadiusComputationBugfix() { + return getValue(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX, + FeatureFlags::enableDynamicRadiusComputationBugfix); + } + + @Override + + public boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix() { + return getValue(Flags.FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX, + FeatureFlags::enableFullScreenWindowOnRemovingSplitScreenStageBugfix); + } + + @Override + + public boolean enableFullyImmersiveInDesktop() { + return getValue(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, + FeatureFlags::enableFullyImmersiveInDesktop); + } + + @Override + + public boolean enableHandleInputFix() { + return getValue(Flags.FLAG_ENABLE_HANDLE_INPUT_FIX, + FeatureFlags::enableHandleInputFix); + } + + @Override + + public boolean enableHoldToDragAppHandle() { + return getValue(Flags.FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE, + FeatureFlags::enableHoldToDragAppHandle); + } + + @Override + + public boolean enableInputLayerTransitionFix() { + return getValue(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX, + FeatureFlags::enableInputLayerTransitionFix); + } + + @Override + + public boolean enableMinimizeButton() { + return getValue(Flags.FLAG_ENABLE_MINIMIZE_BUTTON, + FeatureFlags::enableMinimizeButton); + } + + @Override + + public boolean enableModalsFullscreenWithPermission() { + return getValue(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, + FeatureFlags::enableModalsFullscreenWithPermission); + } + + @Override + + public boolean enableMoveToNextDisplayShortcut() { + return getValue(Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, + FeatureFlags::enableMoveToNextDisplayShortcut); + } + + @Override + + public boolean enableMultiDisplaySplit() { + return getValue(Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT, + FeatureFlags::enableMultiDisplaySplit); + } + + @Override + + public boolean enableMultidisplayTrackpadBackGesture() { + return getValue(Flags.FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE, + FeatureFlags::enableMultidisplayTrackpadBackGesture); + } + + @Override + + public boolean enableMultipleDesktopsBackend() { + return getValue(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + FeatureFlags::enableMultipleDesktopsBackend); + } + + @Override + + public boolean enableMultipleDesktopsFrontend() { + return getValue(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND, + FeatureFlags::enableMultipleDesktopsFrontend); + } + + @Override + + public boolean enableNonDefaultDisplaySplit() { + return getValue(Flags.FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT, + FeatureFlags::enableNonDefaultDisplaySplit); + } + + @Override + + public boolean enableOpaqueBackgroundForTransparentWindows() { + return getValue(Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS, + FeatureFlags::enableOpaqueBackgroundForTransparentWindows); + } + + @Override + + public boolean enablePerDisplayDesktopWallpaperActivity() { + return getValue(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, + FeatureFlags::enablePerDisplayDesktopWallpaperActivity); + } + + @Override + + public boolean enablePerDisplayPackageContextCacheInStatusbarNotif() { + return getValue(Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF, + FeatureFlags::enablePerDisplayPackageContextCacheInStatusbarNotif); + } + + @Override + + public boolean enablePersistingDisplaySizeForConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS, + FeatureFlags::enablePersistingDisplaySizeForConnectedDisplays); + } + + @Override + + public boolean enablePresentationForConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS, + FeatureFlags::enablePresentationForConnectedDisplays); + } + + @Override + + public boolean enableProjectedDisplayDesktopMode() { + return getValue(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE, + FeatureFlags::enableProjectedDisplayDesktopMode); + } + + @Override + + public boolean enableQuickswitchDesktopSplitBugfix() { + return getValue(Flags.FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX, + FeatureFlags::enableQuickswitchDesktopSplitBugfix); + } + + @Override + + public boolean enableRequestFullscreenBugfix() { + return getValue(Flags.FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX, + FeatureFlags::enableRequestFullscreenBugfix); + } + + @Override + + public boolean enableResizingMetrics() { + return getValue(Flags.FLAG_ENABLE_RESIZING_METRICS, + FeatureFlags::enableResizingMetrics); + } + + @Override + + public boolean enableRestartMenuForConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS, + FeatureFlags::enableRestartMenuForConnectedDisplays); + } + + @Override + + public boolean enableRestoreToPreviousSizeFromDesktopImmersive() { + return getValue(Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, + FeatureFlags::enableRestoreToPreviousSizeFromDesktopImmersive); + } + + @Override + + public boolean enableShellInitialBoundsRegressionBugFix() { + return getValue(Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX, + FeatureFlags::enableShellInitialBoundsRegressionBugFix); + } + + @Override + + public boolean enableSizeCompatModeImprovementsForConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS, + FeatureFlags::enableSizeCompatModeImprovementsForConnectedDisplays); + } + + @Override + + public boolean enableStartLaunchTransitionFromTaskbarBugfix() { + return getValue(Flags.FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX, + FeatureFlags::enableStartLaunchTransitionFromTaskbarBugfix); + } + + @Override + + public boolean enableTaskResizingKeyboardShortcuts() { + return getValue(Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS, + FeatureFlags::enableTaskResizingKeyboardShortcuts); + } + + @Override + + public boolean enableTaskStackObserverInShell() { + return getValue(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, + FeatureFlags::enableTaskStackObserverInShell); + } + + @Override + + public boolean enableTaskbarConnectedDisplays() { + return getValue(Flags.FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS, + FeatureFlags::enableTaskbarConnectedDisplays); + } + + @Override + + public boolean enableTaskbarOverflow() { + return getValue(Flags.FLAG_ENABLE_TASKBAR_OVERFLOW, + FeatureFlags::enableTaskbarOverflow); + } + + @Override + + public boolean enableTaskbarRecentsLayoutTransition() { + return getValue(Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, + FeatureFlags::enableTaskbarRecentsLayoutTransition); + } + + @Override + + public boolean enableThemedAppHeaders() { + return getValue(Flags.FLAG_ENABLE_THEMED_APP_HEADERS, + FeatureFlags::enableThemedAppHeaders); + } + + @Override + + public boolean enableTileResizing() { + return getValue(Flags.FLAG_ENABLE_TILE_RESIZING, + FeatureFlags::enableTileResizing); + } + + @Override + + public boolean enableTopVisibleRootTaskPerUserTracking() { + return getValue(Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING, + FeatureFlags::enableTopVisibleRootTaskPerUserTracking); + } + + @Override + + public boolean enableVisualIndicatorInTransitionBugfix() { + return getValue(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX, + FeatureFlags::enableVisualIndicatorInTransitionBugfix); + } + + @Override + + public boolean enableWindowContextResourcesUpdateOnConfigChange() { + return getValue(Flags.FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE, + FeatureFlags::enableWindowContextResourcesUpdateOnConfigChange); + } + + @Override + + public boolean enableWindowingDynamicInitialBounds() { + return getValue(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + FeatureFlags::enableWindowingDynamicInitialBounds); + } + + @Override + + public boolean enableWindowingEdgeDragResize() { + return getValue(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, + FeatureFlags::enableWindowingEdgeDragResize); + } + + @Override + + public boolean enableWindowingScaledResizing() { + return getValue(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING, + FeatureFlags::enableWindowingScaledResizing); + } + + @Override + + public boolean enableWindowingTransitionHandlersObservers() { + return getValue(Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS, + FeatureFlags::enableWindowingTransitionHandlersObservers); + } + + @Override + + public boolean enforceEdgeToEdge() { + return getValue(Flags.FLAG_ENFORCE_EDGE_TO_EDGE, + FeatureFlags::enforceEdgeToEdge); + } + + @Override + + public boolean ensureKeyguardDoesTransitionStarting() { + return getValue(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING, + FeatureFlags::ensureKeyguardDoesTransitionStarting); + } + + @Override + + public boolean ensureWallpaperInTransitions() { + return getValue(Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, + FeatureFlags::ensureWallpaperInTransitions); + } + + @Override + + public boolean ensureWallpaperInWearTransitions() { + return getValue(Flags.FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS, + FeatureFlags::ensureWallpaperInWearTransitions); + } + + @Override + + public boolean enterDesktopByDefaultOnFreeformDisplays() { + return getValue(Flags.FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS, + FeatureFlags::enterDesktopByDefaultOnFreeformDisplays); + } + + @Override + + public boolean excludeCaptionFromAppBounds() { + return getValue(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + FeatureFlags::excludeCaptionFromAppBounds); + } + + @Override + + public boolean excludeDrawingAppThemeSnapshotFromLock() { + return getValue(Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK, + FeatureFlags::excludeDrawingAppThemeSnapshotFromLock); + } + + @Override + + public boolean excludeTaskFromRecents() { + return getValue(Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS, + FeatureFlags::excludeTaskFromRecents); + } + + @Override + + public boolean fifoPriorityForMajorUiProcesses() { + return getValue(Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, + FeatureFlags::fifoPriorityForMajorUiProcesses); + } + + @Override + + public boolean fixHideOverlayApi() { + return getValue(Flags.FLAG_FIX_HIDE_OVERLAY_API, + FeatureFlags::fixHideOverlayApi); + } + + @Override + + public boolean fixLayoutExistingTask() { + return getValue(Flags.FLAG_FIX_LAYOUT_EXISTING_TASK, + FeatureFlags::fixLayoutExistingTask); + } + + @Override + + public boolean fixViewRootCallTrace() { + return getValue(Flags.FLAG_FIX_VIEW_ROOT_CALL_TRACE, + FeatureFlags::fixViewRootCallTrace); + } + + @Override + + public boolean forceCloseTopTransparentFullscreenTask() { + return getValue(Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK, + FeatureFlags::forceCloseTopTransparentFullscreenTask); + } + + @Override + + public boolean formFactorBasedDesktopFirstSwitch() { + return getValue(Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, + FeatureFlags::formFactorBasedDesktopFirstSwitch); + } + + @Override + + public boolean getDimmerOnClosing() { + return getValue(Flags.FLAG_GET_DIMMER_ON_CLOSING, + FeatureFlags::getDimmerOnClosing); + } + + @Override + + public boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities() { + return getValue(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, + FeatureFlags::ignoreAspectRatioRestrictionsForResizeableFreeformActivities); + } + + @Override + + public boolean ignoreCornerRadiusAndShadows() { + return getValue(Flags.FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS, + FeatureFlags::ignoreCornerRadiusAndShadows); + } + + @Override + + public boolean includeTopTransparentFullscreenTaskInDesktopHeuristic() { + return getValue(Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + FeatureFlags::includeTopTransparentFullscreenTaskInDesktopHeuristic); + } + + @Override + + public boolean inheritTaskBoundsForTrampolineTaskLaunches() { + return getValue(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES, + FeatureFlags::inheritTaskBoundsForTrampolineTaskLaunches); + } + + @Override + + public boolean insetsDecoupledConfiguration() { + return getValue(Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, + FeatureFlags::insetsDecoupledConfiguration); + } + + @Override + + public boolean jankApi() { + return getValue(Flags.FLAG_JANK_API, + FeatureFlags::jankApi); + } + + @Override + + public boolean keepAppWindowHideWhileLocked() { + return getValue(Flags.FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED, + FeatureFlags::keepAppWindowHideWhileLocked); + } + + @Override + + public boolean keyboardShortcutsToSwitchDesks() { + return getValue(Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS, + FeatureFlags::keyboardShortcutsToSwitchDesks); + } + + @Override + + public boolean keyguardGoingAwayTimeout() { + return getValue(Flags.FLAG_KEYGUARD_GOING_AWAY_TIMEOUT, + FeatureFlags::keyguardGoingAwayTimeout); + } + + @Override + + public boolean letterboxBackgroundWallpaper() { + return getValue(Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, + FeatureFlags::letterboxBackgroundWallpaper); + } + + @Override + + public boolean movableCutoutConfiguration() { + return getValue(Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, + FeatureFlags::movableCutoutConfiguration); + } + + @Override + + public boolean moveToExternalDisplayShortcut() { + return getValue(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, + FeatureFlags::moveToExternalDisplayShortcut); + } + + @Override + + public boolean multiCrop() { + return getValue(Flags.FLAG_MULTI_CROP, + FeatureFlags::multiCrop); + } + + @Override + + public boolean navBarTransparentByDefault() { + return getValue(Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, + FeatureFlags::navBarTransparentByDefault); + } + + @Override + + public boolean nestedTasksWithIndependentBounds() { + return getValue(Flags.FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS, + FeatureFlags::nestedTasksWithIndependentBounds); + } + + @Override + + public boolean noConsecutiveVisibilityEvents() { + return getValue(Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, + FeatureFlags::noConsecutiveVisibilityEvents); + } + + @Override + + public boolean noDuplicateSurfaceDestroyedEvents() { + return getValue(Flags.FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS, + FeatureFlags::noDuplicateSurfaceDestroyedEvents); + } + + @Override + + public boolean noVisibilityEventOnDisplayStateChange() { + return getValue(Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, + FeatureFlags::noVisibilityEventOnDisplayStateChange); + } + + @Override + + public boolean offloadColorExtraction() { + return getValue(Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, + FeatureFlags::offloadColorExtraction); + } + + @Override + + public boolean portWindowSizeAnimation() { + return getValue(Flags.FLAG_PORT_WINDOW_SIZE_ANIMATION, + FeatureFlags::portWindowSizeAnimation); + } + + @Override + + public boolean predictiveBackDefaultEnableSdk36() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36, + FeatureFlags::predictiveBackDefaultEnableSdk36); + } + + @Override + + public boolean predictiveBackPrioritySystemNavigationObserver() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER, + FeatureFlags::predictiveBackPrioritySystemNavigationObserver); + } + + @Override + + public boolean predictiveBackSwipeEdgeNoneApi() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API, + FeatureFlags::predictiveBackSwipeEdgeNoneApi); + } + + @Override + + public boolean predictiveBackSystemOverrideCallback() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK, + FeatureFlags::predictiveBackSystemOverrideCallback); + } + + @Override + + public boolean predictiveBackThreeButtonNav() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV, + FeatureFlags::predictiveBackThreeButtonNav); + } + + @Override + + public boolean predictiveBackTimestampApi() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API, + FeatureFlags::predictiveBackTimestampApi); + } + + @Override + + public boolean processPriorityPolicyForMultiWindowMode() { + return getValue(Flags.FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE, + FeatureFlags::processPriorityPolicyForMultiWindowMode); + } + + @Override + + public boolean rearDisplayDisableForceDesktopSystemDecorations() { + return getValue(Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, + FeatureFlags::rearDisplayDisableForceDesktopSystemDecorations); + } + + @Override + + public boolean recordTaskSnapshotsBeforeShutdown() { + return getValue(Flags.FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN, + FeatureFlags::recordTaskSnapshotsBeforeShutdown); + } + + @Override + + public boolean reduceChangedExclusionRectsMsgs() { + return getValue(Flags.FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS, + FeatureFlags::reduceChangedExclusionRectsMsgs); + } + + @Override + + public boolean reduceKeyguardTransitions() { + return getValue(Flags.FLAG_REDUCE_KEYGUARD_TRANSITIONS, + FeatureFlags::reduceKeyguardTransitions); + } + + @Override + + public boolean reduceTaskSnapshotMemoryUsage() { + return getValue(Flags.FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE, + FeatureFlags::reduceTaskSnapshotMemoryUsage); + } + + @Override + + public boolean reduceUnnecessaryMeasure() { + return getValue(Flags.FLAG_REDUCE_UNNECESSARY_MEASURE, + FeatureFlags::reduceUnnecessaryMeasure); + } + + @Override + + public boolean relativeInsets() { + return getValue(Flags.FLAG_RELATIVE_INSETS, + FeatureFlags::relativeInsets); + } + + @Override + + public boolean releaseSnapshotAggressively() { + return getValue(Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, + FeatureFlags::releaseSnapshotAggressively); + } + + @Override + + public boolean releaseUserAspectRatioWm() { + return getValue(Flags.FLAG_RELEASE_USER_ASPECT_RATIO_WM, + FeatureFlags::releaseUserAspectRatioWm); + } + + @Override + + public boolean removeActivityStarterDreamCallback() { + return getValue(Flags.FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK, + FeatureFlags::removeActivityStarterDreamCallback); + } + + @Override + + public boolean removeDeferHidingClient() { + return getValue(Flags.FLAG_REMOVE_DEFER_HIDING_CLIENT, + FeatureFlags::removeDeferHidingClient); + } + + @Override + + public boolean removeDepartTargetFromMotion() { + return getValue(Flags.FLAG_REMOVE_DEPART_TARGET_FROM_MOTION, + FeatureFlags::removeDepartTargetFromMotion); + } + + @Override + + public boolean reparentWindowTokenApi() { + return getValue(Flags.FLAG_REPARENT_WINDOW_TOKEN_API, + FeatureFlags::reparentWindowTokenApi); + } + + @Override + + public boolean respectNonTopVisibleFixedOrientation() { + return getValue(Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION, + FeatureFlags::respectNonTopVisibleFixedOrientation); + } + + @Override + + public boolean respectOrientationChangeForUnresizeable() { + return getValue(Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE, + FeatureFlags::respectOrientationChangeForUnresizeable); + } + + @Override + + public boolean safeRegionLetterboxing() { + return getValue(Flags.FLAG_SAFE_REGION_LETTERBOXING, + FeatureFlags::safeRegionLetterboxing); + } + + @Override + + public boolean safeReleaseSnapshotAggressively() { + return getValue(Flags.FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY, + FeatureFlags::safeReleaseSnapshotAggressively); + } + + @Override + + public boolean schedulingForNotificationShade() { + return getValue(Flags.FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE, + FeatureFlags::schedulingForNotificationShade); + } + + @Override + + public boolean scrambleSnapshotFileName() { + return getValue(Flags.FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME, + FeatureFlags::scrambleSnapshotFileName); + } + + @Override + + public boolean screenRecordingCallbacks() { + return getValue(Flags.FLAG_SCREEN_RECORDING_CALLBACKS, + FeatureFlags::screenRecordingCallbacks); + } + + @Override + + public boolean scrollingFromLetterbox() { + return getValue(Flags.FLAG_SCROLLING_FROM_LETTERBOX, + FeatureFlags::scrollingFromLetterbox); + } + + @Override + + public boolean sdkDesiredPresentTime() { + return getValue(Flags.FLAG_SDK_DESIRED_PRESENT_TIME, + FeatureFlags::sdkDesiredPresentTime); + } + + @Override + + public boolean setScPropertiesInClient() { + return getValue(Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, + FeatureFlags::setScPropertiesInClient); + } + + @Override + + public boolean showAppHandleLargeScreens() { + return getValue(Flags.FLAG_SHOW_APP_HANDLE_LARGE_SCREENS, + FeatureFlags::showAppHandleLargeScreens); + } + + @Override + + public boolean showDesktopExperienceDevOption() { + return getValue(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION, + FeatureFlags::showDesktopExperienceDevOption); + } + + @Override + + public boolean showDesktopWindowingDevOption() { + return getValue(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + FeatureFlags::showDesktopWindowingDevOption); + } + + @Override + + public boolean showHomeBehindDesktop() { + return getValue(Flags.FLAG_SHOW_HOME_BEHIND_DESKTOP, + FeatureFlags::showHomeBehindDesktop); + } + + @Override + + public boolean skipCompatUiEducationInDesktopMode() { + return getValue(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE, + FeatureFlags::skipCompatUiEducationInDesktopMode); + } + + @Override + + public boolean skipDecorViewRelayoutWhenClosingBugfix() { + return getValue(Flags.FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX, + FeatureFlags::skipDecorViewRelayoutWhenClosingBugfix); + } + + @Override + + public boolean supportWidgetIntentsOnConnectedDisplay() { + return getValue(Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY, + FeatureFlags::supportWidgetIntentsOnConnectedDisplay); + } + + @Override + + public boolean supportsDragAssistantToMultiwindow() { + return getValue(Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW, + FeatureFlags::supportsDragAssistantToMultiwindow); + } + + @Override + + public boolean supportsMultiInstanceSystemUi() { + return getValue(Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, + FeatureFlags::supportsMultiInstanceSystemUi); } @Override - + public boolean surfaceControlInputReceiver() { return getValue(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, - FeatureFlags::surfaceControlInputReceiver); + FeatureFlags::surfaceControlInputReceiver); } @Override - + public boolean surfaceTrustedOverlay() { return getValue(Flags.FLAG_SURFACE_TRUSTED_OVERLAY, - FeatureFlags::surfaceTrustedOverlay); + FeatureFlags::surfaceTrustedOverlay); } @Override - + public boolean syncScreenCapture() { return getValue(Flags.FLAG_SYNC_SCREEN_CAPTURE, - FeatureFlags::syncScreenCapture); + FeatureFlags::syncScreenCapture); } @Override - + + public boolean systemUiPostAnimationEnd() { + return getValue(Flags.FLAG_SYSTEM_UI_POST_ANIMATION_END, + FeatureFlags::systemUiPostAnimationEnd); + } + + @Override + public boolean taskFragmentSystemOrganizerFlag() { return getValue(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, - FeatureFlags::taskFragmentSystemOrganizerFlag); + FeatureFlags::taskFragmentSystemOrganizerFlag); + } + + @Override + + public boolean touchPassThroughOptIn() { + return getValue(Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN, + FeatureFlags::touchPassThroughOptIn); + } + + @Override + + public boolean trackSystemUiContextBeforeWms() { + return getValue(Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS, + FeatureFlags::trackSystemUiContextBeforeWms); } @Override - + public boolean transitReadyTracking() { return getValue(Flags.FLAG_TRANSIT_READY_TRACKING, - FeatureFlags::transitReadyTracking); + FeatureFlags::transitReadyTracking); + } + + @Override + + public boolean transitTrackerPlumbing() { + return getValue(Flags.FLAG_TRANSIT_TRACKER_PLUMBING, + FeatureFlags::transitTrackerPlumbing); } @Override - + public boolean trustedPresentationListenerForWindow() { return getValue(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, - FeatureFlags::trustedPresentationListenerForWindow); + FeatureFlags::trustedPresentationListenerForWindow); + } + + @Override + + public boolean unifyBackNavigationTransition() { + return getValue(Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION, + FeatureFlags::unifyBackNavigationTransition); + } + + @Override + + public boolean universalResizableByDefault() { + return getValue(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT, + FeatureFlags::universalResizableByDefault); } @Override - + public boolean untrustedEmbeddingAnyAppPermission() { return getValue(Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, - FeatureFlags::untrustedEmbeddingAnyAppPermission); + FeatureFlags::untrustedEmbeddingAnyAppPermission); } @Override - + public boolean untrustedEmbeddingStateSharing() { return getValue(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, - FeatureFlags::untrustedEmbeddingStateSharing); + FeatureFlags::untrustedEmbeddingStateSharing); } @Override - - public boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { - return getValue(Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, - FeatureFlags::useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds); + + public boolean updateDimsWhenWindowShown() { + return getValue(Flags.FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN, + FeatureFlags::updateDimsWhenWindowShown); } @Override - - public boolean userMinAspectRatioAppDefault() { - return getValue(Flags.FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT, - FeatureFlags::userMinAspectRatioAppDefault); + + public boolean useCachedInsetsForDisplaySwitch() { + return getValue(Flags.FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH, + FeatureFlags::useCachedInsetsForDisplaySwitch); } @Override - - public boolean waitForTransitionOnDisplaySwitch() { - return getValue(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH, - FeatureFlags::waitForTransitionOnDisplaySwitch); + + public boolean useRtFrameCallbackForSplashScreenTransfer() { + return getValue(Flags.FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER, + FeatureFlags::useRtFrameCallbackForSplashScreenTransfer); } @Override - - public boolean wallpaperOffsetAsync() { - return getValue(Flags.FLAG_WALLPAPER_OFFSET_ASYNC, - FeatureFlags::wallpaperOffsetAsync); + + public boolean useTasksDimOnly() { + return getValue(Flags.FLAG_USE_TASKS_DIM_ONLY, + FeatureFlags::useTasksDimOnly); + } + + @Override + + public boolean useVisibleRequestedForProcessTracker() { + return getValue(Flags.FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER, + FeatureFlags::useVisibleRequestedForProcessTracker); + } + + @Override + + public boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { + return getValue(Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, + FeatureFlags::useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds); + } + + @Override + + public boolean vdmForceAppUniversalResizableApi() { + return getValue(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API, + FeatureFlags::vdmForceAppUniversalResizableApi); } @Override - - public boolean windowSessionRelayoutInfo() { - return getValue(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO, - FeatureFlags::windowSessionRelayoutInfo); + + public boolean wallpaperOffsetAsync() { + return getValue(Flags.FLAG_WALLPAPER_OFFSET_ASYNC, + FeatureFlags::wallpaperOffsetAsync); } @Override - - public boolean windowTokenConfigThreadSafe() { - return getValue(Flags.FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE, - FeatureFlags::windowTokenConfigThreadSafe); + + public boolean wlinfoOncreate() { + return getValue(Flags.FLAG_WLINFO_ONCREATE, + FeatureFlags::wlinfoOncreate); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } - private boolean isOptimizationEnabled() { + + private boolean isOptimizationEnabled() { return false; } @@ -762,162 +1889,542 @@ protected boolean getValue(String flagName, Predicate getter) { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, - Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, - Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG, - Flags.FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT, - Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG, - Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, - Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, - Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, - Flags.FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT, - Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, - Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, - Flags.FLAG_APP_COMPAT_PROPERTIES_API, - Flags.FLAG_APP_COMPAT_REFACTORING, - Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, - Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, - Flags.FLAG_BAL_IMPROVED_METRICS, - Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, - Flags.FLAG_BAL_REQUIRE_OPT_IN_SAME_UID, - Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, - Flags.FLAG_BAL_SHOW_TOASTS, - Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, - Flags.FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH, - Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG, - Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, - Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, - Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, - Flags.FLAG_COVER_DISPLAY_OPT_IN, - Flags.FLAG_DEFER_DISPLAY_UPDATES, - Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, - Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, - Flags.FLAG_DELETE_CAPTURE_DISPLAY, - Flags.FLAG_DENSITY_390_API, - Flags.FLAG_DISABLE_OBJECT_POOL, - Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY, - Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, - Flags.FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH, - Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, - Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG, - Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR, - Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, - Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, - Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, - Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_SCALED_RESIZING, - Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, - Flags.FLAG_ENABLE_THEMED_APP_HEADERS, - Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, - Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, - Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, - Flags.FLAG_ENFORCE_EDGE_TO_EDGE, - Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, - Flags.FLAG_EXPLICIT_REFRESH_RATE_HINTS, - Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, - Flags.FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE, - Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY, - Flags.FLAG_FULLSCREEN_DIM_FLAG, - Flags.FLAG_GET_DIMMER_ON_CLOSING, - Flags.FLAG_IMMERSIVE_APP_REPOSITIONING, - Flags.FLAG_INSETS_CONTROL_CHANGED_ITEM, - Flags.FLAG_INSETS_CONTROL_SEQ, - Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, - Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER, - Flags.FLAG_KEYGUARD_APPEAR_TRANSITION, - Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, - Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, - Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE, - Flags.FLAG_MULTI_CROP, - Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, - Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, - Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, - Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, - Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS, - Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, - Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, - Flags.FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT, - Flags.FLAG_SCREEN_RECORDING_CALLBACKS, - Flags.FLAG_SDK_DESIRED_PRESENT_TIME, - Flags.FLAG_SECURE_WINDOW_STATE, - Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, - Flags.FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY, - Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, - Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, - Flags.FLAG_SURFACE_TRUSTED_OVERLAY, - Flags.FLAG_SYNC_SCREEN_CAPTURE, - Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, - Flags.FLAG_TRANSIT_READY_TRACKING, - Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, - Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, - Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, - Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, - Flags.FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT, - Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH, - Flags.FLAG_WALLPAPER_OFFSET_ASYNC, - Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO, - Flags.FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE + Flags.FLAG_ACTION_MODE_EDGE_TO_EDGE, + Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, + Flags.FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH, + Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, + Flags.FLAG_ACTIVITY_EMBEDDING_METRICS, + Flags.FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, + Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, + Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, + Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, + Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, + Flags.FLAG_AOD_TRANSITION, + Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT, + Flags.FLAG_APP_COMPAT_PROPERTIES_API, + Flags.FLAG_APP_COMPAT_REFACTORING, + Flags.FLAG_APP_COMPAT_UI_FRAMEWORK, + Flags.FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE, + Flags.FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE, + Flags.FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER, + Flags.FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS, + Flags.FLAG_BAL_ADDITIONAL_LOGGING, + Flags.FLAG_BAL_ADDITIONAL_START_MODES, + Flags.FLAG_BAL_CLEAR_ALLOWLIST_DURATION, + Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, + Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, + Flags.FLAG_BAL_IMPROVED_METRICS, + Flags.FLAG_BAL_REDUCE_GRACE_PERIOD, + Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, + Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, + Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS, + Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, + Flags.FLAG_BAL_STRICT_MODE_GRACE_PERIOD, + Flags.FLAG_BAL_STRICT_MODE_RO, + Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY, + Flags.FLAG_CACHE_WINDOW_STYLE, + Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, + Flags.FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY, + Flags.FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER, + Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION, + Flags.FLAG_CLEAR_SYSTEM_VIBRATOR, + Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, + Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE, + Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, + Flags.FLAG_COVER_DISPLAY_OPT_IN, + Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, + Flags.FLAG_DELEGATE_BACK_GESTURE_TO_SHELL, + Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, + Flags.FLAG_DELETE_CAPTURE_DISPLAY, + Flags.FLAG_DENSITY_390_API, + Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX, + Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, + Flags.FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE, + Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, + Flags.FLAG_EARLY_LAUNCH_HINT, + Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, + Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS, + Flags.FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, + Flags.FLAG_ENABLE_BORDER_SETTINGS, + Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, + Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API, + Flags.FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX, + Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION, + Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION, + Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS, + Flags.FLAG_ENABLE_CASCADING_WINDOWS, + Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS, + Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, + Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND, + Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, + Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG, + Flags.FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION, + Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS, + Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING, + Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR, + Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, + Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, + Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION, + Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, + Flags.FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD, + Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DRAG_TO_MAXIMIZE, + Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX, + Flags.FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX, + Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, + Flags.FLAG_ENABLE_HANDLE_INPUT_FIX, + Flags.FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE, + Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX, + Flags.FLAG_ENABLE_MINIMIZE_BUTTON, + Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, + Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, + Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT, + Flags.FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND, + Flags.FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT, + Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS, + Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF, + Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE, + Flags.FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX, + Flags.FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX, + Flags.FLAG_ENABLE_RESIZING_METRICS, + Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, + Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX, + Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX, + Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS, + Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, + Flags.FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_TASKBAR_OVERFLOW, + Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, + Flags.FLAG_ENABLE_THEMED_APP_HEADERS, + Flags.FLAG_ENABLE_TILE_RESIZING, + Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING, + Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX, + Flags.FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE, + Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, + Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING, + Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS, + Flags.FLAG_ENFORCE_EDGE_TO_EDGE, + Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING, + Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, + Flags.FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS, + Flags.FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS, + Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK, + Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS, + Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, + Flags.FLAG_FIX_HIDE_OVERLAY_API, + Flags.FLAG_FIX_LAYOUT_EXISTING_TASK, + Flags.FLAG_FIX_VIEW_ROOT_CALL_TRACE, + Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK, + Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, + Flags.FLAG_GET_DIMMER_ON_CLOSING, + Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, + Flags.FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES, + Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, + Flags.FLAG_JANK_API, + Flags.FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED, + Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS, + Flags.FLAG_KEYGUARD_GOING_AWAY_TIMEOUT, + Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, + Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, + Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, + Flags.FLAG_MULTI_CROP, + Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, + Flags.FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS, + Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, + Flags.FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS, + Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, + Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, + Flags.FLAG_PORT_WINDOW_SIZE_ANIMATION, + Flags.FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36, + Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER, + Flags.FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API, + Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK, + Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV, + Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API, + Flags.FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE, + Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, + Flags.FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN, + Flags.FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS, + Flags.FLAG_REDUCE_KEYGUARD_TRANSITIONS, + Flags.FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE, + Flags.FLAG_REDUCE_UNNECESSARY_MEASURE, + Flags.FLAG_RELATIVE_INSETS, + Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, + Flags.FLAG_RELEASE_USER_ASPECT_RATIO_WM, + Flags.FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK, + Flags.FLAG_REMOVE_DEFER_HIDING_CLIENT, + Flags.FLAG_REMOVE_DEPART_TARGET_FROM_MOTION, + Flags.FLAG_REPARENT_WINDOW_TOKEN_API, + Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION, + Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE, + Flags.FLAG_SAFE_REGION_LETTERBOXING, + Flags.FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY, + Flags.FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE, + Flags.FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME, + Flags.FLAG_SCREEN_RECORDING_CALLBACKS, + Flags.FLAG_SCROLLING_FROM_LETTERBOX, + Flags.FLAG_SDK_DESIRED_PRESENT_TIME, + Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, + Flags.FLAG_SHOW_APP_HANDLE_LARGE_SCREENS, + Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION, + Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + Flags.FLAG_SHOW_HOME_BEHIND_DESKTOP, + Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE, + Flags.FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX, + Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY, + Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW, + Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, + Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, + Flags.FLAG_SURFACE_TRUSTED_OVERLAY, + Flags.FLAG_SYNC_SCREEN_CAPTURE, + Flags.FLAG_SYSTEM_UI_POST_ANIMATION_END, + Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, + Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN, + Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS, + Flags.FLAG_TRANSIT_READY_TRACKING, + Flags.FLAG_TRANSIT_TRACKER_PLUMBING, + Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, + Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION, + Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT, + Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, + Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, + Flags.FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN, + Flags.FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH, + Flags.FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER, + Flags.FLAG_USE_TASKS_DIM_ONLY, + Flags.FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER, + Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, + Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API, + Flags.FLAG_WALLPAPER_OFFSET_ASYNC, + Flags.FLAG_WLINFO_ONCREATE ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT, - Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG, - Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, - Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, - Flags.FLAG_APP_COMPAT_PROPERTIES_API, - Flags.FLAG_APP_COMPAT_REFACTORING, - Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG, - Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, - Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, - Flags.FLAG_COVER_DISPLAY_OPT_IN, - Flags.FLAG_DEFER_DISPLAY_UPDATES, - Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, - Flags.FLAG_DELETE_CAPTURE_DISPLAY, - Flags.FLAG_DENSITY_390_API, - Flags.FLAG_DISABLE_OBJECT_POOL, - Flags.FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH, - Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, - Flags.FLAG_ENABLE_SCALED_RESIZING, - Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, - Flags.FLAG_ENFORCE_EDGE_TO_EDGE, - Flags.FLAG_EXPLICIT_REFRESH_RATE_HINTS, - Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, - Flags.FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE, - Flags.FLAG_GET_DIMMER_ON_CLOSING, - Flags.FLAG_INSETS_CONTROL_SEQ, - Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, - Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER, - Flags.FLAG_KEYGUARD_APPEAR_TRANSITION, - Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, - Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, - Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, - Flags.FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT, - Flags.FLAG_SCREEN_RECORDING_CALLBACKS, - Flags.FLAG_SDK_DESIRED_PRESENT_TIME, - Flags.FLAG_SECURE_WINDOW_STATE, - Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, - Flags.FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY, - Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, - Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, - Flags.FLAG_SURFACE_TRUSTED_OVERLAY, - Flags.FLAG_SYNC_SCREEN_CAPTURE, - Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, - Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, - Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, - Flags.FLAG_WALLPAPER_OFFSET_ASYNC, - Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO, - "" - ) + Arrays.asList( + Flags.FLAG_ACTION_MODE_EDGE_TO_EDGE, + Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, + Flags.FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH, + Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, + Flags.FLAG_ACTIVITY_EMBEDDING_METRICS, + Flags.FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, + Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, + Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, + Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, + Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, + Flags.FLAG_AOD_TRANSITION, + Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT, + Flags.FLAG_APP_COMPAT_PROPERTIES_API, + Flags.FLAG_APP_COMPAT_REFACTORING, + Flags.FLAG_APP_COMPAT_UI_FRAMEWORK, + Flags.FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE, + Flags.FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE, + Flags.FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER, + Flags.FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS, + Flags.FLAG_BAL_ADDITIONAL_LOGGING, + Flags.FLAG_BAL_ADDITIONAL_START_MODES, + Flags.FLAG_BAL_CLEAR_ALLOWLIST_DURATION, + Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, + Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, + Flags.FLAG_BAL_IMPROVED_METRICS, + Flags.FLAG_BAL_REDUCE_GRACE_PERIOD, + Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, + Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, + Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS, + Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, + Flags.FLAG_BAL_STRICT_MODE_GRACE_PERIOD, + Flags.FLAG_BAL_STRICT_MODE_RO, + Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY, + Flags.FLAG_CACHE_WINDOW_STYLE, + Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, + Flags.FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY, + Flags.FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER, + Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION, + Flags.FLAG_CLEAR_SYSTEM_VIBRATOR, + Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, + Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE, + Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, + Flags.FLAG_COVER_DISPLAY_OPT_IN, + Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, + Flags.FLAG_DELEGATE_BACK_GESTURE_TO_SHELL, + Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, + Flags.FLAG_DELETE_CAPTURE_DISPLAY, + Flags.FLAG_DENSITY_390_API, + Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX, + Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, + Flags.FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE, + Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, + Flags.FLAG_EARLY_LAUNCH_HINT, + Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, + Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS, + Flags.FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, + Flags.FLAG_ENABLE_BORDER_SETTINGS, + Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, + Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API, + Flags.FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX, + Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION, + Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION, + Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS, + Flags.FLAG_ENABLE_CASCADING_WINDOWS, + Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS, + Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, + Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND, + Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, + Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG, + Flags.FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION, + Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS, + Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING, + Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR, + Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, + Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, + Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION, + Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, + Flags.FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD, + Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DRAG_TO_MAXIMIZE, + Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX, + Flags.FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX, + Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, + Flags.FLAG_ENABLE_HANDLE_INPUT_FIX, + Flags.FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE, + Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX, + Flags.FLAG_ENABLE_MINIMIZE_BUTTON, + Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, + Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, + Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT, + Flags.FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND, + Flags.FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT, + Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS, + Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF, + Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE, + Flags.FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX, + Flags.FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX, + Flags.FLAG_ENABLE_RESIZING_METRICS, + Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, + Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX, + Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX, + Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS, + Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, + Flags.FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS, + Flags.FLAG_ENABLE_TASKBAR_OVERFLOW, + Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, + Flags.FLAG_ENABLE_THEMED_APP_HEADERS, + Flags.FLAG_ENABLE_TILE_RESIZING, + Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING, + Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX, + Flags.FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE, + Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, + Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING, + Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS, + Flags.FLAG_ENFORCE_EDGE_TO_EDGE, + Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING, + Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, + Flags.FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS, + Flags.FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS, + Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK, + Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS, + Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, + Flags.FLAG_FIX_HIDE_OVERLAY_API, + Flags.FLAG_FIX_LAYOUT_EXISTING_TASK, + Flags.FLAG_FIX_VIEW_ROOT_CALL_TRACE, + Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK, + Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, + Flags.FLAG_GET_DIMMER_ON_CLOSING, + Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, + Flags.FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES, + Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, + Flags.FLAG_JANK_API, + Flags.FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED, + Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS, + Flags.FLAG_KEYGUARD_GOING_AWAY_TIMEOUT, + Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, + Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, + Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, + Flags.FLAG_MULTI_CROP, + Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, + Flags.FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS, + Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, + Flags.FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS, + Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, + Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, + Flags.FLAG_PORT_WINDOW_SIZE_ANIMATION, + Flags.FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36, + Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER, + Flags.FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API, + Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK, + Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV, + Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API, + Flags.FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE, + Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, + Flags.FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN, + Flags.FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS, + Flags.FLAG_REDUCE_KEYGUARD_TRANSITIONS, + Flags.FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE, + Flags.FLAG_REDUCE_UNNECESSARY_MEASURE, + Flags.FLAG_RELATIVE_INSETS, + Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, + Flags.FLAG_RELEASE_USER_ASPECT_RATIO_WM, + Flags.FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK, + Flags.FLAG_REMOVE_DEFER_HIDING_CLIENT, + Flags.FLAG_REMOVE_DEPART_TARGET_FROM_MOTION, + Flags.FLAG_REPARENT_WINDOW_TOKEN_API, + Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION, + Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE, + Flags.FLAG_SAFE_REGION_LETTERBOXING, + Flags.FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY, + Flags.FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE, + Flags.FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME, + Flags.FLAG_SCREEN_RECORDING_CALLBACKS, + Flags.FLAG_SCROLLING_FROM_LETTERBOX, + Flags.FLAG_SDK_DESIRED_PRESENT_TIME, + Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, + Flags.FLAG_SHOW_APP_HANDLE_LARGE_SCREENS, + Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION, + Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + Flags.FLAG_SHOW_HOME_BEHIND_DESKTOP, + Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE, + Flags.FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX, + Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY, + Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW, + Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, + Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, + Flags.FLAG_SURFACE_TRUSTED_OVERLAY, + Flags.FLAG_SYNC_SCREEN_CAPTURE, + Flags.FLAG_SYSTEM_UI_POST_ANIMATION_END, + Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, + Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN, + Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS, + Flags.FLAG_TRANSIT_READY_TRACKING, + Flags.FLAG_TRANSIT_TRACKER_PLUMBING, + Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, + Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION, + Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT, + Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, + Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, + Flags.FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN, + Flags.FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH, + Flags.FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER, + Flags.FLAG_USE_TASKS_DIM_ONLY, + Flags.FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER, + Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, + Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API, + Flags.FLAG_WALLPAPER_OFFSET_ASYNC, + Flags.FLAG_WLINFO_ONCREATE, + "" + ) ); } diff --git a/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java b/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java index 3fd351436e7..222ea4b0eca 100644 --- a/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; - /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/window/flags2/FeatureFlags.java b/flags/src/com/android/window/flags2/FeatureFlags.java index bba4aabe121..d8c9510e77c 100644 --- a/flags/src/com/android/window/flags2/FeatureFlags.java +++ b/flags/src/com/android/window/flags2/FeatureFlags.java @@ -3,212 +3,1064 @@ /** @hide */ public interface FeatureFlags { - + + + + boolean actionModeEdgeToEdge(); + + + boolean activityEmbeddingAnimationCustomizationFlag(); - + + + + boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch(); + + + boolean activityEmbeddingInteractiveDividerFlag(); - - boolean activityEmbeddingOverlayPresentationFlag(); - - boolean activitySnapshotByDefault(); - - boolean activityWindowInfoFlag(); - + + + + boolean activityEmbeddingMetrics(); + + + + boolean activityEmbeddingSupportForConnectedDisplays(); + + + boolean allowDisableActivityRecordInputSink(); - + + + boolean allowHideScmButton(); - + + + boolean allowsScreenSizeDecoupledFromStatusBarAndCutout(); - - boolean alwaysDeferTransitionWhenApplyWct(); - + + + boolean alwaysDrawMagnificationFullscreenBorder(); - + + + boolean alwaysUpdateWallpaperPermission(); - + + + + boolean aodTransition(); + + + + boolean appCompatAsyncRelayout(); + + + boolean appCompatPropertiesApi(); - + + + boolean appCompatRefactoring(); - + + + + boolean appCompatUiFramework(); + + + + boolean appHandleNoRelayoutOnExclusionChange(); + + + + boolean applyLifecycleOnPipChange(); + + + + boolean avoidRebindingIntentionallyDisconnectedWallpaper(); + + + + boolean backupAndRestoreForUserAspectRatioSettings(); + + + + boolean balAdditionalLogging(); + + + + boolean balAdditionalStartModes(); + + + + boolean balClearAllowlistDuration(); + + + boolean balDontBringExistingBackgroundTaskStackToFg(); - + + + boolean balImproveRealCallerVisibilityCheck(); - + + + boolean balImprovedMetrics(); - + + + + boolean balReduceGracePeriod(); + + + boolean balRequireOptInByPendingIntentCreator(); - - boolean balRequireOptInSameUid(); - + + + boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid(); - - boolean balShowToasts(); - + + + + boolean balSendIntentWithOptions(); + + + boolean balShowToastsBlocked(); - - boolean blastSyncNotificationShadeOnDisplaySwitch(); - - boolean bundleClientTransactionFlag(); - + + + + boolean balStrictModeGracePeriod(); + + + + boolean balStrictModeRo(); + + + + boolean betterSupportNonMatchParentActivity(); + + + + boolean cacheWindowStyle(); + + + boolean cameraCompatForFreeform(); - + + + + boolean cameraCompatFullscreenPickSameTaskActivity(); + + + + boolean checkDisabledSnapshotsInTaskPersister(); + + + + boolean cleanupDispatchPendingTransactionsRemoteException(); + + + + boolean clearSystemVibrator(); + + + boolean closeToSquareConfigIncludesStatusBar(); - + + + + boolean condenseConfigurationChangeForSimpleMode(); + + + boolean configurableFontScaleDefault(); - + + + boolean coverDisplayOptIn(); - - boolean deferDisplayUpdates(); - + + + boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition(); - + + + + boolean delegateBackGestureToShell(); + + + boolean delegateUnhandledDrags(); - + + + boolean deleteCaptureDisplay(); - + + + boolean density390Api(); - - boolean disableObjectPool(); - - boolean disableThinLetterboxingPolicy(); - + + + + boolean disableDesktopLaunchParamsOutsideDesktopBugFix(); + + + + boolean disableNonResizableAppSnapResizing(); + + + + boolean disableOptOutEdgeToEdge(); + + + boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions(); - - boolean drawSnapshotAspectRatioMatch(); - + + + + boolean earlyLaunchHint(); + + + boolean edgeToEdgeByDefault(); - - boolean embeddedActivityBackNavFlag(); - - boolean enableAdditionalWindowsAboveStatusBar(); - + + + + boolean enableAccessibleCustomHeaders(); + + + + boolean enableActivityEmbeddingSupportForConnectedDisplays(); + + + boolean enableAppHeaderWithTaskDensity(); - + + + + boolean enableBorderSettings(); + + + boolean enableBufferTransformHintFromDisplay(); - + + + + boolean enableBugFixesForSecondaryDisplay(); + + + boolean enableCameraCompatForDesktopWindowing(); - + + + + boolean enableCameraCompatForDesktopWindowingOptOut(); + + + + boolean enableCameraCompatForDesktopWindowingOptOutApi(); + + + + boolean enableCameraCompatTrackTaskAndAppBugfix(); + + + + boolean enableCaptionCompatInsetConversion(); + + + + boolean enableCaptionCompatInsetForceConsumption(); + + + + boolean enableCaptionCompatInsetForceConsumptionAlways(); + + + + boolean enableCascadingWindows(); + + + + boolean enableCompatUiVisibilityStatus(); + + + boolean enableCompatuiSysuiLauncher(); - - boolean enableDesktopWindowingImmersiveHandleHiding(); - - boolean enableDesktopWindowingModalsPolicy(); - - boolean enableDesktopWindowingMode(); - - boolean enableDesktopWindowingQuickSwitch(); - - boolean enableDesktopWindowingScvhCache(); - - boolean enableDesktopWindowingSizeConstraints(); - - boolean enableDesktopWindowingTaskLimit(); - - boolean enableDesktopWindowingTaskbarRunningApps(); - - boolean enableDesktopWindowingWallpaperActivity(); - - boolean enableScaledResizing(); - - boolean enableTaskStackObserverInShell(); - - boolean enableThemedAppHeaders(); - - boolean enableWindowingDynamicInitialBounds(); - - boolean enableWindowingEdgeDragResize(); - - boolean enableWmExtensionsForAllFlag(); - - boolean enforceEdgeToEdge(); - - boolean ensureWallpaperInTransitions(); - - boolean explicitRefreshRateHints(); - - boolean fifoPriorityForMajorUiProcesses(); - - boolean fixNoContainerUpdateWithoutResize(); - - boolean fixPipRestoreToOverlay(); - - boolean fullscreenDimFlag(); - - boolean getDimmerOnClosing(); - - boolean immersiveAppRepositioning(); - - boolean insetsControlChangedItem(); - - boolean insetsControlSeq(); - - boolean insetsDecoupledConfiguration(); - - boolean introduceSmootherDimmer(); - - boolean keyguardAppearTransition(); - - boolean letterboxBackgroundWallpaper(); - - boolean movableCutoutConfiguration(); - - boolean moveAnimationOptionsToChange(); - - boolean multiCrop(); - - boolean navBarTransparentByDefault(); - - boolean noConsecutiveVisibilityEvents(); - - boolean noVisibilityEventOnDisplayStateChange(); - - boolean offloadColorExtraction(); - - boolean predictiveBackSystemAnims(); - - boolean rearDisplayDisableForceDesktopSystemDecorations(); - - boolean releaseSnapshotAggressively(); - - boolean removePrepareSurfaceInPlacement(); - - boolean screenRecordingCallbacks(); - - boolean sdkDesiredPresentTime(); - - boolean secureWindowState(); - - boolean setScPropertiesInClient(); - - boolean skipSleepingWhenSwitchingDisplay(); - - boolean supportsMultiInstanceSystemUi(); - - boolean surfaceControlInputReceiver(); - - boolean surfaceTrustedOverlay(); - - boolean syncScreenCapture(); - - boolean taskFragmentSystemOrganizerFlag(); - - boolean transitReadyTracking(); - - boolean trustedPresentationListenerForWindow(); - - boolean untrustedEmbeddingAnyAppPermission(); - - boolean untrustedEmbeddingStateSharing(); - - boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds(); - - boolean userMinAspectRatioAppDefault(); - - boolean waitForTransitionOnDisplaySwitch(); - - boolean wallpaperOffsetAsync(); - - boolean windowSessionRelayoutInfo(); - - boolean windowTokenConfigThreadSafe(); + + + + boolean enableConnectedDisplaysDnd(); + + + + boolean enableConnectedDisplaysPip(); + + + + boolean enableConnectedDisplaysWindowDrag(); + + + + boolean enableDesktopAppHandleAnimation(); + + + + boolean enableDesktopAppLaunchAlttabTransitions(); + + + + boolean enableDesktopAppLaunchAlttabTransitionsBugfix(); + + + + boolean enableDesktopAppLaunchTransitions(); + + + + boolean enableDesktopAppLaunchTransitionsBugfix(); + + + + boolean enableDesktopCloseShortcutBugfix(); + + + + boolean enableDesktopCloseTaskAnimationInDtcBugfix(); + + + + boolean enableDesktopImeBugfix(); + + + + boolean enableDesktopImmersiveDragBugfix(); + + + + boolean enableDesktopIndicatorInSeparateThreadBugfix(); + + + + boolean enableDesktopModeThroughDevOption(); + + + + boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix(); + + + + boolean enableDesktopRecentsTransitionsCornersBugfix(); + + + + boolean enableDesktopSwipeBackMinimizeAnimationBugfix(); + + + + boolean enableDesktopSystemDialogsTransitions(); + + + + boolean enableDesktopTabTearingMinimizeAnimationBugfix(); + + + + boolean enableDesktopTaskbarOnFreeformDisplays(); + + + + boolean enableDesktopTrampolineCloseAnimationBugfix(); + + + + boolean enableDesktopWallpaperActivityForSystemUser(); + + + + boolean enableDesktopWindowingAppHandleEducation(); + + + + boolean enableDesktopWindowingAppToWeb(); + + + + boolean enableDesktopWindowingAppToWebEducation(); + + + + boolean enableDesktopWindowingAppToWebEducationIntegration(); + + + + boolean enableDesktopWindowingBackNavigation(); + + + + boolean enableDesktopWindowingEnterTransitionBugfix(); + + + + boolean enableDesktopWindowingEnterTransitions(); + + + + boolean enableDesktopWindowingExitByMinimizeTransitionBugfix(); + + + + boolean enableDesktopWindowingExitTransitions(); + + + + boolean enableDesktopWindowingExitTransitionsBugfix(); + + + + boolean enableDesktopWindowingHsum(); + + + + boolean enableDesktopWindowingImmersiveHandleHiding(); + + + + boolean enableDesktopWindowingModalsPolicy(); + + + + boolean enableDesktopWindowingMode(); + + + + boolean enableDesktopWindowingMultiInstanceFeatures(); + + + + boolean enableDesktopWindowingPersistence(); + + + + boolean enableDesktopWindowingPip(); + + + + boolean enableDesktopWindowingQuickSwitch(); + + + + boolean enableDesktopWindowingScvhCacheBugFix(); + + + + boolean enableDesktopWindowingSizeConstraints(); + + + + boolean enableDesktopWindowingTaskLimit(); + + + + boolean enableDesktopWindowingTaskbarRunningApps(); + + + + boolean enableDesktopWindowingTransitions(); + + + + boolean enableDesktopWindowingWallpaperActivity(); + + + + boolean enableDeviceStateAutoRotateSettingLogging(); + + + + boolean enableDeviceStateAutoRotateSettingRefactor(); + + + + boolean enableDisplayDisconnectInteraction(); + + + + boolean enableDisplayFocusInShellTransitions(); + + + + boolean enableDisplayReconnectInteraction(); + + + + boolean enableDisplayWindowingModeSwitching(); + + + + boolean enableDragResizeSetUpInBgThread(); + + + + boolean enableDragToDesktopIncomingTransitionsBugfix(); + + + + boolean enableDragToMaximize(); + + + + boolean enableDynamicRadiusComputationBugfix(); + + + + boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix(); + + + + boolean enableFullyImmersiveInDesktop(); + + + + boolean enableHandleInputFix(); + + + + boolean enableHoldToDragAppHandle(); + + + + boolean enableInputLayerTransitionFix(); + + + + boolean enableMinimizeButton(); + + + + boolean enableModalsFullscreenWithPermission(); + + + + boolean enableMoveToNextDisplayShortcut(); + + + + boolean enableMultiDisplaySplit(); + + + + boolean enableMultidisplayTrackpadBackGesture(); + + + + boolean enableMultipleDesktopsBackend(); + + + + boolean enableMultipleDesktopsFrontend(); + + + + boolean enableNonDefaultDisplaySplit(); + + + + boolean enableOpaqueBackgroundForTransparentWindows(); + + + + boolean enablePerDisplayDesktopWallpaperActivity(); + + + + boolean enablePerDisplayPackageContextCacheInStatusbarNotif(); + + + + boolean enablePersistingDisplaySizeForConnectedDisplays(); + + + + boolean enablePresentationForConnectedDisplays(); + + + + boolean enableProjectedDisplayDesktopMode(); + + + + boolean enableQuickswitchDesktopSplitBugfix(); + + + + boolean enableRequestFullscreenBugfix(); + + + + boolean enableResizingMetrics(); + + + + boolean enableRestartMenuForConnectedDisplays(); + + + + boolean enableRestoreToPreviousSizeFromDesktopImmersive(); + + + + boolean enableShellInitialBoundsRegressionBugFix(); + + + + boolean enableSizeCompatModeImprovementsForConnectedDisplays(); + + + + boolean enableStartLaunchTransitionFromTaskbarBugfix(); + + + + boolean enableTaskResizingKeyboardShortcuts(); + + + + boolean enableTaskStackObserverInShell(); + + + + boolean enableTaskbarConnectedDisplays(); + + + + boolean enableTaskbarOverflow(); + + + + boolean enableTaskbarRecentsLayoutTransition(); + + + + boolean enableThemedAppHeaders(); + + + + boolean enableTileResizing(); + + + + boolean enableTopVisibleRootTaskPerUserTracking(); + + + + boolean enableVisualIndicatorInTransitionBugfix(); + + + + boolean enableWindowContextResourcesUpdateOnConfigChange(); + + + + boolean enableWindowingDynamicInitialBounds(); + + + + boolean enableWindowingEdgeDragResize(); + + + + boolean enableWindowingScaledResizing(); + + + + boolean enableWindowingTransitionHandlersObservers(); + + + + boolean enforceEdgeToEdge(); + + + + boolean ensureKeyguardDoesTransitionStarting(); + + + + boolean ensureWallpaperInTransitions(); + + + + boolean ensureWallpaperInWearTransitions(); + + + + boolean enterDesktopByDefaultOnFreeformDisplays(); + + + + boolean excludeCaptionFromAppBounds(); + + + + boolean excludeDrawingAppThemeSnapshotFromLock(); + + + + boolean excludeTaskFromRecents(); + + + + boolean fifoPriorityForMajorUiProcesses(); + + + + boolean fixHideOverlayApi(); + + + + boolean fixLayoutExistingTask(); + + + + boolean fixViewRootCallTrace(); + + + + boolean forceCloseTopTransparentFullscreenTask(); + + + + boolean formFactorBasedDesktopFirstSwitch(); + + + + boolean getDimmerOnClosing(); + + + + boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities(); + + + + boolean ignoreCornerRadiusAndShadows(); + + + + boolean includeTopTransparentFullscreenTaskInDesktopHeuristic(); + + + + boolean inheritTaskBoundsForTrampolineTaskLaunches(); + + + + boolean insetsDecoupledConfiguration(); + + + + boolean jankApi(); + + + + boolean keepAppWindowHideWhileLocked(); + + + + boolean keyboardShortcutsToSwitchDesks(); + + + + boolean keyguardGoingAwayTimeout(); + + + + boolean letterboxBackgroundWallpaper(); + + + + boolean movableCutoutConfiguration(); + + + + boolean moveToExternalDisplayShortcut(); + + + + boolean multiCrop(); + + + + boolean navBarTransparentByDefault(); + + + + boolean nestedTasksWithIndependentBounds(); + + + + boolean noConsecutiveVisibilityEvents(); + + + + boolean noDuplicateSurfaceDestroyedEvents(); + + + + boolean noVisibilityEventOnDisplayStateChange(); + + + + boolean offloadColorExtraction(); + + + + boolean portWindowSizeAnimation(); + + + + boolean predictiveBackDefaultEnableSdk36(); + + + + boolean predictiveBackPrioritySystemNavigationObserver(); + + + + boolean predictiveBackSwipeEdgeNoneApi(); + + + + boolean predictiveBackSystemOverrideCallback(); + + + + boolean predictiveBackThreeButtonNav(); + + + + boolean predictiveBackTimestampApi(); + + + + boolean processPriorityPolicyForMultiWindowMode(); + + + + boolean rearDisplayDisableForceDesktopSystemDecorations(); + + + + boolean recordTaskSnapshotsBeforeShutdown(); + + + + boolean reduceChangedExclusionRectsMsgs(); + + + + boolean reduceKeyguardTransitions(); + + + + boolean reduceTaskSnapshotMemoryUsage(); + + + + boolean reduceUnnecessaryMeasure(); + + + + boolean relativeInsets(); + + + + boolean releaseSnapshotAggressively(); + + + + boolean releaseUserAspectRatioWm(); + + + + boolean removeActivityStarterDreamCallback(); + + + + boolean removeDeferHidingClient(); + + + + boolean removeDepartTargetFromMotion(); + + + + boolean reparentWindowTokenApi(); + + + + boolean respectNonTopVisibleFixedOrientation(); + + + + boolean respectOrientationChangeForUnresizeable(); + + + + boolean safeRegionLetterboxing(); + + + + boolean safeReleaseSnapshotAggressively(); + + + + boolean schedulingForNotificationShade(); + + + + boolean scrambleSnapshotFileName(); + + + + boolean screenRecordingCallbacks(); + + + + boolean scrollingFromLetterbox(); + + + + boolean sdkDesiredPresentTime(); + + + + boolean setScPropertiesInClient(); + + + + boolean showAppHandleLargeScreens(); + + + + boolean showDesktopExperienceDevOption(); + + + + boolean showDesktopWindowingDevOption(); + + + + boolean showHomeBehindDesktop(); + + + + boolean skipCompatUiEducationInDesktopMode(); + + + + boolean skipDecorViewRelayoutWhenClosingBugfix(); + + + + boolean supportWidgetIntentsOnConnectedDisplay(); + + + + boolean supportsDragAssistantToMultiwindow(); + + + + boolean supportsMultiInstanceSystemUi(); + + + + boolean surfaceControlInputReceiver(); + + + + boolean surfaceTrustedOverlay(); + + + + boolean syncScreenCapture(); + + + + boolean systemUiPostAnimationEnd(); + + + + boolean taskFragmentSystemOrganizerFlag(); + + + + boolean touchPassThroughOptIn(); + + + + boolean trackSystemUiContextBeforeWms(); + + + + boolean transitReadyTracking(); + + + + boolean transitTrackerPlumbing(); + + + + boolean trustedPresentationListenerForWindow(); + + + + boolean unifyBackNavigationTransition(); + + + + boolean universalResizableByDefault(); + + + + boolean untrustedEmbeddingAnyAppPermission(); + + + + boolean untrustedEmbeddingStateSharing(); + + + + boolean updateDimsWhenWindowShown(); + + + + boolean useCachedInsetsForDisplaySwitch(); + + + + boolean useRtFrameCallbackForSplashScreenTransfer(); + + + + boolean useTasksDimOnly(); + + + + boolean useVisibleRequestedForProcessTracker(); + + + + boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds(); + + + + boolean vdmForceAppUniversalResizableApi(); + + + + boolean wallpaperOffsetAsync(); + + + + boolean wlinfoOncreate(); } diff --git a/flags/src/com/android/window/flags2/FeatureFlagsImpl.java b/flags/src/com/android/window/flags2/FeatureFlagsImpl.java index 56df1ad9a19..142a6392983 100644 --- a/flags/src/com/android/window/flags2/FeatureFlagsImpl.java +++ b/flags/src/com/android/window/flags2/FeatureFlagsImpl.java @@ -1,1638 +1,1860 @@ package com.android.window.flags2; // TODO(b/303773055): Remove the annotation after access issue is resolved. - -import com.android.quickstep.util.DeviceConfigHelper; - -import java.nio.file.Files; -import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); - private static volatile boolean isCached = false; - private static volatile boolean accessibility_is_cached = false; - private static volatile boolean large_screen_experiences_app_compat_is_cached = false; - private static volatile boolean lse_desktop_experience_is_cached = false; - private static volatile boolean multitasking_is_cached = false; - private static volatile boolean responsible_apis_is_cached = false; - private static volatile boolean systemui_is_cached = false; - private static volatile boolean wear_frameworks_is_cached = false; - private static volatile boolean window_surfaces_is_cached = false; - private static volatile boolean windowing_frontend_is_cached = false; - private static volatile boolean windowing_sdk_is_cached = false; - private static boolean activityEmbeddingAnimationCustomizationFlag = false; - private static boolean activityEmbeddingInteractiveDividerFlag = true; - private static boolean activityEmbeddingOverlayPresentationFlag = true; - private static boolean allowHideScmButton = true; - private static boolean alwaysDeferTransitionWhenApplyWct = true; - private static boolean alwaysDrawMagnificationFullscreenBorder = true; - private static boolean alwaysUpdateWallpaperPermission = true; - private static boolean balDontBringExistingBackgroundTaskStackToFg = true; - private static boolean balImproveRealCallerVisibilityCheck = true; - private static boolean balImprovedMetrics = true; - private static boolean balRequireOptInByPendingIntentCreator = true; - private static boolean balRequireOptInSameUid = false; - private static boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid = true; - private static boolean balShowToasts = false; - private static boolean balShowToastsBlocked = true; - private static boolean blastSyncNotificationShadeOnDisplaySwitch = true; - private static boolean closeToSquareConfigIncludesStatusBar = false; - private static boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition = true; - private static boolean disableThinLetterboxingPolicy = true; - private static boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions = true; - private static boolean edgeToEdgeByDefault = false; - private static boolean embeddedActivityBackNavFlag = true; - private static boolean enableAdditionalWindowsAboveStatusBar = false; - private static boolean enableAppHeaderWithTaskDensity = true; - private static boolean enableCameraCompatForDesktopWindowing = true; - private static boolean enableCompatuiSysuiLauncher = false; - private static boolean enableDesktopWindowingImmersiveHandleHiding = false; - private static boolean enableDesktopWindowingModalsPolicy = true; - private static boolean enableDesktopWindowingMode = false; - private static boolean enableDesktopWindowingQuickSwitch = false; - private static boolean enableDesktopWindowingScvhCache = false; - private static boolean enableDesktopWindowingSizeConstraints = false; - private static boolean enableDesktopWindowingTaskLimit = true; - private static boolean enableDesktopWindowingTaskbarRunningApps = true; - private static boolean enableDesktopWindowingWallpaperActivity = false; - private static boolean enableTaskStackObserverInShell = true; - private static boolean enableThemedAppHeaders = true; - private static boolean enableWindowingDynamicInitialBounds = false; - private static boolean enableWindowingEdgeDragResize = false; - private static boolean ensureWallpaperInTransitions = false; - private static boolean fixPipRestoreToOverlay = true; - private static boolean fullscreenDimFlag = true; - private static boolean immersiveAppRepositioning = true; - private static boolean insetsControlChangedItem = false; - private static boolean letterboxBackgroundWallpaper = false; - private static boolean moveAnimationOptionsToChange = true; - private static boolean multiCrop = true; - private static boolean navBarTransparentByDefault = true; - private static boolean noConsecutiveVisibilityEvents = true; - private static boolean noVisibilityEventOnDisplayStateChange = true; - private static boolean offloadColorExtraction = false; - private static boolean predictiveBackSystemAnims = true; - private static boolean taskFragmentSystemOrganizerFlag = true; - private static boolean transitReadyTracking = false; - private static boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds = true; - private static boolean userMinAspectRatioAppDefault = true; - private static boolean waitForTransitionOnDisplaySwitch = true; - private static boolean windowTokenConfigThreadSafe = true; - - - private void init() { - isCached = true; - } - - - - - private void load_overrides_accessibility() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - alwaysDrawMagnificationFullscreenBorder = - properties.getBoolean(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, true); - delayNotificationToMagnificationWhenRecentsWindowToFrontTransition = - properties.getBoolean(Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, true); - doNotCheckIntersectionWhenNonMagnifiableWindowTransitions = - properties.getBoolean(Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, true); - useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds = - properties.getBoolean(Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace accessibility " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - accessibility_is_cached = true; - } - - private void load_overrides_large_screen_experiences_app_compat() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - allowHideScmButton = - properties.getBoolean(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, true); - disableThinLetterboxingPolicy = - properties.getBoolean(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY, true); - enableCompatuiSysuiLauncher = - properties.getBoolean(Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, false); - immersiveAppRepositioning = - properties.getBoolean(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING, true); - letterboxBackgroundWallpaper = - properties.getBoolean(Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, false); - userMinAspectRatioAppDefault = - properties.getBoolean(Flags.FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace large_screen_experiences_app_compat " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - large_screen_experiences_app_compat_is_cached = true; - } - - private void load_overrides_lse_desktop_experience() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - enableAdditionalWindowsAboveStatusBar = - properties.getBoolean(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR, false); - enableAppHeaderWithTaskDensity = - properties.getBoolean(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, true); - enableCameraCompatForDesktopWindowing = - properties.getBoolean(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, true); - enableDesktopWindowingImmersiveHandleHiding = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, false); - enableDesktopWindowingModalsPolicy = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, true); - enableDesktopWindowingMode = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, true); - enableDesktopWindowingQuickSwitch = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, false); - enableDesktopWindowingScvhCache = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE, false); - enableDesktopWindowingSizeConstraints = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, false); - enableDesktopWindowingTaskLimit = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, true); - enableDesktopWindowingTaskbarRunningApps = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, true); - enableDesktopWindowingWallpaperActivity = - properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, false); - enableTaskStackObserverInShell = - properties.getBoolean(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, true); - enableThemedAppHeaders = - properties.getBoolean(Flags.FLAG_ENABLE_THEMED_APP_HEADERS, true); - enableWindowingDynamicInitialBounds = - properties.getBoolean(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, false); - enableWindowingEdgeDragResize = - properties.getBoolean(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, false); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace lse_desktop_experience " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - lse_desktop_experience_is_cached = true; - } - - private void load_overrides_multitasking() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace multitasking " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - multitasking_is_cached = true; - } - - private void load_overrides_responsible_apis() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - balDontBringExistingBackgroundTaskStackToFg = - properties.getBoolean(Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, true); - balImproveRealCallerVisibilityCheck = - properties.getBoolean(Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, true); - balImprovedMetrics = - properties.getBoolean(Flags.FLAG_BAL_IMPROVED_METRICS, true); - balRequireOptInByPendingIntentCreator = - properties.getBoolean(Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, true); - balRequireOptInSameUid = - properties.getBoolean(Flags.FLAG_BAL_REQUIRE_OPT_IN_SAME_UID, false); - balRespectAppSwitchStateWhenCheckBoundByForegroundUid = - properties.getBoolean(Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, true); - balShowToasts = - properties.getBoolean(Flags.FLAG_BAL_SHOW_TOASTS, false); - balShowToastsBlocked = - properties.getBoolean(Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace responsible_apis " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - responsible_apis_is_cached = true; - } - - private void load_overrides_systemui() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - multiCrop = - properties.getBoolean(Flags.FLAG_MULTI_CROP, true); - noConsecutiveVisibilityEvents = - properties.getBoolean(Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, true); - offloadColorExtraction = - properties.getBoolean(Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, false); - predictiveBackSystemAnims = - properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace systemui " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - systemui_is_cached = true; - } - - private void load_overrides_wear_frameworks() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - alwaysUpdateWallpaperPermission = - properties.getBoolean(Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, true); - noVisibilityEventOnDisplayStateChange = - properties.getBoolean(Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace wear_frameworks " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - wear_frameworks_is_cached = true; - } - - private void load_overrides_window_surfaces() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace window_surfaces " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - window_surfaces_is_cached = true; - } - - private void load_overrides_windowing_frontend() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - blastSyncNotificationShadeOnDisplaySwitch = - properties.getBoolean(Flags.FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH, true); - closeToSquareConfigIncludesStatusBar = - properties.getBoolean(Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, false); - edgeToEdgeByDefault = - properties.getBoolean(Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, false); - ensureWallpaperInTransitions = - properties.getBoolean(Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, false); - navBarTransparentByDefault = - properties.getBoolean(Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, true); - transitReadyTracking = - properties.getBoolean(Flags.FLAG_TRANSIT_READY_TRACKING, false); - waitForTransitionOnDisplaySwitch = - properties.getBoolean(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace windowing_frontend " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - windowing_frontend_is_cached = true; - } - - private void load_overrides_windowing_sdk() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - activityEmbeddingAnimationCustomizationFlag = - properties.getBoolean(Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, false); - activityEmbeddingInteractiveDividerFlag = - properties.getBoolean(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, true); - activityEmbeddingOverlayPresentationFlag = - properties.getBoolean(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG, true); - alwaysDeferTransitionWhenApplyWct = - properties.getBoolean(Flags.FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT, true); - embeddedActivityBackNavFlag = - properties.getBoolean(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG, true); - fixPipRestoreToOverlay = - properties.getBoolean(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY, true); - fullscreenDimFlag = - properties.getBoolean(Flags.FLAG_FULLSCREEN_DIM_FLAG, true); - insetsControlChangedItem = - properties.getBoolean(Flags.FLAG_INSETS_CONTROL_CHANGED_ITEM, false); - moveAnimationOptionsToChange = - properties.getBoolean(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE, true); - taskFragmentSystemOrganizerFlag = - properties.getBoolean(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, true); - windowTokenConfigThreadSafe = - properties.getBoolean(Flags.FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE, true); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace windowing_sdk " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } catch (SecurityException e) { - // for isolated process case, skip loading flag value from the storage, use the default - } - windowing_sdk_is_cached = true; - } - - @Override - + @Override + + + public boolean actionModeEdgeToEdge() { + return false; + } + + @Override + + public boolean activityEmbeddingAnimationCustomizationFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return activityEmbeddingAnimationCustomizationFlag; + return true; + } + + @Override + + public boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch() { + return false; } @Override - + + public boolean activityEmbeddingInteractiveDividerFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return activityEmbeddingInteractiveDividerFlag; + return true; + } + + @Override + + public boolean activityEmbeddingMetrics() { + return false; } @Override - - public boolean activityEmbeddingOverlayPresentationFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return activityEmbeddingOverlayPresentationFlag; + + public boolean activityEmbeddingSupportForConnectedDisplays() { + return false; } @Override - - public boolean activitySnapshotByDefault() { + + + public boolean allowDisableActivityRecordInputSink() { return true; + } + + @Override + + public boolean allowHideScmButton() { + return true; } @Override - - public boolean activityWindowInfoFlag() { + + + public boolean allowsScreenSizeDecoupledFromStatusBarAndCutout() { return true; + } + @Override + + + public boolean alwaysDrawMagnificationFullscreenBorder() { + return true; } @Override - - public boolean allowDisableActivityRecordInputSink() { + + + public boolean alwaysUpdateWallpaperPermission() { return true; + } + + @Override + + public boolean aodTransition() { + return false; } @Override - - public boolean allowHideScmButton() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!large_screen_experiences_app_compat_is_cached) { - load_overrides_large_screen_experiences_app_compat(); - } - } - return allowHideScmButton; + + public boolean appCompatAsyncRelayout() { + return false; } @Override - - public boolean allowsScreenSizeDecoupledFromStatusBarAndCutout() { + + + public boolean appCompatPropertiesApi() { return true; + } + + @Override + + public boolean appCompatRefactoring() { + return false; } @Override - - public boolean alwaysDeferTransitionWhenApplyWct() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return alwaysDeferTransitionWhenApplyWct; + + public boolean appCompatUiFramework() { + return false; } @Override - - public boolean alwaysDrawMagnificationFullscreenBorder() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return alwaysDrawMagnificationFullscreenBorder; + + public boolean appHandleNoRelayoutOnExclusionChange() { + return false; } @Override - - public boolean alwaysUpdateWallpaperPermission() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!wear_frameworks_is_cached) { - load_overrides_wear_frameworks(); - } - } - return alwaysUpdateWallpaperPermission; + + public boolean applyLifecycleOnPipChange() { + return false; } @Override - - public boolean appCompatPropertiesApi() { + + + public boolean avoidRebindingIntentionallyDisconnectedWallpaper() { return true; + } + + @Override + + public boolean backupAndRestoreForUserAspectRatioSettings() { + return false; } @Override - - public boolean appCompatRefactoring() { + + + public boolean balAdditionalLogging() { return false; + } + + @Override + + public boolean balAdditionalStartModes() { + return true; } @Override - - public boolean balDontBringExistingBackgroundTaskStackToFg() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balDontBringExistingBackgroundTaskStackToFg; + + public boolean balClearAllowlistDuration() { + return false; + } + + @Override + + + public boolean balDontBringExistingBackgroundTaskStackToFg() { + return true; } @Override - + + public boolean balImproveRealCallerVisibilityCheck() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balImproveRealCallerVisibilityCheck; + return true; + } + + @Override + + + public boolean balImprovedMetrics() { + return true; + } + + @Override + + + public boolean balReduceGracePeriod() { + return false; + } + + @Override + + + public boolean balRequireOptInByPendingIntentCreator() { + return true; + } + + @Override + + + public boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { + return true; + } + + @Override + + + public boolean balSendIntentWithOptions() { + return true; + } + + @Override + + + public boolean balShowToastsBlocked() { + return false; + } + + @Override + + + public boolean balStrictModeGracePeriod() { + return true; + } + + @Override + + + public boolean balStrictModeRo() { + return true; + } + + @Override + + + public boolean betterSupportNonMatchParentActivity() { + return true; + } + + @Override + + + public boolean cacheWindowStyle() { + return true; + } + + @Override + + + public boolean cameraCompatForFreeform() { + return false; + } + + @Override + + + public boolean cameraCompatFullscreenPickSameTaskActivity() { + return false; + } + + @Override + + + public boolean checkDisabledSnapshotsInTaskPersister() { + return true; + } + + @Override + + + public boolean cleanupDispatchPendingTransactionsRemoteException() { + return false; + } + + @Override + + + public boolean clearSystemVibrator() { + return true; + } + + @Override + + + public boolean closeToSquareConfigIncludesStatusBar() { + return false; + } + + @Override + + + public boolean condenseConfigurationChangeForSimpleMode() { + return true; + } + + @Override + + + public boolean configurableFontScaleDefault() { + return true; + } + + @Override + + + public boolean coverDisplayOptIn() { + return true; + } + + @Override + + + public boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { + return true; + } + + @Override + + + public boolean delegateBackGestureToShell() { + return false; + } + + @Override + + + public boolean delegateUnhandledDrags() { + return true; + } + + @Override + + + public boolean deleteCaptureDisplay() { + return true; + } + + @Override + + public boolean density390Api() { + return true; + } + + @Override + + + public boolean disableDesktopLaunchParamsOutsideDesktopBugFix() { + return true; + } + + @Override + + + public boolean disableNonResizableAppSnapResizing() { + return true; + } + + @Override + + + public boolean disableOptOutEdgeToEdge() { + return true; + } + + @Override + + + public boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions() { + return false; + } + + @Override + + + public boolean earlyLaunchHint() { + return true; + } + + @Override + + + public boolean edgeToEdgeByDefault() { + return false; + } + + @Override + + + public boolean enableAccessibleCustomHeaders() { + return true; + } + + @Override + + + public boolean enableActivityEmbeddingSupportForConnectedDisplays() { + return false; + } + + @Override + + + public boolean enableAppHeaderWithTaskDensity() { + return true; + } + + @Override + + + public boolean enableBorderSettings() { + return false; + } + + @Override + + + public boolean enableBufferTransformHintFromDisplay() { + return true; + } + + @Override + + + public boolean enableBugFixesForSecondaryDisplay() { + return false; + } + + @Override + + + public boolean enableCameraCompatForDesktopWindowing() { + return true; + } + + @Override + + + public boolean enableCameraCompatForDesktopWindowingOptOut() { + return true; + } + + @Override + + + public boolean enableCameraCompatForDesktopWindowingOptOutApi() { + return false; + } + + @Override + + + public boolean enableCameraCompatTrackTaskAndAppBugfix() { + return false; + } + + @Override + + + public boolean enableCaptionCompatInsetConversion() { + return false; + } + + @Override + + + public boolean enableCaptionCompatInsetForceConsumption() { + return true; + } + + @Override + + + public boolean enableCaptionCompatInsetForceConsumptionAlways() { + return true; + } + + @Override + + + public boolean enableCascadingWindows() { + return true; + } + + @Override + + + public boolean enableCompatUiVisibilityStatus() { + return true; + } + + @Override + + + public boolean enableCompatuiSysuiLauncher() { + return false; + } + + @Override + + + public boolean enableConnectedDisplaysDnd() { + return false; + } + + @Override + + + public boolean enableConnectedDisplaysPip() { + return false; + } + + @Override + + + public boolean enableConnectedDisplaysWindowDrag() { + return false; + } + + @Override + + + public boolean enableDesktopAppHandleAnimation() { + return true; + } + + @Override + + + public boolean enableDesktopAppLaunchAlttabTransitions() { + return false; + } + + @Override + + + public boolean enableDesktopAppLaunchAlttabTransitionsBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopAppLaunchTransitions() { + return false; + } + + @Override + + + public boolean enableDesktopAppLaunchTransitionsBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopCloseShortcutBugfix() { + return false; + } + + @Override + + + public boolean enableDesktopCloseTaskAnimationInDtcBugfix() { + return false; + } + + @Override + + + public boolean enableDesktopImeBugfix() { + return false; + } + + @Override + + + public boolean enableDesktopImmersiveDragBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopIndicatorInSeparateThreadBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopModeThroughDevOption() { + return false; + } + + @Override + + + public boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopRecentsTransitionsCornersBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopSwipeBackMinimizeAnimationBugfix() { + return false; + } + + @Override + + + public boolean enableDesktopSystemDialogsTransitions() { + return true; + } + + @Override + + + public boolean enableDesktopTabTearingMinimizeAnimationBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopTaskbarOnFreeformDisplays() { + return false; + } + + @Override + + + public boolean enableDesktopTrampolineCloseAnimationBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopWallpaperActivityForSystemUser() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingAppHandleEducation() { + return false; + } + + @Override + + + public boolean enableDesktopWindowingAppToWeb() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingAppToWebEducation() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingAppToWebEducationIntegration() { + return false; + } + + @Override + + + public boolean enableDesktopWindowingBackNavigation() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingEnterTransitionBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingEnterTransitions() { + return false; + } + + @Override + + + public boolean enableDesktopWindowingExitByMinimizeTransitionBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingExitTransitions() { + return false; + } + + @Override + + + public boolean enableDesktopWindowingExitTransitionsBugfix() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingHsum() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingImmersiveHandleHiding() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingModalsPolicy() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingMode() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingMultiInstanceFeatures() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingPersistence() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingPip() { + return false; + } + + @Override + + + public boolean enableDesktopWindowingQuickSwitch() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingScvhCacheBugFix() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingSizeConstraints() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingTaskLimit() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingTaskbarRunningApps() { + return true; + } + + @Override + + + public boolean enableDesktopWindowingTransitions() { + return false; + } + + @Override + + + public boolean enableDesktopWindowingWallpaperActivity() { + return true; + } + + @Override + + + public boolean enableDeviceStateAutoRotateSettingLogging() { + return false; + } + + @Override + + + public boolean enableDeviceStateAutoRotateSettingRefactor() { + return false; + } + + @Override + + + public boolean enableDisplayDisconnectInteraction() { + return false; + } + + @Override + + + public boolean enableDisplayFocusInShellTransitions() { + return false; + } + + @Override + + + public boolean enableDisplayReconnectInteraction() { + return false; + } + + @Override + + + public boolean enableDisplayWindowingModeSwitching() { + return false; + } + + @Override + + + public boolean enableDragResizeSetUpInBgThread() { + return true; + } + + @Override + + + public boolean enableDragToDesktopIncomingTransitionsBugfix() { + return true; + } + + @Override + + + public boolean enableDragToMaximize() { + return false; + } + + @Override + + + public boolean enableDynamicRadiusComputationBugfix() { + return false; + } + + @Override + + + public boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix() { + return true; + } + + @Override + + + public boolean enableFullyImmersiveInDesktop() { + return true; + } + + @Override + + + public boolean enableHandleInputFix() { + return true; + } + + @Override + + + public boolean enableHoldToDragAppHandle() { + return true; + } + + @Override + + + public boolean enableInputLayerTransitionFix() { + return true; + } + + @Override + + + public boolean enableMinimizeButton() { + return true; + } + + @Override + + + public boolean enableModalsFullscreenWithPermission() { + return true; + } + + @Override + + + public boolean enableMoveToNextDisplayShortcut() { + return false; + } + + @Override + + + public boolean enableMultiDisplaySplit() { + return false; + } + + @Override + + + public boolean enableMultidisplayTrackpadBackGesture() { + return false; + } + + @Override + + + public boolean enableMultipleDesktopsBackend() { + return false; + } + + @Override + + + public boolean enableMultipleDesktopsFrontend() { + return false; + } + + @Override + + + public boolean enableNonDefaultDisplaySplit() { + return false; + } + + @Override + + + public boolean enableOpaqueBackgroundForTransparentWindows() { + return true; + } + + @Override + + + public boolean enablePerDisplayDesktopWallpaperActivity() { + return false; + } + + @Override + + + public boolean enablePerDisplayPackageContextCacheInStatusbarNotif() { + return false; + } + + @Override + + + public boolean enablePersistingDisplaySizeForConnectedDisplays() { + return false; + } + + @Override + + + public boolean enablePresentationForConnectedDisplays() { + return false; + } + + @Override + + + public boolean enableProjectedDisplayDesktopMode() { + return false; + } + + @Override + + + public boolean enableQuickswitchDesktopSplitBugfix() { + return true; + } + + @Override + + + public boolean enableRequestFullscreenBugfix() { + return true; + } + + @Override + + + public boolean enableResizingMetrics() { + return true; + } + + @Override + + + public boolean enableRestartMenuForConnectedDisplays() { + return false; + } + + @Override + + + public boolean enableRestoreToPreviousSizeFromDesktopImmersive() { + return true; + } + + @Override + + + public boolean enableShellInitialBoundsRegressionBugFix() { + return true; + } + + @Override + + + public boolean enableSizeCompatModeImprovementsForConnectedDisplays() { + return false; + } + + @Override + + + public boolean enableStartLaunchTransitionFromTaskbarBugfix() { + return true; + } + + @Override + + + public boolean enableTaskResizingKeyboardShortcuts() { + return true; + } + + @Override + + + public boolean enableTaskStackObserverInShell() { + return true; + } + + @Override + + + public boolean enableTaskbarConnectedDisplays() { + return false; + } + + @Override + + + public boolean enableTaskbarOverflow() { + return false; + } + + @Override + + + public boolean enableTaskbarRecentsLayoutTransition() { + return true; + } + + @Override + + + public boolean enableThemedAppHeaders() { + return true; + } + + @Override + + + public boolean enableTileResizing() { + return false; + } + + @Override + + + public boolean enableTopVisibleRootTaskPerUserTracking() { + return true; + } + + @Override + + + public boolean enableVisualIndicatorInTransitionBugfix() { + return true; + } + + @Override + + + public boolean enableWindowContextResourcesUpdateOnConfigChange() { + return true; + } + + @Override + + + public boolean enableWindowingDynamicInitialBounds() { + return true; + } + + @Override + + + public boolean enableWindowingEdgeDragResize() { + return true; + } + + @Override + + + public boolean enableWindowingScaledResizing() { + return true; + } + + @Override + + + public boolean enableWindowingTransitionHandlersObservers() { + return false; + } + + @Override + + + public boolean enforceEdgeToEdge() { + return true; + } + + @Override + + + public boolean ensureKeyguardDoesTransitionStarting() { + return false; + } + + @Override + + + public boolean ensureWallpaperInTransitions() { + return true; + } + + @Override + + + public boolean ensureWallpaperInWearTransitions() { + return true; + } + + @Override + + + public boolean enterDesktopByDefaultOnFreeformDisplays() { + return false; + } + + @Override + + + public boolean excludeCaptionFromAppBounds() { + return true; + } + + @Override + + + public boolean excludeDrawingAppThemeSnapshotFromLock() { + return true; + } + + @Override + + + public boolean excludeTaskFromRecents() { + return false; + } + + @Override + + + public boolean fifoPriorityForMajorUiProcesses() { + return false; + } + + @Override + + + public boolean fixHideOverlayApi() { + return true; + } + + @Override + + + public boolean fixLayoutExistingTask() { + return true; + } + + @Override + + + public boolean fixViewRootCallTrace() { + return false; + } + + @Override + + + public boolean forceCloseTopTransparentFullscreenTask() { + return false; + } + + @Override + + + public boolean formFactorBasedDesktopFirstSwitch() { + return false; + } + + @Override + + + public boolean getDimmerOnClosing() { + return true; + } + + @Override + + + public boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities() { + return true; + } + + @Override + + + public boolean ignoreCornerRadiusAndShadows() { + return false; + } + + @Override + + + public boolean includeTopTransparentFullscreenTaskInDesktopHeuristic() { + return true; + } + + @Override + + + public boolean inheritTaskBoundsForTrampolineTaskLaunches() { + return true; + } + + @Override + + + public boolean insetsDecoupledConfiguration() { + return true; + } + + @Override + + + public boolean jankApi() { + return true; + } + + @Override + + + public boolean keepAppWindowHideWhileLocked() { + return true; + } + + @Override + + + public boolean keyboardShortcutsToSwitchDesks() { + return false; + } + + @Override + + + public boolean keyguardGoingAwayTimeout() { + return true; + } + + @Override + + + public boolean letterboxBackgroundWallpaper() { + return false; + } + + @Override + + + public boolean movableCutoutConfiguration() { + return true; + } + + @Override + + + public boolean moveToExternalDisplayShortcut() { + return false; + } + + @Override + + + public boolean multiCrop() { + return true; + } + + @Override + + + public boolean navBarTransparentByDefault() { + return false; + } + + @Override + + + public boolean nestedTasksWithIndependentBounds() { + return false; } @Override - - public boolean balImprovedMetrics() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balImprovedMetrics; + + public boolean noConsecutiveVisibilityEvents() { + return true; } @Override - - public boolean balRequireOptInByPendingIntentCreator() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balRequireOptInByPendingIntentCreator; + + public boolean noDuplicateSurfaceDestroyedEvents() { + return true; } @Override - - public boolean balRequireOptInSameUid() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balRequireOptInSameUid; + + public boolean noVisibilityEventOnDisplayStateChange() { + return true; } @Override - - public boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balRespectAppSwitchStateWhenCheckBoundByForegroundUid; + + public boolean offloadColorExtraction() { + return false; } @Override - - public boolean balShowToasts() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balShowToasts; + + public boolean portWindowSizeAnimation() { + return false; } @Override - - public boolean balShowToastsBlocked() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!responsible_apis_is_cached) { - load_overrides_responsible_apis(); - } - } - return balShowToastsBlocked; + + public boolean predictiveBackDefaultEnableSdk36() { + return true; } @Override - - public boolean blastSyncNotificationShadeOnDisplaySwitch() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_frontend_is_cached) { - load_overrides_windowing_frontend(); - } - } - return blastSyncNotificationShadeOnDisplaySwitch; - } - @Override - - public boolean bundleClientTransactionFlag() { + public boolean predictiveBackPrioritySystemNavigationObserver() { return true; - } @Override - - public boolean cameraCompatForFreeform() { - return true; + + public boolean predictiveBackSwipeEdgeNoneApi() { + return true; } @Override - - public boolean closeToSquareConfigIncludesStatusBar() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_frontend_is_cached) { - load_overrides_windowing_frontend(); - } - } - return closeToSquareConfigIncludesStatusBar; - } - @Override - - public boolean configurableFontScaleDefault() { + public boolean predictiveBackSystemOverrideCallback() { return true; - } @Override - - public boolean coverDisplayOptIn() { - return true; - } - @Override - - public boolean deferDisplayUpdates() { + public boolean predictiveBackThreeButtonNav() { return true; - } @Override - - public boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return delayNotificationToMagnificationWhenRecentsWindowToFrontTransition; - } - @Override - - public boolean delegateUnhandledDrags() { + public boolean predictiveBackTimestampApi() { return true; - } @Override - - public boolean deleteCaptureDisplay() { - return true; - } - @Override - - public boolean density390Api() { + public boolean processPriorityPolicyForMultiWindowMode() { return true; - } @Override - - public boolean disableObjectPool() { - return true; + + public boolean rearDisplayDisableForceDesktopSystemDecorations() { + return true; } @Override - - public boolean disableThinLetterboxingPolicy() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!large_screen_experiences_app_compat_is_cached) { - load_overrides_large_screen_experiences_app_compat(); - } - } - return disableThinLetterboxingPolicy; + + public boolean recordTaskSnapshotsBeforeShutdown() { + return true; } @Override - - public boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return doNotCheckIntersectionWhenNonMagnifiableWindowTransitions; - } - @Override - - public boolean drawSnapshotAspectRatioMatch() { + public boolean reduceChangedExclusionRectsMsgs() { return false; - } @Override - - public boolean edgeToEdgeByDefault() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_frontend_is_cached) { - load_overrides_windowing_frontend(); - } - } - return edgeToEdgeByDefault; + + public boolean reduceKeyguardTransitions() { + return true; } @Override - - public boolean embeddedActivityBackNavFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return embeddedActivityBackNavFlag; + + public boolean reduceTaskSnapshotMemoryUsage() { + return false; } @Override - - public boolean enableAdditionalWindowsAboveStatusBar() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableAdditionalWindowsAboveStatusBar; + + public boolean reduceUnnecessaryMeasure() { + return false; } @Override - - public boolean enableAppHeaderWithTaskDensity() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableAppHeaderWithTaskDensity; + + public boolean relativeInsets() { + return false; } @Override - - public boolean enableBufferTransformHintFromDisplay() { - return true; + + public boolean releaseSnapshotAggressively() { + return true; } @Override - - public boolean enableCameraCompatForDesktopWindowing() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableCameraCompatForDesktopWindowing; + + public boolean releaseUserAspectRatioWm() { + return true; } @Override - - public boolean enableCompatuiSysuiLauncher() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!large_screen_experiences_app_compat_is_cached) { - load_overrides_large_screen_experiences_app_compat(); - } - } - return enableCompatuiSysuiLauncher; + + public boolean removeActivityStarterDreamCallback() { + return false; } @Override - - public boolean enableDesktopWindowingImmersiveHandleHiding() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingImmersiveHandleHiding; + + public boolean removeDeferHidingClient() { + return true; } @Override - - public boolean enableDesktopWindowingModalsPolicy() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingModalsPolicy; + + public boolean removeDepartTargetFromMotion() { + return false; } @Override - - public boolean enableDesktopWindowingMode() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingMode; + + public boolean reparentWindowTokenApi() { + return true; } @Override - - public boolean enableDesktopWindowingQuickSwitch() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingQuickSwitch; + + public boolean respectNonTopVisibleFixedOrientation() { + return true; } @Override - - public boolean enableDesktopWindowingScvhCache() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingScvhCache; + + public boolean respectOrientationChangeForUnresizeable() { + return true; } @Override - - public boolean enableDesktopWindowingSizeConstraints() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingSizeConstraints; + + public boolean safeRegionLetterboxing() { + return false; } @Override - - public boolean enableDesktopWindowingTaskLimit() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingTaskLimit; + + public boolean safeReleaseSnapshotAggressively() { + return false; } @Override - - public boolean enableDesktopWindowingTaskbarRunningApps() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingTaskbarRunningApps; + + public boolean schedulingForNotificationShade() { + return true; } @Override - - public boolean enableDesktopWindowingWallpaperActivity() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableDesktopWindowingWallpaperActivity; - } - @Override - - public boolean enableScaledResizing() { + public boolean scrambleSnapshotFileName() { return false; - } @Override - - public boolean enableTaskStackObserverInShell() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableTaskStackObserverInShell; - - } - @Override - - public boolean enableThemedAppHeaders() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableThemedAppHeaders; + public boolean screenRecordingCallbacks() { + return true; } @Override - - public boolean enableWindowingDynamicInitialBounds() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableWindowingDynamicInitialBounds; + + public boolean scrollingFromLetterbox() { + return false; } @Override - - public boolean enableWindowingEdgeDragResize() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!lse_desktop_experience_is_cached) { - load_overrides_lse_desktop_experience(); - } - } - return enableWindowingEdgeDragResize; - } - @Override - - public boolean enableWmExtensionsForAllFlag() { + public boolean sdkDesiredPresentTime() { return true; - } @Override - - public boolean enforceEdgeToEdge() { - return true; + + public boolean setScPropertiesInClient() { + return false; } @Override - - public boolean ensureWallpaperInTransitions() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_frontend_is_cached) { - load_overrides_windowing_frontend(); - } - } - return ensureWallpaperInTransitions; + + public boolean showAppHandleLargeScreens() { + return false; } @Override - - public boolean explicitRefreshRateHints() { - return true; - } - @Override - - public boolean fifoPriorityForMajorUiProcesses() { + public boolean showDesktopExperienceDevOption() { return false; - } @Override - - public boolean fixNoContainerUpdateWithoutResize() { - return false; + + public boolean showDesktopWindowingDevOption() { + return true; } @Override - - public boolean fixPipRestoreToOverlay() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return fixPipRestoreToOverlay; + + public boolean showHomeBehindDesktop() { + return false; } @Override - - public boolean fullscreenDimFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return fullscreenDimFlag; - } - @Override - - public boolean getDimmerOnClosing() { + public boolean skipCompatUiEducationInDesktopMode() { return true; - } @Override - - public boolean immersiveAppRepositioning() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!large_screen_experiences_app_compat_is_cached) { - load_overrides_large_screen_experiences_app_compat(); - } - } - return immersiveAppRepositioning; + + public boolean skipDecorViewRelayoutWhenClosingBugfix() { + return true; } @Override - - public boolean insetsControlChangedItem() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return insetsControlChangedItem; - } - @Override - - public boolean insetsControlSeq() { + public boolean supportWidgetIntentsOnConnectedDisplay() { return false; - } @Override - - public boolean insetsDecoupledConfiguration() { - return true; - } - @Override - - public boolean introduceSmootherDimmer() { + public boolean supportsDragAssistantToMultiwindow() { return true; - } @Override - - public boolean keyguardAppearTransition() { - return true; + + public boolean supportsMultiInstanceSystemUi() { + return true; } @Override - - public boolean letterboxBackgroundWallpaper() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!large_screen_experiences_app_compat_is_cached) { - load_overrides_large_screen_experiences_app_compat(); - } - } - return letterboxBackgroundWallpaper; - } - @Override - - public boolean movableCutoutConfiguration() { + public boolean surfaceControlInputReceiver() { return true; - } @Override - - public boolean moveAnimationOptionsToChange() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return moveAnimationOptionsToChange; + + public boolean surfaceTrustedOverlay() { + return true; } @Override - - public boolean multiCrop() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return multiCrop; + + public boolean syncScreenCapture() { + return true; } @Override - - public boolean navBarTransparentByDefault() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_frontend_is_cached) { - load_overrides_windowing_frontend(); - } - } - return navBarTransparentByDefault; + + public boolean systemUiPostAnimationEnd() { + return false; } @Override - - public boolean noConsecutiveVisibilityEvents() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return noConsecutiveVisibilityEvents; + + public boolean taskFragmentSystemOrganizerFlag() { + return true; } @Override - - public boolean noVisibilityEventOnDisplayStateChange() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!wear_frameworks_is_cached) { - load_overrides_wear_frameworks(); - } - } - return noVisibilityEventOnDisplayStateChange; + + public boolean touchPassThroughOptIn() { + return true; } @Override - - public boolean offloadColorExtraction() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return offloadColorExtraction; + + public boolean trackSystemUiContextBeforeWms() { + return true; } @Override - - public boolean predictiveBackSystemAnims() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!systemui_is_cached) { - load_overrides_systemui(); - } - } - return predictiveBackSystemAnims; + + public boolean transitReadyTracking() { + return false; } @Override - - public boolean rearDisplayDisableForceDesktopSystemDecorations() { - return true; - } - @Override - - public boolean releaseSnapshotAggressively() { + public boolean transitTrackerPlumbing() { return false; - } @Override - - public boolean removePrepareSurfaceInPlacement() { - return true; - } - @Override - - public boolean screenRecordingCallbacks() { + public boolean trustedPresentationListenerForWindow() { return true; - } @Override - - public boolean sdkDesiredPresentTime() { - return true; - } - @Override - - public boolean secureWindowState() { + public boolean unifyBackNavigationTransition() { return true; - } @Override - - public boolean setScPropertiesInClient() { - return false; - } - @Override - - public boolean skipSleepingWhenSwitchingDisplay() { + public boolean universalResizableByDefault() { return true; - } @Override - - public boolean supportsMultiInstanceSystemUi() { - return true; + + public boolean untrustedEmbeddingAnyAppPermission() { + return false; } @Override - - public boolean surfaceControlInputReceiver() { - return true; - } - @Override - - public boolean surfaceTrustedOverlay() { + public boolean untrustedEmbeddingStateSharing() { return true; - } @Override - - public boolean syncScreenCapture() { - return true; + + public boolean updateDimsWhenWindowShown() { + return false; } @Override - - public boolean taskFragmentSystemOrganizerFlag() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return taskFragmentSystemOrganizerFlag; + + public boolean useCachedInsetsForDisplaySwitch() { + return false; } @Override - - public boolean transitReadyTracking() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_frontend_is_cached) { - load_overrides_windowing_frontend(); - } - } - return transitReadyTracking; - } - @Override - - public boolean trustedPresentationListenerForWindow() { + public boolean useRtFrameCallbackForSplashScreenTransfer() { return true; - } @Override - - public boolean untrustedEmbeddingAnyAppPermission() { - return true; - } - @Override - - public boolean untrustedEmbeddingStateSharing() { + public boolean useTasksDimOnly() { return true; - } @Override - - public boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!accessibility_is_cached) { - load_overrides_accessibility(); - } - } - return useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds; + + public boolean useVisibleRequestedForProcessTracker() { + return false; } @Override - - public boolean userMinAspectRatioAppDefault() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!large_screen_experiences_app_compat_is_cached) { - load_overrides_large_screen_experiences_app_compat(); - } - } - return userMinAspectRatioAppDefault; + + public boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { + return false; } @Override - - public boolean waitForTransitionOnDisplaySwitch() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_frontend_is_cached) { - load_overrides_windowing_frontend(); - } - } - return waitForTransitionOnDisplaySwitch; - } - @Override - - public boolean wallpaperOffsetAsync() { + public boolean vdmForceAppUniversalResizableApi() { return true; - } @Override - - public boolean windowSessionRelayoutInfo() { - return true; + + public boolean wallpaperOffsetAsync() { + return true; } @Override - - public boolean windowTokenConfigThreadSafe() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!windowing_sdk_is_cached) { - load_overrides_windowing_sdk(); - } - } - return windowTokenConfigThreadSafe; + + public boolean wlinfoOncreate() { + return true; } } - - - diff --git a/flags/src/com/android/window/flags2/Flags.java b/flags/src/com/android/window/flags2/Flags.java index 8888b48ce72..5ad83f821d0 100644 --- a/flags/src/com/android/window/flags2/Flags.java +++ b/flags/src/com/android/window/flags2/Flags.java @@ -1,17 +1,20 @@ package com.android.window.flags2; // TODO(b/303773055): Remove the annotation after access issue is resolved. + /** @hide */ public final class Flags { + /** @hide */ + public static final String FLAG_ACTION_MODE_EDGE_TO_EDGE = "com.android.window.flags.action_mode_edge_to_edge"; /** @hide */ public static final String FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG = "com.android.window.flags.activity_embedding_animation_customization_flag"; /** @hide */ - public static final String FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG = "com.android.window.flags.activity_embedding_interactive_divider_flag"; + public static final String FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH = "com.android.window.flags.activity_embedding_delay_task_fragment_finish_for_activity_launch"; /** @hide */ - public static final String FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG = "com.android.window.flags.activity_embedding_overlay_presentation_flag"; + public static final String FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG = "com.android.window.flags.activity_embedding_interactive_divider_flag"; /** @hide */ - public static final String FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT = "com.android.window.flags.activity_snapshot_by_default"; + public static final String FLAG_ACTIVITY_EMBEDDING_METRICS = "com.android.window.flags.activity_embedding_metrics"; /** @hide */ - public static final String FLAG_ACTIVITY_WINDOW_INFO_FLAG = "com.android.window.flags.activity_window_info_flag"; + public static final String FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.activity_embedding_support_for_connected_displays"; /** @hide */ public static final String FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK = "com.android.window.flags.allow_disable_activity_record_input_sink"; /** @hide */ @@ -19,85 +22,211 @@ public final class Flags { /** @hide */ public static final String FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT = "com.android.window.flags.allows_screen_size_decoupled_from_status_bar_and_cutout"; /** @hide */ - public static final String FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT = "com.android.window.flags.always_defer_transition_when_apply_wct"; - /** @hide */ public static final String FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER = "com.android.window.flags.always_draw_magnification_fullscreen_border"; /** @hide */ public static final String FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION = "com.android.window.flags.always_update_wallpaper_permission"; /** @hide */ + public static final String FLAG_AOD_TRANSITION = "com.android.window.flags.aod_transition"; + /** @hide */ + public static final String FLAG_APP_COMPAT_ASYNC_RELAYOUT = "com.android.window.flags.app_compat_async_relayout"; + /** @hide */ public static final String FLAG_APP_COMPAT_PROPERTIES_API = "com.android.window.flags.app_compat_properties_api"; /** @hide */ public static final String FLAG_APP_COMPAT_REFACTORING = "com.android.window.flags.app_compat_refactoring"; /** @hide */ + public static final String FLAG_APP_COMPAT_UI_FRAMEWORK = "com.android.window.flags.app_compat_ui_framework"; + /** @hide */ + public static final String FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE = "com.android.window.flags.app_handle_no_relayout_on_exclusion_change"; + /** @hide */ + public static final String FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE = "com.android.window.flags.apply_lifecycle_on_pip_change"; + /** @hide */ + public static final String FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER = "com.android.window.flags.avoid_rebinding_intentionally_disconnected_wallpaper"; + /** @hide */ + public static final String FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS = "com.android.window.flags.backup_and_restore_for_user_aspect_ratio_settings"; + /** @hide */ + public static final String FLAG_BAL_ADDITIONAL_LOGGING = "com.android.window.flags.bal_additional_logging"; + /** @hide */ + public static final String FLAG_BAL_ADDITIONAL_START_MODES = "com.android.window.flags.bal_additional_start_modes"; + /** @hide */ + public static final String FLAG_BAL_CLEAR_ALLOWLIST_DURATION = "com.android.window.flags.bal_clear_allowlist_duration"; + /** @hide */ public static final String FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG = "com.android.window.flags.bal_dont_bring_existing_background_task_stack_to_fg"; /** @hide */ public static final String FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK = "com.android.window.flags.bal_improve_real_caller_visibility_check"; /** @hide */ public static final String FLAG_BAL_IMPROVED_METRICS = "com.android.window.flags.bal_improved_metrics"; /** @hide */ - public static final String FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR = "com.android.window.flags.bal_require_opt_in_by_pending_intent_creator"; + public static final String FLAG_BAL_REDUCE_GRACE_PERIOD = "com.android.window.flags.bal_reduce_grace_period"; /** @hide */ - public static final String FLAG_BAL_REQUIRE_OPT_IN_SAME_UID = "com.android.window.flags.bal_require_opt_in_same_uid"; + public static final String FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR = "com.android.window.flags.bal_require_opt_in_by_pending_intent_creator"; /** @hide */ public static final String FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID = "com.android.window.flags.bal_respect_app_switch_state_when_check_bound_by_foreground_uid"; /** @hide */ - public static final String FLAG_BAL_SHOW_TOASTS = "com.android.window.flags.bal_show_toasts"; + public static final String FLAG_BAL_SEND_INTENT_WITH_OPTIONS = "com.android.window.flags.bal_send_intent_with_options"; /** @hide */ public static final String FLAG_BAL_SHOW_TOASTS_BLOCKED = "com.android.window.flags.bal_show_toasts_blocked"; /** @hide */ - public static final String FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH = "com.android.window.flags.blast_sync_notification_shade_on_display_switch"; + public static final String FLAG_BAL_STRICT_MODE_GRACE_PERIOD = "com.android.window.flags.bal_strict_mode_grace_period"; + /** @hide */ + public static final String FLAG_BAL_STRICT_MODE_RO = "com.android.window.flags.bal_strict_mode_ro"; /** @hide */ - public static final String FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG = "com.android.window.flags.bundle_client_transaction_flag"; + public static final String FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY = "com.android.window.flags.better_support_non_match_parent_activity"; + /** @hide */ + public static final String FLAG_CACHE_WINDOW_STYLE = "com.android.window.flags.cache_window_style"; /** @hide */ public static final String FLAG_CAMERA_COMPAT_FOR_FREEFORM = "com.android.window.flags.camera_compat_for_freeform"; /** @hide */ + public static final String FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY = "com.android.window.flags.camera_compat_fullscreen_pick_same_task_activity"; + /** @hide */ + public static final String FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER = "com.android.window.flags.check_disabled_snapshots_in_task_persister"; + /** @hide */ + public static final String FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION = "com.android.window.flags.cleanup_dispatch_pending_transactions_remote_exception"; + /** @hide */ + public static final String FLAG_CLEAR_SYSTEM_VIBRATOR = "com.android.window.flags.clear_system_vibrator"; + /** @hide */ public static final String FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR = "com.android.window.flags.close_to_square_config_includes_status_bar"; /** @hide */ + public static final String FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE = "com.android.window.flags.condense_configuration_change_for_simple_mode"; + /** @hide */ public static final String FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT = "com.android.window.flags.configurable_font_scale_default"; /** @hide */ public static final String FLAG_COVER_DISPLAY_OPT_IN = "com.android.window.flags.cover_display_opt_in"; /** @hide */ - public static final String FLAG_DEFER_DISPLAY_UPDATES = "com.android.window.flags.defer_display_updates"; - /** @hide */ public static final String FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION = "com.android.window.flags.delay_notification_to_magnification_when_recents_window_to_front_transition"; /** @hide */ + public static final String FLAG_DELEGATE_BACK_GESTURE_TO_SHELL = "com.android.window.flags.delegate_back_gesture_to_shell"; + /** @hide */ public static final String FLAG_DELEGATE_UNHANDLED_DRAGS = "com.android.window.flags.delegate_unhandled_drags"; /** @hide */ public static final String FLAG_DELETE_CAPTURE_DISPLAY = "com.android.window.flags.delete_capture_display"; /** @hide */ public static final String FLAG_DENSITY_390_API = "com.android.window.flags.density_390_api"; /** @hide */ - public static final String FLAG_DISABLE_OBJECT_POOL = "com.android.window.flags.disable_object_pool"; + public static final String FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX = "com.android.window.flags.disable_desktop_launch_params_outside_desktop_bug_fix"; /** @hide */ - public static final String FLAG_DISABLE_THIN_LETTERBOXING_POLICY = "com.android.window.flags.disable_thin_letterboxing_policy"; + public static final String FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING = "com.android.window.flags.disable_non_resizable_app_snap_resizing"; + /** @hide */ + public static final String FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE = "com.android.window.flags.disable_opt_out_edge_to_edge"; /** @hide */ public static final String FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS = "com.android.window.flags.do_not_check_intersection_when_non_magnifiable_window_transitions"; /** @hide */ - public static final String FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH = "com.android.window.flags.draw_snapshot_aspect_ratio_match"; + public static final String FLAG_EARLY_LAUNCH_HINT = "com.android.window.flags.early_launch_hint"; /** @hide */ public static final String FLAG_EDGE_TO_EDGE_BY_DEFAULT = "com.android.window.flags.edge_to_edge_by_default"; /** @hide */ - public static final String FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG = "com.android.window.flags.embedded_activity_back_nav_flag"; + public static final String FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS = "com.android.window.flags.enable_accessible_custom_headers"; /** @hide */ - public static final String FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR = "com.android.window.flags.enable_additional_windows_above_status_bar"; + public static final String FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_activity_embedding_support_for_connected_displays"; /** @hide */ public static final String FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY = "com.android.window.flags.enable_app_header_with_task_density"; /** @hide */ + public static final String FLAG_ENABLE_BORDER_SETTINGS = "com.android.window.flags.enable_border_settings"; + /** @hide */ public static final String FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY = "com.android.window.flags.enable_buffer_transform_hint_from_display"; /** @hide */ + public static final String FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY = "com.android.window.flags.enable_bug_fixes_for_secondary_display"; + /** @hide */ public static final String FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING = "com.android.window.flags.enable_camera_compat_for_desktop_windowing"; /** @hide */ + public static final String FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT = "com.android.window.flags.enable_camera_compat_for_desktop_windowing_opt_out"; + /** @hide */ + public static final String FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API = "com.android.window.flags.enable_camera_compat_for_desktop_windowing_opt_out_api"; + /** @hide */ + public static final String FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX = "com.android.window.flags.enable_camera_compat_track_task_and_app_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION = "com.android.window.flags.enable_caption_compat_inset_conversion"; + /** @hide */ + public static final String FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION = "com.android.window.flags.enable_caption_compat_inset_force_consumption"; + /** @hide */ + public static final String FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS = "com.android.window.flags.enable_caption_compat_inset_force_consumption_always"; + /** @hide */ + public static final String FLAG_ENABLE_CASCADING_WINDOWS = "com.android.window.flags.enable_cascading_windows"; + /** @hide */ + public static final String FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS = "com.android.window.flags.enable_compat_ui_visibility_status"; + /** @hide */ public static final String FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER = "com.android.window.flags.enable_compatui_sysui_launcher"; /** @hide */ + public static final String FLAG_ENABLE_CONNECTED_DISPLAYS_DND = "com.android.window.flags.enable_connected_displays_dnd"; + /** @hide */ + public static final String FLAG_ENABLE_CONNECTED_DISPLAYS_PIP = "com.android.window.flags.enable_connected_displays_pip"; + /** @hide */ + public static final String FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG = "com.android.window.flags.enable_connected_displays_window_drag"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION = "com.android.window.flags.enable_desktop_app_handle_animation"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS = "com.android.window.flags.enable_desktop_app_launch_alttab_transitions"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_desktop_app_launch_alttab_transitions_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS = "com.android.window.flags.enable_desktop_app_launch_transitions"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_desktop_app_launch_transitions_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX = "com.android.window.flags.enable_desktop_close_shortcut_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX = "com.android.window.flags.enable_desktop_close_task_animation_in_dtc_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_IME_BUGFIX = "com.android.window.flags.enable_desktop_ime_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX = "com.android.window.flags.enable_desktop_immersive_drag_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX = "com.android.window.flags.enable_desktop_indicator_in_separate_thread_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION = "com.android.window.flags.enable_desktop_mode_through_dev_option"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_opening_deeplink_minimize_animation_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX = "com.android.window.flags.enable_desktop_recents_transitions_corners_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_swipe_back_minimize_animation_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS = "com.android.window.flags.enable_desktop_system_dialogs_transitions"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_tab_tearing_minimize_animation_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS = "com.android.window.flags.enable_desktop_taskbar_on_freeform_displays"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_trampoline_close_animation_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER = "com.android.window.flags.enable_desktop_wallpaper_activity_for_system_user"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION = "com.android.window.flags.enable_desktop_windowing_app_handle_education"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB = "com.android.window.flags.enable_desktop_windowing_app_to_web"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION = "com.android.window.flags.enable_desktop_windowing_app_to_web_education"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION = "com.android.window.flags.enable_desktop_windowing_app_to_web_education_integration"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION = "com.android.window.flags.enable_desktop_windowing_back_navigation"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX = "com.android.window.flags.enable_desktop_windowing_enter_transition_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS = "com.android.window.flags.enable_desktop_windowing_enter_transitions"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX = "com.android.window.flags.enable_desktop_windowing_exit_by_minimize_transition_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS = "com.android.window.flags.enable_desktop_windowing_exit_transitions"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_desktop_windowing_exit_transitions_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_HSUM = "com.android.window.flags.enable_desktop_windowing_hsum"; + /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING = "com.android.window.flags.enable_desktop_windowing_immersive_handle_hiding"; /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY = "com.android.window.flags.enable_desktop_windowing_modals_policy"; /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_MODE = "com.android.window.flags.enable_desktop_windowing_mode"; /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES = "com.android.window.flags.enable_desktop_windowing_multi_instance_features"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE = "com.android.window.flags.enable_desktop_windowing_persistence"; + /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_PIP = "com.android.window.flags.enable_desktop_windowing_pip"; + /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH = "com.android.window.flags.enable_desktop_windowing_quick_switch"; /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE = "com.android.window.flags.enable_desktop_windowing_scvh_cache"; + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX = "com.android.window.flags.enable_desktop_windowing_scvh_cache_bug_fix"; /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS = "com.android.window.flags.enable_desktop_windowing_size_constraints"; /** @hide */ @@ -105,81 +234,257 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS = "com.android.window.flags.enable_desktop_windowing_taskbar_running_apps"; /** @hide */ + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS = "com.android.window.flags.enable_desktop_windowing_transitions"; + /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY = "com.android.window.flags.enable_desktop_windowing_wallpaper_activity"; /** @hide */ - public static final String FLAG_ENABLE_SCALED_RESIZING = "com.android.window.flags.enable_scaled_resizing"; + public static final String FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING = "com.android.window.flags.enable_device_state_auto_rotate_setting_logging"; + /** @hide */ + public static final String FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR = "com.android.window.flags.enable_device_state_auto_rotate_setting_refactor"; + /** @hide */ + public static final String FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION = "com.android.window.flags.enable_display_disconnect_interaction"; + /** @hide */ + public static final String FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS = "com.android.window.flags.enable_display_focus_in_shell_transitions"; + /** @hide */ + public static final String FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION = "com.android.window.flags.enable_display_reconnect_interaction"; + /** @hide */ + public static final String FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING = "com.android.window.flags.enable_display_windowing_mode_switching"; + /** @hide */ + public static final String FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD = "com.android.window.flags.enable_drag_resize_set_up_in_bg_thread"; + /** @hide */ + public static final String FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_drag_to_desktop_incoming_transitions_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_DRAG_TO_MAXIMIZE = "com.android.window.flags.enable_drag_to_maximize"; + /** @hide */ + public static final String FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX = "com.android.window.flags.enable_dynamic_radius_computation_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX = "com.android.window.flags.enable_full_screen_window_on_removing_split_screen_stage_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP = "com.android.window.flags.enable_fully_immersive_in_desktop"; + /** @hide */ + public static final String FLAG_ENABLE_HANDLE_INPUT_FIX = "com.android.window.flags.enable_handle_input_fix"; + /** @hide */ + public static final String FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE = "com.android.window.flags.enable_hold_to_drag_app_handle"; + /** @hide */ + public static final String FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX = "com.android.window.flags.enable_input_layer_transition_fix"; + /** @hide */ + public static final String FLAG_ENABLE_MINIMIZE_BUTTON = "com.android.window.flags.enable_minimize_button"; + /** @hide */ + public static final String FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION = "com.android.window.flags.enable_modals_fullscreen_with_permission"; + /** @hide */ + public static final String FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT = "com.android.window.flags.enable_move_to_next_display_shortcut"; + /** @hide */ + public static final String FLAG_ENABLE_MULTI_DISPLAY_SPLIT = "com.android.window.flags.enable_multi_display_split"; + /** @hide */ + public static final String FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE = "com.android.window.flags.enable_multidisplay_trackpad_back_gesture"; + /** @hide */ + public static final String FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND = "com.android.window.flags.enable_multiple_desktops_backend"; + /** @hide */ + public static final String FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND = "com.android.window.flags.enable_multiple_desktops_frontend"; + /** @hide */ + public static final String FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT = "com.android.window.flags.enable_non_default_display_split"; + /** @hide */ + public static final String FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS = "com.android.window.flags.enable_opaque_background_for_transparent_windows"; + /** @hide */ + public static final String FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY = "com.android.window.flags.enable_per_display_desktop_wallpaper_activity"; + /** @hide */ + public static final String FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF = "com.android.window.flags.enable_per_display_package_context_cache_in_statusbar_notif"; + /** @hide */ + public static final String FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_persisting_display_size_for_connected_displays"; + /** @hide */ + public static final String FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_presentation_for_connected_displays"; + /** @hide */ + public static final String FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE = "com.android.window.flags.enable_projected_display_desktop_mode"; + /** @hide */ + public static final String FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX = "com.android.window.flags.enable_quickswitch_desktop_split_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX = "com.android.window.flags.enable_request_fullscreen_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_RESIZING_METRICS = "com.android.window.flags.enable_resizing_metrics"; + /** @hide */ + public static final String FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_restart_menu_for_connected_displays"; + /** @hide */ + public static final String FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE = "com.android.window.flags.enable_restore_to_previous_size_from_desktop_immersive"; + /** @hide */ + public static final String FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX = "com.android.window.flags.enable_shell_initial_bounds_regression_bug_fix"; + /** @hide */ + public static final String FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_size_compat_mode_improvements_for_connected_displays"; + /** @hide */ + public static final String FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX = "com.android.window.flags.enable_start_launch_transition_from_taskbar_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS = "com.android.window.flags.enable_task_resizing_keyboard_shortcuts"; /** @hide */ public static final String FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL = "com.android.window.flags.enable_task_stack_observer_in_shell"; /** @hide */ + public static final String FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_taskbar_connected_displays"; + /** @hide */ + public static final String FLAG_ENABLE_TASKBAR_OVERFLOW = "com.android.window.flags.enable_taskbar_overflow"; + /** @hide */ + public static final String FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION = "com.android.window.flags.enable_taskbar_recents_layout_transition"; + /** @hide */ public static final String FLAG_ENABLE_THEMED_APP_HEADERS = "com.android.window.flags.enable_themed_app_headers"; /** @hide */ + public static final String FLAG_ENABLE_TILE_RESIZING = "com.android.window.flags.enable_tile_resizing"; + /** @hide */ + public static final String FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING = "com.android.window.flags.enable_top_visible_root_task_per_user_tracking"; + /** @hide */ + public static final String FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX = "com.android.window.flags.enable_visual_indicator_in_transition_bugfix"; + /** @hide */ + public static final String FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE = "com.android.window.flags.enable_window_context_resources_update_on_config_change"; + /** @hide */ public static final String FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS = "com.android.window.flags.enable_windowing_dynamic_initial_bounds"; /** @hide */ public static final String FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE = "com.android.window.flags.enable_windowing_edge_drag_resize"; /** @hide */ - public static final String FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG = "com.android.window.flags.enable_wm_extensions_for_all_flag"; + public static final String FLAG_ENABLE_WINDOWING_SCALED_RESIZING = "com.android.window.flags.enable_windowing_scaled_resizing"; + /** @hide */ + public static final String FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS = "com.android.window.flags.enable_windowing_transition_handlers_observers"; /** @hide */ public static final String FLAG_ENFORCE_EDGE_TO_EDGE = "com.android.window.flags.enforce_edge_to_edge"; /** @hide */ + public static final String FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING = "com.android.window.flags.ensure_keyguard_does_transition_starting"; + /** @hide */ public static final String FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS = "com.android.window.flags.ensure_wallpaper_in_transitions"; /** @hide */ - public static final String FLAG_EXPLICIT_REFRESH_RATE_HINTS = "com.android.window.flags.explicit_refresh_rate_hints"; + public static final String FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS = "com.android.window.flags.ensure_wallpaper_in_wear_transitions"; + /** @hide */ + public static final String FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS = "com.android.window.flags.enter_desktop_by_default_on_freeform_displays"; + /** @hide */ + public static final String FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS = "com.android.window.flags.exclude_caption_from_app_bounds"; + /** @hide */ + public static final String FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK = "com.android.window.flags.exclude_drawing_app_theme_snapshot_from_lock"; + /** @hide */ + public static final String FLAG_EXCLUDE_TASK_FROM_RECENTS = "com.android.window.flags.exclude_task_from_recents"; /** @hide */ public static final String FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES = "com.android.window.flags.fifo_priority_for_major_ui_processes"; /** @hide */ - public static final String FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE = "com.android.window.flags.fix_no_container_update_without_resize"; + public static final String FLAG_FIX_HIDE_OVERLAY_API = "com.android.window.flags.fix_hide_overlay_api"; /** @hide */ - public static final String FLAG_FIX_PIP_RESTORE_TO_OVERLAY = "com.android.window.flags.fix_pip_restore_to_overlay"; + public static final String FLAG_FIX_LAYOUT_EXISTING_TASK = "com.android.window.flags.fix_layout_existing_task"; /** @hide */ - public static final String FLAG_FULLSCREEN_DIM_FLAG = "com.android.window.flags.fullscreen_dim_flag"; + public static final String FLAG_FIX_VIEW_ROOT_CALL_TRACE = "com.android.window.flags.fix_view_root_call_trace"; + /** @hide */ + public static final String FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK = "com.android.window.flags.force_close_top_transparent_fullscreen_task"; + /** @hide */ + public static final String FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH = "com.android.window.flags.form_factor_based_desktop_first_switch"; /** @hide */ public static final String FLAG_GET_DIMMER_ON_CLOSING = "com.android.window.flags.get_dimmer_on_closing"; /** @hide */ - public static final String FLAG_IMMERSIVE_APP_REPOSITIONING = "com.android.window.flags.immersive_app_repositioning"; + public static final String FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES = "com.android.window.flags.ignore_aspect_ratio_restrictions_for_resizeable_freeform_activities"; /** @hide */ - public static final String FLAG_INSETS_CONTROL_CHANGED_ITEM = "com.android.window.flags.insets_control_changed_item"; + public static final String FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS = "com.android.window.flags.ignore_corner_radius_and_shadows"; /** @hide */ - public static final String FLAG_INSETS_CONTROL_SEQ = "com.android.window.flags.insets_control_seq"; + public static final String FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC = "com.android.window.flags.include_top_transparent_fullscreen_task_in_desktop_heuristic"; + /** @hide */ + public static final String FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES = "com.android.window.flags.inherit_task_bounds_for_trampoline_task_launches"; /** @hide */ public static final String FLAG_INSETS_DECOUPLED_CONFIGURATION = "com.android.window.flags.insets_decoupled_configuration"; /** @hide */ - public static final String FLAG_INTRODUCE_SMOOTHER_DIMMER = "com.android.window.flags.introduce_smoother_dimmer"; + public static final String FLAG_JANK_API = "com.android.window.flags.jank_api"; + /** @hide */ + public static final String FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED = "com.android.window.flags.keep_app_window_hide_while_locked"; /** @hide */ - public static final String FLAG_KEYGUARD_APPEAR_TRANSITION = "com.android.window.flags.keyguard_appear_transition"; + public static final String FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS = "com.android.window.flags.keyboard_shortcuts_to_switch_desks"; + /** @hide */ + public static final String FLAG_KEYGUARD_GOING_AWAY_TIMEOUT = "com.android.window.flags.keyguard_going_away_timeout"; /** @hide */ public static final String FLAG_LETTERBOX_BACKGROUND_WALLPAPER = "com.android.window.flags.letterbox_background_wallpaper"; /** @hide */ public static final String FLAG_MOVABLE_CUTOUT_CONFIGURATION = "com.android.window.flags.movable_cutout_configuration"; /** @hide */ - public static final String FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE = "com.android.window.flags.move_animation_options_to_change"; + public static final String FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT = "com.android.window.flags.move_to_external_display_shortcut"; /** @hide */ public static final String FLAG_MULTI_CROP = "com.android.window.flags.multi_crop"; /** @hide */ public static final String FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT = "com.android.window.flags.nav_bar_transparent_by_default"; /** @hide */ + public static final String FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS = "com.android.window.flags.nested_tasks_with_independent_bounds"; + /** @hide */ public static final String FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS = "com.android.window.flags.no_consecutive_visibility_events"; /** @hide */ + public static final String FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS = "com.android.window.flags.no_duplicate_surface_destroyed_events"; + /** @hide */ public static final String FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE = "com.android.window.flags.no_visibility_event_on_display_state_change"; /** @hide */ public static final String FLAG_OFFLOAD_COLOR_EXTRACTION = "com.android.window.flags.offload_color_extraction"; /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS = "com.android.window.flags.predictive_back_system_anims"; + public static final String FLAG_PORT_WINDOW_SIZE_ANIMATION = "com.android.window.flags.port_window_size_animation"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36 = "com.android.window.flags.predictive_back_default_enable_sdk_36"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER = "com.android.window.flags.predictive_back_priority_system_navigation_observer"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API = "com.android.window.flags.predictive_back_swipe_edge_none_api"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK = "com.android.window.flags.predictive_back_system_override_callback"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV = "com.android.window.flags.predictive_back_three_button_nav"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_TIMESTAMP_API = "com.android.window.flags.predictive_back_timestamp_api"; + /** @hide */ + public static final String FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE = "com.android.window.flags.process_priority_policy_for_multi_window_mode"; /** @hide */ public static final String FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS = "com.android.window.flags.rear_display_disable_force_desktop_system_decorations"; /** @hide */ + public static final String FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN = "com.android.window.flags.record_task_snapshots_before_shutdown"; + /** @hide */ + public static final String FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS = "com.android.window.flags.reduce_changed_exclusion_rects_msgs"; + /** @hide */ + public static final String FLAG_REDUCE_KEYGUARD_TRANSITIONS = "com.android.window.flags.reduce_keyguard_transitions"; + /** @hide */ + public static final String FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE = "com.android.window.flags.reduce_task_snapshot_memory_usage"; + /** @hide */ + public static final String FLAG_REDUCE_UNNECESSARY_MEASURE = "com.android.window.flags.reduce_unnecessary_measure"; + /** @hide */ + public static final String FLAG_RELATIVE_INSETS = "com.android.window.flags.relative_insets"; + /** @hide */ public static final String FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY = "com.android.window.flags.release_snapshot_aggressively"; /** @hide */ - public static final String FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT = "com.android.window.flags.remove_prepare_surface_in_placement"; + public static final String FLAG_RELEASE_USER_ASPECT_RATIO_WM = "com.android.window.flags.release_user_aspect_ratio_wm"; + /** @hide */ + public static final String FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK = "com.android.window.flags.remove_activity_starter_dream_callback"; + /** @hide */ + public static final String FLAG_REMOVE_DEFER_HIDING_CLIENT = "com.android.window.flags.remove_defer_hiding_client"; + /** @hide */ + public static final String FLAG_REMOVE_DEPART_TARGET_FROM_MOTION = "com.android.window.flags.remove_depart_target_from_motion"; + /** @hide */ + public static final String FLAG_REPARENT_WINDOW_TOKEN_API = "com.android.window.flags.reparent_window_token_api"; + /** @hide */ + public static final String FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION = "com.android.window.flags.respect_non_top_visible_fixed_orientation"; + /** @hide */ + public static final String FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE = "com.android.window.flags.respect_orientation_change_for_unresizeable"; + /** @hide */ + public static final String FLAG_SAFE_REGION_LETTERBOXING = "com.android.window.flags.safe_region_letterboxing"; + /** @hide */ + public static final String FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY = "com.android.window.flags.safe_release_snapshot_aggressively"; + /** @hide */ + public static final String FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE = "com.android.window.flags.scheduling_for_notification_shade"; + /** @hide */ + public static final String FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME = "com.android.window.flags.scramble_snapshot_file_name"; /** @hide */ public static final String FLAG_SCREEN_RECORDING_CALLBACKS = "com.android.window.flags.screen_recording_callbacks"; /** @hide */ - public static final String FLAG_SDK_DESIRED_PRESENT_TIME = "com.android.window.flags.sdk_desired_present_time"; + public static final String FLAG_SCROLLING_FROM_LETTERBOX = "com.android.window.flags.scrolling_from_letterbox"; /** @hide */ - public static final String FLAG_SECURE_WINDOW_STATE = "com.android.window.flags.secure_window_state"; + public static final String FLAG_SDK_DESIRED_PRESENT_TIME = "com.android.window.flags.sdk_desired_present_time"; /** @hide */ public static final String FLAG_SET_SC_PROPERTIES_IN_CLIENT = "com.android.window.flags.set_sc_properties_in_client"; /** @hide */ - public static final String FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY = "com.android.window.flags.skip_sleeping_when_switching_display"; + public static final String FLAG_SHOW_APP_HANDLE_LARGE_SCREENS = "com.android.window.flags.show_app_handle_large_screens"; + /** @hide */ + public static final String FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION = "com.android.window.flags.show_desktop_experience_dev_option"; + /** @hide */ + public static final String FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION = "com.android.window.flags.show_desktop_windowing_dev_option"; + /** @hide */ + public static final String FLAG_SHOW_HOME_BEHIND_DESKTOP = "com.android.window.flags.show_home_behind_desktop"; + /** @hide */ + public static final String FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE = "com.android.window.flags.skip_compat_ui_education_in_desktop_mode"; + /** @hide */ + public static final String FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX = "com.android.window.flags.skip_decor_view_relayout_when_closing_bugfix"; + /** @hide */ + public static final String FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY = "com.android.window.flags.support_widget_intents_on_connected_display"; + /** @hide */ + public static final String FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW = "com.android.window.flags.supports_drag_assistant_to_multiwindow"; /** @hide */ public static final String FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "com.android.window.flags.supports_multi_instance_system_ui"; /** @hide */ @@ -189,442 +494,1899 @@ public final class Flags { /** @hide */ public static final String FLAG_SYNC_SCREEN_CAPTURE = "com.android.window.flags.sync_screen_capture"; /** @hide */ + public static final String FLAG_SYSTEM_UI_POST_ANIMATION_END = "com.android.window.flags.system_ui_post_animation_end"; + /** @hide */ public static final String FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG = "com.android.window.flags.task_fragment_system_organizer_flag"; /** @hide */ + public static final String FLAG_TOUCH_PASS_THROUGH_OPT_IN = "com.android.window.flags.touch_pass_through_opt_in"; + /** @hide */ + public static final String FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS = "com.android.window.flags.track_system_ui_context_before_wms"; + /** @hide */ public static final String FLAG_TRANSIT_READY_TRACKING = "com.android.window.flags.transit_ready_tracking"; /** @hide */ + public static final String FLAG_TRANSIT_TRACKER_PLUMBING = "com.android.window.flags.transit_tracker_plumbing"; + /** @hide */ public static final String FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW = "com.android.window.flags.trusted_presentation_listener_for_window"; /** @hide */ + public static final String FLAG_UNIFY_BACK_NAVIGATION_TRANSITION = "com.android.window.flags.unify_back_navigation_transition"; + /** @hide */ + public static final String FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT = "com.android.window.flags.universal_resizable_by_default"; + /** @hide */ public static final String FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION = "com.android.window.flags.untrusted_embedding_any_app_permission"; /** @hide */ public static final String FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING = "com.android.window.flags.untrusted_embedding_state_sharing"; /** @hide */ - public static final String FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS = "com.android.window.flags.use_window_original_touchable_region_when_magnification_recompute_bounds"; + public static final String FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN = "com.android.window.flags.update_dims_when_window_shown"; /** @hide */ - public static final String FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT = "com.android.window.flags.user_min_aspect_ratio_app_default"; + public static final String FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH = "com.android.window.flags.use_cached_insets_for_display_switch"; /** @hide */ - public static final String FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH = "com.android.window.flags.wait_for_transition_on_display_switch"; + public static final String FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER = "com.android.window.flags.use_rt_frame_callback_for_splash_screen_transfer"; /** @hide */ - public static final String FLAG_WALLPAPER_OFFSET_ASYNC = "com.android.window.flags.wallpaper_offset_async"; + public static final String FLAG_USE_TASKS_DIM_ONLY = "com.android.window.flags.use_tasks_dim_only"; + /** @hide */ + public static final String FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER = "com.android.window.flags.use_visible_requested_for_process_tracker"; /** @hide */ - public static final String FLAG_WINDOW_SESSION_RELAYOUT_INFO = "com.android.window.flags.window_session_relayout_info"; + public static final String FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS = "com.android.window.flags.use_window_original_touchable_region_when_magnification_recompute_bounds"; + /** @hide */ + public static final String FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API = "com.android.window.flags.vdm_force_app_universal_resizable_api"; + /** @hide */ + public static final String FLAG_WALLPAPER_OFFSET_ASYNC = "com.android.window.flags.wallpaper_offset_async"; /** @hide */ - public static final String FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE = "com.android.window.flags.window_token_config_thread_safe"; - + public static final String FLAG_WLINFO_ONCREATE = "com.android.window.flags.wlinfo_oncreate"; + + + + public static boolean actionModeEdgeToEdge() { + + return FEATURE_FLAGS.actionModeEdgeToEdge(); + } + + + public static boolean activityEmbeddingAnimationCustomizationFlag() { + return FEATURE_FLAGS.activityEmbeddingAnimationCustomizationFlag(); } - + + + + public static boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch() { + + return FEATURE_FLAGS.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch(); + } + + + public static boolean activityEmbeddingInteractiveDividerFlag() { + return FEATURE_FLAGS.activityEmbeddingInteractiveDividerFlag(); } - - public static boolean activityEmbeddingOverlayPresentationFlag() { - return FEATURE_FLAGS.activityEmbeddingOverlayPresentationFlag(); - } - - public static boolean activitySnapshotByDefault() { - return FEATURE_FLAGS.activitySnapshotByDefault(); + + + + public static boolean activityEmbeddingMetrics() { + + return FEATURE_FLAGS.activityEmbeddingMetrics(); } - - public static boolean activityWindowInfoFlag() { - return FEATURE_FLAGS.activityWindowInfoFlag(); + + + + public static boolean activityEmbeddingSupportForConnectedDisplays() { + + return FEATURE_FLAGS.activityEmbeddingSupportForConnectedDisplays(); } - + + + public static boolean allowDisableActivityRecordInputSink() { + return FEATURE_FLAGS.allowDisableActivityRecordInputSink(); } - + + + public static boolean allowHideScmButton() { + return FEATURE_FLAGS.allowHideScmButton(); } - + + + public static boolean allowsScreenSizeDecoupledFromStatusBarAndCutout() { + return FEATURE_FLAGS.allowsScreenSizeDecoupledFromStatusBarAndCutout(); } - - public static boolean alwaysDeferTransitionWhenApplyWct() { - return FEATURE_FLAGS.alwaysDeferTransitionWhenApplyWct(); - } - + + + public static boolean alwaysDrawMagnificationFullscreenBorder() { + return FEATURE_FLAGS.alwaysDrawMagnificationFullscreenBorder(); } - + + + public static boolean alwaysUpdateWallpaperPermission() { + return FEATURE_FLAGS.alwaysUpdateWallpaperPermission(); } - + + + + public static boolean aodTransition() { + + return FEATURE_FLAGS.aodTransition(); + } + + + + public static boolean appCompatAsyncRelayout() { + + return FEATURE_FLAGS.appCompatAsyncRelayout(); + } + + + public static boolean appCompatPropertiesApi() { + return FEATURE_FLAGS.appCompatPropertiesApi(); } - + + + public static boolean appCompatRefactoring() { + return FEATURE_FLAGS.appCompatRefactoring(); } - - public static boolean balDontBringExistingBackgroundTaskStackToFg() { - return FEATURE_FLAGS.balDontBringExistingBackgroundTaskStackToFg(); - } - - public static boolean balImproveRealCallerVisibilityCheck() { - return FEATURE_FLAGS.balImproveRealCallerVisibilityCheck(); + + + + public static boolean appCompatUiFramework() { + + return FEATURE_FLAGS.appCompatUiFramework(); } - - public static boolean balImprovedMetrics() { - return FEATURE_FLAGS.balImprovedMetrics(); + + + + public static boolean appHandleNoRelayoutOnExclusionChange() { + + return FEATURE_FLAGS.appHandleNoRelayoutOnExclusionChange(); } - - public static boolean balRequireOptInByPendingIntentCreator() { - return FEATURE_FLAGS.balRequireOptInByPendingIntentCreator(); + + + + public static boolean applyLifecycleOnPipChange() { + + return FEATURE_FLAGS.applyLifecycleOnPipChange(); } - - public static boolean balRequireOptInSameUid() { - return FEATURE_FLAGS.balRequireOptInSameUid(); + + + + public static boolean avoidRebindingIntentionallyDisconnectedWallpaper() { + + return FEATURE_FLAGS.avoidRebindingIntentionallyDisconnectedWallpaper(); } - - public static boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { - return FEATURE_FLAGS.balRespectAppSwitchStateWhenCheckBoundByForegroundUid(); + + + + public static boolean backupAndRestoreForUserAspectRatioSettings() { + + return FEATURE_FLAGS.backupAndRestoreForUserAspectRatioSettings(); } - - public static boolean balShowToasts() { - return FEATURE_FLAGS.balShowToasts(); + + + + public static boolean balAdditionalLogging() { + + return FEATURE_FLAGS.balAdditionalLogging(); } - + + + + public static boolean balAdditionalStartModes() { + + return FEATURE_FLAGS.balAdditionalStartModes(); + } + + + + public static boolean balClearAllowlistDuration() { + + return FEATURE_FLAGS.balClearAllowlistDuration(); + } + + + + public static boolean balDontBringExistingBackgroundTaskStackToFg() { + + return FEATURE_FLAGS.balDontBringExistingBackgroundTaskStackToFg(); + } + + + + public static boolean balImproveRealCallerVisibilityCheck() { + + return FEATURE_FLAGS.balImproveRealCallerVisibilityCheck(); + } + + + + public static boolean balImprovedMetrics() { + + return FEATURE_FLAGS.balImprovedMetrics(); + } + + + + public static boolean balReduceGracePeriod() { + + return FEATURE_FLAGS.balReduceGracePeriod(); + } + + + + public static boolean balRequireOptInByPendingIntentCreator() { + + return FEATURE_FLAGS.balRequireOptInByPendingIntentCreator(); + } + + + + public static boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { + + return FEATURE_FLAGS.balRespectAppSwitchStateWhenCheckBoundByForegroundUid(); + } + + + + public static boolean balSendIntentWithOptions() { + + return FEATURE_FLAGS.balSendIntentWithOptions(); + } + + + public static boolean balShowToastsBlocked() { + return FEATURE_FLAGS.balShowToastsBlocked(); } - - public static boolean blastSyncNotificationShadeOnDisplaySwitch() { - return FEATURE_FLAGS.blastSyncNotificationShadeOnDisplaySwitch(); + + + + public static boolean balStrictModeGracePeriod() { + + return FEATURE_FLAGS.balStrictModeGracePeriod(); + } + + + + public static boolean balStrictModeRo() { + + return FEATURE_FLAGS.balStrictModeRo(); + } + + + + public static boolean betterSupportNonMatchParentActivity() { + + return FEATURE_FLAGS.betterSupportNonMatchParentActivity(); } - - public static boolean bundleClientTransactionFlag() { - return FEATURE_FLAGS.bundleClientTransactionFlag(); + + + + public static boolean cacheWindowStyle() { + + return FEATURE_FLAGS.cacheWindowStyle(); } - + + + public static boolean cameraCompatForFreeform() { + return FEATURE_FLAGS.cameraCompatForFreeform(); } - + + + + public static boolean cameraCompatFullscreenPickSameTaskActivity() { + + return FEATURE_FLAGS.cameraCompatFullscreenPickSameTaskActivity(); + } + + + + public static boolean checkDisabledSnapshotsInTaskPersister() { + + return FEATURE_FLAGS.checkDisabledSnapshotsInTaskPersister(); + } + + + + public static boolean cleanupDispatchPendingTransactionsRemoteException() { + + return FEATURE_FLAGS.cleanupDispatchPendingTransactionsRemoteException(); + } + + + + public static boolean clearSystemVibrator() { + + return FEATURE_FLAGS.clearSystemVibrator(); + } + + + public static boolean closeToSquareConfigIncludesStatusBar() { + return FEATURE_FLAGS.closeToSquareConfigIncludesStatusBar(); } - + + + + public static boolean condenseConfigurationChangeForSimpleMode() { + + return FEATURE_FLAGS.condenseConfigurationChangeForSimpleMode(); + } + + + public static boolean configurableFontScaleDefault() { + return FEATURE_FLAGS.configurableFontScaleDefault(); } - + + + public static boolean coverDisplayOptIn() { + return FEATURE_FLAGS.coverDisplayOptIn(); } - - public static boolean deferDisplayUpdates() { - return FEATURE_FLAGS.deferDisplayUpdates(); - } - + + + public static boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { + return FEATURE_FLAGS.delayNotificationToMagnificationWhenRecentsWindowToFrontTransition(); } - + + + + public static boolean delegateBackGestureToShell() { + + return FEATURE_FLAGS.delegateBackGestureToShell(); + } + + + public static boolean delegateUnhandledDrags() { + return FEATURE_FLAGS.delegateUnhandledDrags(); } - + + + public static boolean deleteCaptureDisplay() { + return FEATURE_FLAGS.deleteCaptureDisplay(); } - + + + public static boolean density390Api() { + return FEATURE_FLAGS.density390Api(); } - - public static boolean disableObjectPool() { - return FEATURE_FLAGS.disableObjectPool(); + + + + public static boolean disableDesktopLaunchParamsOutsideDesktopBugFix() { + + return FEATURE_FLAGS.disableDesktopLaunchParamsOutsideDesktopBugFix(); + } + + + + public static boolean disableNonResizableAppSnapResizing() { + + return FEATURE_FLAGS.disableNonResizableAppSnapResizing(); } - - public static boolean disableThinLetterboxingPolicy() { - return FEATURE_FLAGS.disableThinLetterboxingPolicy(); + + + + public static boolean disableOptOutEdgeToEdge() { + + return FEATURE_FLAGS.disableOptOutEdgeToEdge(); } - + + + public static boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions() { + return FEATURE_FLAGS.doNotCheckIntersectionWhenNonMagnifiableWindowTransitions(); } - - public static boolean drawSnapshotAspectRatioMatch() { - return FEATURE_FLAGS.drawSnapshotAspectRatioMatch(); + + + + public static boolean earlyLaunchHint() { + + return FEATURE_FLAGS.earlyLaunchHint(); } - + + + public static boolean edgeToEdgeByDefault() { + return FEATURE_FLAGS.edgeToEdgeByDefault(); } - - public static boolean embeddedActivityBackNavFlag() { - return FEATURE_FLAGS.embeddedActivityBackNavFlag(); + + + + public static boolean enableAccessibleCustomHeaders() { + + return FEATURE_FLAGS.enableAccessibleCustomHeaders(); } - - public static boolean enableAdditionalWindowsAboveStatusBar() { - return FEATURE_FLAGS.enableAdditionalWindowsAboveStatusBar(); + + + + public static boolean enableActivityEmbeddingSupportForConnectedDisplays() { + + return FEATURE_FLAGS.enableActivityEmbeddingSupportForConnectedDisplays(); } - + + + public static boolean enableAppHeaderWithTaskDensity() { + return FEATURE_FLAGS.enableAppHeaderWithTaskDensity(); } - + + + + public static boolean enableBorderSettings() { + + return FEATURE_FLAGS.enableBorderSettings(); + } + + + public static boolean enableBufferTransformHintFromDisplay() { + return FEATURE_FLAGS.enableBufferTransformHintFromDisplay(); } - + + + + public static boolean enableBugFixesForSecondaryDisplay() { + + return FEATURE_FLAGS.enableBugFixesForSecondaryDisplay(); + } + + + public static boolean enableCameraCompatForDesktopWindowing() { + return FEATURE_FLAGS.enableCameraCompatForDesktopWindowing(); } - + + + + public static boolean enableCameraCompatForDesktopWindowingOptOut() { + + return FEATURE_FLAGS.enableCameraCompatForDesktopWindowingOptOut(); + } + + + + public static boolean enableCameraCompatForDesktopWindowingOptOutApi() { + + return FEATURE_FLAGS.enableCameraCompatForDesktopWindowingOptOutApi(); + } + + + + public static boolean enableCameraCompatTrackTaskAndAppBugfix() { + + return FEATURE_FLAGS.enableCameraCompatTrackTaskAndAppBugfix(); + } + + + + public static boolean enableCaptionCompatInsetConversion() { + + return FEATURE_FLAGS.enableCaptionCompatInsetConversion(); + } + + + + public static boolean enableCaptionCompatInsetForceConsumption() { + + return FEATURE_FLAGS.enableCaptionCompatInsetForceConsumption(); + } + + + + public static boolean enableCaptionCompatInsetForceConsumptionAlways() { + + return FEATURE_FLAGS.enableCaptionCompatInsetForceConsumptionAlways(); + } + + + + public static boolean enableCascadingWindows() { + + return FEATURE_FLAGS.enableCascadingWindows(); + } + + + + public static boolean enableCompatUiVisibilityStatus() { + + return FEATURE_FLAGS.enableCompatUiVisibilityStatus(); + } + + + public static boolean enableCompatuiSysuiLauncher() { + return FEATURE_FLAGS.enableCompatuiSysuiLauncher(); } - - public static boolean enableDesktopWindowingImmersiveHandleHiding() { - return FEATURE_FLAGS.enableDesktopWindowingImmersiveHandleHiding(); + + + + public static boolean enableConnectedDisplaysDnd() { + + return FEATURE_FLAGS.enableConnectedDisplaysDnd(); } - - public static boolean enableDesktopWindowingModalsPolicy() { - return FEATURE_FLAGS.enableDesktopWindowingModalsPolicy(); + + + + public static boolean enableConnectedDisplaysPip() { + + return FEATURE_FLAGS.enableConnectedDisplaysPip(); } - - public static boolean enableDesktopWindowingMode() { - return FEATURE_FLAGS.enableDesktopWindowingMode(); + + + + public static boolean enableConnectedDisplaysWindowDrag() { + + return FEATURE_FLAGS.enableConnectedDisplaysWindowDrag(); } - - public static boolean enableDesktopWindowingQuickSwitch() { - return FEATURE_FLAGS.enableDesktopWindowingQuickSwitch(); + + + + public static boolean enableDesktopAppHandleAnimation() { + + return FEATURE_FLAGS.enableDesktopAppHandleAnimation(); } - - public static boolean enableDesktopWindowingScvhCache() { - return FEATURE_FLAGS.enableDesktopWindowingScvhCache(); + + + + public static boolean enableDesktopAppLaunchAlttabTransitions() { + + return FEATURE_FLAGS.enableDesktopAppLaunchAlttabTransitions(); } - - public static boolean enableDesktopWindowingSizeConstraints() { - return FEATURE_FLAGS.enableDesktopWindowingSizeConstraints(); + + + + public static boolean enableDesktopAppLaunchAlttabTransitionsBugfix() { + + return FEATURE_FLAGS.enableDesktopAppLaunchAlttabTransitionsBugfix(); } - - public static boolean enableDesktopWindowingTaskLimit() { - return FEATURE_FLAGS.enableDesktopWindowingTaskLimit(); + + + + public static boolean enableDesktopAppLaunchTransitions() { + + return FEATURE_FLAGS.enableDesktopAppLaunchTransitions(); } - - public static boolean enableDesktopWindowingTaskbarRunningApps() { - return FEATURE_FLAGS.enableDesktopWindowingTaskbarRunningApps(); + + + + public static boolean enableDesktopAppLaunchTransitionsBugfix() { + + return FEATURE_FLAGS.enableDesktopAppLaunchTransitionsBugfix(); } - - public static boolean enableDesktopWindowingWallpaperActivity() { - return FEATURE_FLAGS.enableDesktopWindowingWallpaperActivity(); + + + + public static boolean enableDesktopCloseShortcutBugfix() { + + return FEATURE_FLAGS.enableDesktopCloseShortcutBugfix(); } - - public static boolean enableScaledResizing() { - return FEATURE_FLAGS.enableScaledResizing(); + + + + public static boolean enableDesktopCloseTaskAnimationInDtcBugfix() { + + return FEATURE_FLAGS.enableDesktopCloseTaskAnimationInDtcBugfix(); } - - public static boolean enableTaskStackObserverInShell() { - return FEATURE_FLAGS.enableTaskStackObserverInShell(); + + + + public static boolean enableDesktopImeBugfix() { + + return FEATURE_FLAGS.enableDesktopImeBugfix(); } - - public static boolean enableThemedAppHeaders() { - return FEATURE_FLAGS.enableThemedAppHeaders(); + + + + public static boolean enableDesktopImmersiveDragBugfix() { + + return FEATURE_FLAGS.enableDesktopImmersiveDragBugfix(); } - - public static boolean enableWindowingDynamicInitialBounds() { - return FEATURE_FLAGS.enableWindowingDynamicInitialBounds(); + + + + public static boolean enableDesktopIndicatorInSeparateThreadBugfix() { + + return FEATURE_FLAGS.enableDesktopIndicatorInSeparateThreadBugfix(); + } + + + + public static boolean enableDesktopModeThroughDevOption() { + + return FEATURE_FLAGS.enableDesktopModeThroughDevOption(); + } + + + + public static boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix() { + + return FEATURE_FLAGS.enableDesktopOpeningDeeplinkMinimizeAnimationBugfix(); + } + + + + public static boolean enableDesktopRecentsTransitionsCornersBugfix() { + + return FEATURE_FLAGS.enableDesktopRecentsTransitionsCornersBugfix(); + } + + + + public static boolean enableDesktopSwipeBackMinimizeAnimationBugfix() { + + return FEATURE_FLAGS.enableDesktopSwipeBackMinimizeAnimationBugfix(); + } + + + + public static boolean enableDesktopSystemDialogsTransitions() { + + return FEATURE_FLAGS.enableDesktopSystemDialogsTransitions(); + } + + + + public static boolean enableDesktopTabTearingMinimizeAnimationBugfix() { + + return FEATURE_FLAGS.enableDesktopTabTearingMinimizeAnimationBugfix(); + } + + + + public static boolean enableDesktopTaskbarOnFreeformDisplays() { + + return FEATURE_FLAGS.enableDesktopTaskbarOnFreeformDisplays(); + } + + + + public static boolean enableDesktopTrampolineCloseAnimationBugfix() { + + return FEATURE_FLAGS.enableDesktopTrampolineCloseAnimationBugfix(); + } + + + + public static boolean enableDesktopWallpaperActivityForSystemUser() { + + return FEATURE_FLAGS.enableDesktopWallpaperActivityForSystemUser(); + } + + + + public static boolean enableDesktopWindowingAppHandleEducation() { + + return FEATURE_FLAGS.enableDesktopWindowingAppHandleEducation(); + } + + + + public static boolean enableDesktopWindowingAppToWeb() { + + return FEATURE_FLAGS.enableDesktopWindowingAppToWeb(); + } + + + + public static boolean enableDesktopWindowingAppToWebEducation() { + + return FEATURE_FLAGS.enableDesktopWindowingAppToWebEducation(); + } + + + + public static boolean enableDesktopWindowingAppToWebEducationIntegration() { + + return FEATURE_FLAGS.enableDesktopWindowingAppToWebEducationIntegration(); + } + + + + public static boolean enableDesktopWindowingBackNavigation() { + + return FEATURE_FLAGS.enableDesktopWindowingBackNavigation(); + } + + + + public static boolean enableDesktopWindowingEnterTransitionBugfix() { + + return FEATURE_FLAGS.enableDesktopWindowingEnterTransitionBugfix(); + } + + + + public static boolean enableDesktopWindowingEnterTransitions() { + + return FEATURE_FLAGS.enableDesktopWindowingEnterTransitions(); + } + + + + public static boolean enableDesktopWindowingExitByMinimizeTransitionBugfix() { + + return FEATURE_FLAGS.enableDesktopWindowingExitByMinimizeTransitionBugfix(); + } + + + + public static boolean enableDesktopWindowingExitTransitions() { + + return FEATURE_FLAGS.enableDesktopWindowingExitTransitions(); + } + + + + public static boolean enableDesktopWindowingExitTransitionsBugfix() { + + return FEATURE_FLAGS.enableDesktopWindowingExitTransitionsBugfix(); + } + + + + public static boolean enableDesktopWindowingHsum() { + + return FEATURE_FLAGS.enableDesktopWindowingHsum(); + } + + + + public static boolean enableDesktopWindowingImmersiveHandleHiding() { + + return FEATURE_FLAGS.enableDesktopWindowingImmersiveHandleHiding(); + } + + + + public static boolean enableDesktopWindowingModalsPolicy() { + + return FEATURE_FLAGS.enableDesktopWindowingModalsPolicy(); + } + + + + public static boolean enableDesktopWindowingMode() { + + return FEATURE_FLAGS.enableDesktopWindowingMode(); + } + + + + public static boolean enableDesktopWindowingMultiInstanceFeatures() { + + return FEATURE_FLAGS.enableDesktopWindowingMultiInstanceFeatures(); + } + + + + public static boolean enableDesktopWindowingPersistence() { + + return FEATURE_FLAGS.enableDesktopWindowingPersistence(); + } + + + + public static boolean enableDesktopWindowingPip() { + + return FEATURE_FLAGS.enableDesktopWindowingPip(); + } + + + + public static boolean enableDesktopWindowingQuickSwitch() { + + return FEATURE_FLAGS.enableDesktopWindowingQuickSwitch(); + } + + + + public static boolean enableDesktopWindowingScvhCacheBugFix() { + + return FEATURE_FLAGS.enableDesktopWindowingScvhCacheBugFix(); + } + + + + public static boolean enableDesktopWindowingSizeConstraints() { + + return FEATURE_FLAGS.enableDesktopWindowingSizeConstraints(); + } + + + + public static boolean enableDesktopWindowingTaskLimit() { + + return FEATURE_FLAGS.enableDesktopWindowingTaskLimit(); + } + + + + public static boolean enableDesktopWindowingTaskbarRunningApps() { + + return FEATURE_FLAGS.enableDesktopWindowingTaskbarRunningApps(); + } + + + + public static boolean enableDesktopWindowingTransitions() { + + return FEATURE_FLAGS.enableDesktopWindowingTransitions(); + } + + + + public static boolean enableDesktopWindowingWallpaperActivity() { + + return FEATURE_FLAGS.enableDesktopWindowingWallpaperActivity(); + } + + + + public static boolean enableDeviceStateAutoRotateSettingLogging() { + + return FEATURE_FLAGS.enableDeviceStateAutoRotateSettingLogging(); + } + + + + public static boolean enableDeviceStateAutoRotateSettingRefactor() { + + return FEATURE_FLAGS.enableDeviceStateAutoRotateSettingRefactor(); + } + + + + public static boolean enableDisplayDisconnectInteraction() { + + return FEATURE_FLAGS.enableDisplayDisconnectInteraction(); + } + + + + public static boolean enableDisplayFocusInShellTransitions() { + + return FEATURE_FLAGS.enableDisplayFocusInShellTransitions(); + } + + + + public static boolean enableDisplayReconnectInteraction() { + + return FEATURE_FLAGS.enableDisplayReconnectInteraction(); + } + + + + public static boolean enableDisplayWindowingModeSwitching() { + + return FEATURE_FLAGS.enableDisplayWindowingModeSwitching(); + } + + + + public static boolean enableDragResizeSetUpInBgThread() { + + return FEATURE_FLAGS.enableDragResizeSetUpInBgThread(); + } + + + + public static boolean enableDragToDesktopIncomingTransitionsBugfix() { + + return FEATURE_FLAGS.enableDragToDesktopIncomingTransitionsBugfix(); + } + + + + public static boolean enableDragToMaximize() { + + return FEATURE_FLAGS.enableDragToMaximize(); + } + + + + public static boolean enableDynamicRadiusComputationBugfix() { + + return FEATURE_FLAGS.enableDynamicRadiusComputationBugfix(); + } + + + + public static boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix() { + + return FEATURE_FLAGS.enableFullScreenWindowOnRemovingSplitScreenStageBugfix(); + } + + + + public static boolean enableFullyImmersiveInDesktop() { + + return FEATURE_FLAGS.enableFullyImmersiveInDesktop(); + } + + + + public static boolean enableHandleInputFix() { + + return FEATURE_FLAGS.enableHandleInputFix(); + } + + + + public static boolean enableHoldToDragAppHandle() { + + return FEATURE_FLAGS.enableHoldToDragAppHandle(); + } + + + + public static boolean enableInputLayerTransitionFix() { + + return FEATURE_FLAGS.enableInputLayerTransitionFix(); + } + + + + public static boolean enableMinimizeButton() { + + return FEATURE_FLAGS.enableMinimizeButton(); + } + + + + public static boolean enableModalsFullscreenWithPermission() { + + return FEATURE_FLAGS.enableModalsFullscreenWithPermission(); + } + + + + public static boolean enableMoveToNextDisplayShortcut() { + + return FEATURE_FLAGS.enableMoveToNextDisplayShortcut(); + } + + + + public static boolean enableMultiDisplaySplit() { + + return FEATURE_FLAGS.enableMultiDisplaySplit(); + } + + + + public static boolean enableMultidisplayTrackpadBackGesture() { + + return FEATURE_FLAGS.enableMultidisplayTrackpadBackGesture(); + } + + + + public static boolean enableMultipleDesktopsBackend() { + + return FEATURE_FLAGS.enableMultipleDesktopsBackend(); + } + + + + public static boolean enableMultipleDesktopsFrontend() { + + return FEATURE_FLAGS.enableMultipleDesktopsFrontend(); + } + + + + public static boolean enableNonDefaultDisplaySplit() { + + return FEATURE_FLAGS.enableNonDefaultDisplaySplit(); + } + + + + public static boolean enableOpaqueBackgroundForTransparentWindows() { + + return FEATURE_FLAGS.enableOpaqueBackgroundForTransparentWindows(); + } + + + + public static boolean enablePerDisplayDesktopWallpaperActivity() { + + return FEATURE_FLAGS.enablePerDisplayDesktopWallpaperActivity(); + } + + + + public static boolean enablePerDisplayPackageContextCacheInStatusbarNotif() { + + return FEATURE_FLAGS.enablePerDisplayPackageContextCacheInStatusbarNotif(); + } + + + + public static boolean enablePersistingDisplaySizeForConnectedDisplays() { + + return FEATURE_FLAGS.enablePersistingDisplaySizeForConnectedDisplays(); + } + + + + public static boolean enablePresentationForConnectedDisplays() { + + return FEATURE_FLAGS.enablePresentationForConnectedDisplays(); + } + + + + public static boolean enableProjectedDisplayDesktopMode() { + + return FEATURE_FLAGS.enableProjectedDisplayDesktopMode(); + } + + + + public static boolean enableQuickswitchDesktopSplitBugfix() { + + return FEATURE_FLAGS.enableQuickswitchDesktopSplitBugfix(); + } + + + + public static boolean enableRequestFullscreenBugfix() { + + return FEATURE_FLAGS.enableRequestFullscreenBugfix(); + } + + + + public static boolean enableResizingMetrics() { + + return FEATURE_FLAGS.enableResizingMetrics(); + } + + + + public static boolean enableRestartMenuForConnectedDisplays() { + + return FEATURE_FLAGS.enableRestartMenuForConnectedDisplays(); + } + + + + public static boolean enableRestoreToPreviousSizeFromDesktopImmersive() { + + return FEATURE_FLAGS.enableRestoreToPreviousSizeFromDesktopImmersive(); + } + + + + public static boolean enableShellInitialBoundsRegressionBugFix() { + + return FEATURE_FLAGS.enableShellInitialBoundsRegressionBugFix(); + } + + + + public static boolean enableSizeCompatModeImprovementsForConnectedDisplays() { + + return FEATURE_FLAGS.enableSizeCompatModeImprovementsForConnectedDisplays(); + } + + + + public static boolean enableStartLaunchTransitionFromTaskbarBugfix() { + + return FEATURE_FLAGS.enableStartLaunchTransitionFromTaskbarBugfix(); + } + + + + public static boolean enableTaskResizingKeyboardShortcuts() { + + return FEATURE_FLAGS.enableTaskResizingKeyboardShortcuts(); + } + + + + public static boolean enableTaskStackObserverInShell() { + + return FEATURE_FLAGS.enableTaskStackObserverInShell(); + } + + + + public static boolean enableTaskbarConnectedDisplays() { + + return FEATURE_FLAGS.enableTaskbarConnectedDisplays(); + } + + + + public static boolean enableTaskbarOverflow() { + + return FEATURE_FLAGS.enableTaskbarOverflow(); + } + + + + public static boolean enableTaskbarRecentsLayoutTransition() { + + return FEATURE_FLAGS.enableTaskbarRecentsLayoutTransition(); + } + + + + public static boolean enableThemedAppHeaders() { + + return FEATURE_FLAGS.enableThemedAppHeaders(); + } + + + + public static boolean enableTileResizing() { + + return FEATURE_FLAGS.enableTileResizing(); + } + + + + public static boolean enableTopVisibleRootTaskPerUserTracking() { + + return FEATURE_FLAGS.enableTopVisibleRootTaskPerUserTracking(); + } + + + + public static boolean enableVisualIndicatorInTransitionBugfix() { + + return FEATURE_FLAGS.enableVisualIndicatorInTransitionBugfix(); + } + + + + public static boolean enableWindowContextResourcesUpdateOnConfigChange() { + + return FEATURE_FLAGS.enableWindowContextResourcesUpdateOnConfigChange(); + } + + + + public static boolean enableWindowingDynamicInitialBounds() { + + return FEATURE_FLAGS.enableWindowingDynamicInitialBounds(); + } + + + + public static boolean enableWindowingEdgeDragResize() { + + return FEATURE_FLAGS.enableWindowingEdgeDragResize(); + } + + + + public static boolean enableWindowingScaledResizing() { + + return FEATURE_FLAGS.enableWindowingScaledResizing(); + } + + + + public static boolean enableWindowingTransitionHandlersObservers() { + + return FEATURE_FLAGS.enableWindowingTransitionHandlersObservers(); + } + + + + public static boolean enforceEdgeToEdge() { + + return FEATURE_FLAGS.enforceEdgeToEdge(); + } + + + + public static boolean ensureKeyguardDoesTransitionStarting() { + + return FEATURE_FLAGS.ensureKeyguardDoesTransitionStarting(); + } + + + + public static boolean ensureWallpaperInTransitions() { + + return FEATURE_FLAGS.ensureWallpaperInTransitions(); + } + + + + public static boolean ensureWallpaperInWearTransitions() { + + return FEATURE_FLAGS.ensureWallpaperInWearTransitions(); + } + + + + public static boolean enterDesktopByDefaultOnFreeformDisplays() { + + return FEATURE_FLAGS.enterDesktopByDefaultOnFreeformDisplays(); + } + + + + public static boolean excludeCaptionFromAppBounds() { + + return FEATURE_FLAGS.excludeCaptionFromAppBounds(); + } + + + + public static boolean excludeDrawingAppThemeSnapshotFromLock() { + + return FEATURE_FLAGS.excludeDrawingAppThemeSnapshotFromLock(); + } + + + + public static boolean excludeTaskFromRecents() { + + return FEATURE_FLAGS.excludeTaskFromRecents(); + } + + + + public static boolean fifoPriorityForMajorUiProcesses() { + + return FEATURE_FLAGS.fifoPriorityForMajorUiProcesses(); + } + + + + public static boolean fixHideOverlayApi() { + + return FEATURE_FLAGS.fixHideOverlayApi(); + } + + + + public static boolean fixLayoutExistingTask() { + + return FEATURE_FLAGS.fixLayoutExistingTask(); + } + + + + public static boolean fixViewRootCallTrace() { + + return FEATURE_FLAGS.fixViewRootCallTrace(); + } + + + + public static boolean forceCloseTopTransparentFullscreenTask() { + + return FEATURE_FLAGS.forceCloseTopTransparentFullscreenTask(); + } + + + + public static boolean formFactorBasedDesktopFirstSwitch() { + + return FEATURE_FLAGS.formFactorBasedDesktopFirstSwitch(); + } + + + + public static boolean getDimmerOnClosing() { + + return FEATURE_FLAGS.getDimmerOnClosing(); + } + + + + public static boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities() { + + return FEATURE_FLAGS.ignoreAspectRatioRestrictionsForResizeableFreeformActivities(); + } + + + + public static boolean ignoreCornerRadiusAndShadows() { + + return FEATURE_FLAGS.ignoreCornerRadiusAndShadows(); + } + + + + public static boolean includeTopTransparentFullscreenTaskInDesktopHeuristic() { + + return FEATURE_FLAGS.includeTopTransparentFullscreenTaskInDesktopHeuristic(); + } + + + + public static boolean inheritTaskBoundsForTrampolineTaskLaunches() { + + return FEATURE_FLAGS.inheritTaskBoundsForTrampolineTaskLaunches(); + } + + + + public static boolean insetsDecoupledConfiguration() { + + return FEATURE_FLAGS.insetsDecoupledConfiguration(); + } + + + + public static boolean jankApi() { + + return FEATURE_FLAGS.jankApi(); + } + + + + public static boolean keepAppWindowHideWhileLocked() { + + return FEATURE_FLAGS.keepAppWindowHideWhileLocked(); + } + + + + public static boolean keyboardShortcutsToSwitchDesks() { + + return FEATURE_FLAGS.keyboardShortcutsToSwitchDesks(); + } + + + + public static boolean keyguardGoingAwayTimeout() { + + return FEATURE_FLAGS.keyguardGoingAwayTimeout(); + } + + + + public static boolean letterboxBackgroundWallpaper() { + + return FEATURE_FLAGS.letterboxBackgroundWallpaper(); + } + + + + public static boolean movableCutoutConfiguration() { + + return FEATURE_FLAGS.movableCutoutConfiguration(); + } + + + + public static boolean moveToExternalDisplayShortcut() { + + return FEATURE_FLAGS.moveToExternalDisplayShortcut(); + } + + + + public static boolean multiCrop() { + + return FEATURE_FLAGS.multiCrop(); + } + + + + public static boolean navBarTransparentByDefault() { + + return FEATURE_FLAGS.navBarTransparentByDefault(); + } + + + + public static boolean nestedTasksWithIndependentBounds() { + + return FEATURE_FLAGS.nestedTasksWithIndependentBounds(); + } + + + + public static boolean noConsecutiveVisibilityEvents() { + + return FEATURE_FLAGS.noConsecutiveVisibilityEvents(); + } + + + + public static boolean noDuplicateSurfaceDestroyedEvents() { + + return FEATURE_FLAGS.noDuplicateSurfaceDestroyedEvents(); } - - public static boolean enableWindowingEdgeDragResize() { - return FEATURE_FLAGS.enableWindowingEdgeDragResize(); + + + + public static boolean noVisibilityEventOnDisplayStateChange() { + + return FEATURE_FLAGS.noVisibilityEventOnDisplayStateChange(); } - - public static boolean enableWmExtensionsForAllFlag() { - return FEATURE_FLAGS.enableWmExtensionsForAllFlag(); + + + + public static boolean offloadColorExtraction() { + + return FEATURE_FLAGS.offloadColorExtraction(); } - - public static boolean enforceEdgeToEdge() { - return FEATURE_FLAGS.enforceEdgeToEdge(); + + + + public static boolean portWindowSizeAnimation() { + + return FEATURE_FLAGS.portWindowSizeAnimation(); } - - public static boolean ensureWallpaperInTransitions() { - return FEATURE_FLAGS.ensureWallpaperInTransitions(); + + + + public static boolean predictiveBackDefaultEnableSdk36() { + + return FEATURE_FLAGS.predictiveBackDefaultEnableSdk36(); + } + + + + public static boolean predictiveBackPrioritySystemNavigationObserver() { + + return FEATURE_FLAGS.predictiveBackPrioritySystemNavigationObserver(); } - - public static boolean explicitRefreshRateHints() { - return FEATURE_FLAGS.explicitRefreshRateHints(); + + + + public static boolean predictiveBackSwipeEdgeNoneApi() { + + return FEATURE_FLAGS.predictiveBackSwipeEdgeNoneApi(); } - - public static boolean fifoPriorityForMajorUiProcesses() { - return FEATURE_FLAGS.fifoPriorityForMajorUiProcesses(); + + + + public static boolean predictiveBackSystemOverrideCallback() { + + return FEATURE_FLAGS.predictiveBackSystemOverrideCallback(); } - - public static boolean fixNoContainerUpdateWithoutResize() { - return FEATURE_FLAGS.fixNoContainerUpdateWithoutResize(); + + + + public static boolean predictiveBackThreeButtonNav() { + + return FEATURE_FLAGS.predictiveBackThreeButtonNav(); } - - public static boolean fixPipRestoreToOverlay() { - return FEATURE_FLAGS.fixPipRestoreToOverlay(); + + + + public static boolean predictiveBackTimestampApi() { + + return FEATURE_FLAGS.predictiveBackTimestampApi(); } - - public static boolean fullscreenDimFlag() { - return FEATURE_FLAGS.fullscreenDimFlag(); + + + + public static boolean processPriorityPolicyForMultiWindowMode() { + + return FEATURE_FLAGS.processPriorityPolicyForMultiWindowMode(); } - - public static boolean getDimmerOnClosing() { - return FEATURE_FLAGS.getDimmerOnClosing(); + + + + public static boolean rearDisplayDisableForceDesktopSystemDecorations() { + + return FEATURE_FLAGS.rearDisplayDisableForceDesktopSystemDecorations(); } - - public static boolean immersiveAppRepositioning() { - return FEATURE_FLAGS.immersiveAppRepositioning(); + + + + public static boolean recordTaskSnapshotsBeforeShutdown() { + + return FEATURE_FLAGS.recordTaskSnapshotsBeforeShutdown(); } - - public static boolean insetsControlChangedItem() { - return FEATURE_FLAGS.insetsControlChangedItem(); + + + + public static boolean reduceChangedExclusionRectsMsgs() { + + return FEATURE_FLAGS.reduceChangedExclusionRectsMsgs(); } - - public static boolean insetsControlSeq() { - return FEATURE_FLAGS.insetsControlSeq(); + + + + public static boolean reduceKeyguardTransitions() { + + return FEATURE_FLAGS.reduceKeyguardTransitions(); } - - public static boolean insetsDecoupledConfiguration() { - return FEATURE_FLAGS.insetsDecoupledConfiguration(); + + + + public static boolean reduceTaskSnapshotMemoryUsage() { + + return FEATURE_FLAGS.reduceTaskSnapshotMemoryUsage(); } - - public static boolean introduceSmootherDimmer() { - return FEATURE_FLAGS.introduceSmootherDimmer(); + + + + public static boolean reduceUnnecessaryMeasure() { + + return FEATURE_FLAGS.reduceUnnecessaryMeasure(); } - - public static boolean keyguardAppearTransition() { - return FEATURE_FLAGS.keyguardAppearTransition(); + + + + public static boolean relativeInsets() { + + return FEATURE_FLAGS.relativeInsets(); } - - public static boolean letterboxBackgroundWallpaper() { - return FEATURE_FLAGS.letterboxBackgroundWallpaper(); + + + + public static boolean releaseSnapshotAggressively() { + + return FEATURE_FLAGS.releaseSnapshotAggressively(); } - - public static boolean movableCutoutConfiguration() { - return FEATURE_FLAGS.movableCutoutConfiguration(); + + + + public static boolean releaseUserAspectRatioWm() { + + return FEATURE_FLAGS.releaseUserAspectRatioWm(); } - - public static boolean moveAnimationOptionsToChange() { - return FEATURE_FLAGS.moveAnimationOptionsToChange(); + + + + public static boolean removeActivityStarterDreamCallback() { + + return FEATURE_FLAGS.removeActivityStarterDreamCallback(); } - - public static boolean multiCrop() { - return FEATURE_FLAGS.multiCrop(); + + + + public static boolean removeDeferHidingClient() { + + return FEATURE_FLAGS.removeDeferHidingClient(); } - - public static boolean navBarTransparentByDefault() { - return FEATURE_FLAGS.navBarTransparentByDefault(); + + + + public static boolean removeDepartTargetFromMotion() { + + return FEATURE_FLAGS.removeDepartTargetFromMotion(); } - - public static boolean noConsecutiveVisibilityEvents() { - return FEATURE_FLAGS.noConsecutiveVisibilityEvents(); + + + + public static boolean reparentWindowTokenApi() { + + return FEATURE_FLAGS.reparentWindowTokenApi(); } - - public static boolean noVisibilityEventOnDisplayStateChange() { - return FEATURE_FLAGS.noVisibilityEventOnDisplayStateChange(); + + + + public static boolean respectNonTopVisibleFixedOrientation() { + + return FEATURE_FLAGS.respectNonTopVisibleFixedOrientation(); } - - public static boolean offloadColorExtraction() { - return FEATURE_FLAGS.offloadColorExtraction(); + + + + public static boolean respectOrientationChangeForUnresizeable() { + + return FEATURE_FLAGS.respectOrientationChangeForUnresizeable(); } - - public static boolean predictiveBackSystemAnims() { - return FEATURE_FLAGS.predictiveBackSystemAnims(); + + + + public static boolean safeRegionLetterboxing() { + + return FEATURE_FLAGS.safeRegionLetterboxing(); } - - public static boolean rearDisplayDisableForceDesktopSystemDecorations() { - return FEATURE_FLAGS.rearDisplayDisableForceDesktopSystemDecorations(); + + + + public static boolean safeReleaseSnapshotAggressively() { + + return FEATURE_FLAGS.safeReleaseSnapshotAggressively(); } - - public static boolean releaseSnapshotAggressively() { - return FEATURE_FLAGS.releaseSnapshotAggressively(); + + + + public static boolean schedulingForNotificationShade() { + + return FEATURE_FLAGS.schedulingForNotificationShade(); } - - public static boolean removePrepareSurfaceInPlacement() { - return FEATURE_FLAGS.removePrepareSurfaceInPlacement(); + + + + public static boolean scrambleSnapshotFileName() { + + return FEATURE_FLAGS.scrambleSnapshotFileName(); } - + + + public static boolean screenRecordingCallbacks() { + return FEATURE_FLAGS.screenRecordingCallbacks(); } - + + + + public static boolean scrollingFromLetterbox() { + + return FEATURE_FLAGS.scrollingFromLetterbox(); + } + + + public static boolean sdkDesiredPresentTime() { + return FEATURE_FLAGS.sdkDesiredPresentTime(); } - - public static boolean secureWindowState() { - return FEATURE_FLAGS.secureWindowState(); - } - + + + public static boolean setScPropertiesInClient() { + return FEATURE_FLAGS.setScPropertiesInClient(); } - - public static boolean skipSleepingWhenSwitchingDisplay() { - return FEATURE_FLAGS.skipSleepingWhenSwitchingDisplay(); + + + + public static boolean showAppHandleLargeScreens() { + + return FEATURE_FLAGS.showAppHandleLargeScreens(); + } + + + + public static boolean showDesktopExperienceDevOption() { + + return FEATURE_FLAGS.showDesktopExperienceDevOption(); + } + + + + public static boolean showDesktopWindowingDevOption() { + + return FEATURE_FLAGS.showDesktopWindowingDevOption(); + } + + + + public static boolean showHomeBehindDesktop() { + + return FEATURE_FLAGS.showHomeBehindDesktop(); + } + + + + public static boolean skipCompatUiEducationInDesktopMode() { + + return FEATURE_FLAGS.skipCompatUiEducationInDesktopMode(); + } + + + + public static boolean skipDecorViewRelayoutWhenClosingBugfix() { + + return FEATURE_FLAGS.skipDecorViewRelayoutWhenClosingBugfix(); + } + + + + public static boolean supportWidgetIntentsOnConnectedDisplay() { + + return FEATURE_FLAGS.supportWidgetIntentsOnConnectedDisplay(); + } + + + + public static boolean supportsDragAssistantToMultiwindow() { + + return FEATURE_FLAGS.supportsDragAssistantToMultiwindow(); } - + + + public static boolean supportsMultiInstanceSystemUi() { + return FEATURE_FLAGS.supportsMultiInstanceSystemUi(); } - + + + public static boolean surfaceControlInputReceiver() { + return FEATURE_FLAGS.surfaceControlInputReceiver(); } - + + + public static boolean surfaceTrustedOverlay() { + return FEATURE_FLAGS.surfaceTrustedOverlay(); } - + + + public static boolean syncScreenCapture() { + return FEATURE_FLAGS.syncScreenCapture(); } - + + + + public static boolean systemUiPostAnimationEnd() { + + return FEATURE_FLAGS.systemUiPostAnimationEnd(); + } + + + public static boolean taskFragmentSystemOrganizerFlag() { + return FEATURE_FLAGS.taskFragmentSystemOrganizerFlag(); } - + + + + public static boolean touchPassThroughOptIn() { + + return FEATURE_FLAGS.touchPassThroughOptIn(); + } + + + + public static boolean trackSystemUiContextBeforeWms() { + + return FEATURE_FLAGS.trackSystemUiContextBeforeWms(); + } + + + public static boolean transitReadyTracking() { + return FEATURE_FLAGS.transitReadyTracking(); } - + + + + public static boolean transitTrackerPlumbing() { + + return FEATURE_FLAGS.transitTrackerPlumbing(); + } + + + public static boolean trustedPresentationListenerForWindow() { + return FEATURE_FLAGS.trustedPresentationListenerForWindow(); } - + + + + public static boolean unifyBackNavigationTransition() { + + return FEATURE_FLAGS.unifyBackNavigationTransition(); + } + + + + public static boolean universalResizableByDefault() { + + return FEATURE_FLAGS.universalResizableByDefault(); + } + + + public static boolean untrustedEmbeddingAnyAppPermission() { + return FEATURE_FLAGS.untrustedEmbeddingAnyAppPermission(); } - + + + public static boolean untrustedEmbeddingStateSharing() { + return FEATURE_FLAGS.untrustedEmbeddingStateSharing(); } - + + + + public static boolean updateDimsWhenWindowShown() { + + return FEATURE_FLAGS.updateDimsWhenWindowShown(); + } + + + + public static boolean useCachedInsetsForDisplaySwitch() { + + return FEATURE_FLAGS.useCachedInsetsForDisplaySwitch(); + } + + + + public static boolean useRtFrameCallbackForSplashScreenTransfer() { + + return FEATURE_FLAGS.useRtFrameCallbackForSplashScreenTransfer(); + } + + + + public static boolean useTasksDimOnly() { + + return FEATURE_FLAGS.useTasksDimOnly(); + } + + + + public static boolean useVisibleRequestedForProcessTracker() { + + return FEATURE_FLAGS.useVisibleRequestedForProcessTracker(); + } + + + public static boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { + return FEATURE_FLAGS.useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds(); } - - public static boolean userMinAspectRatioAppDefault() { - return FEATURE_FLAGS.userMinAspectRatioAppDefault(); - } - - public static boolean waitForTransitionOnDisplaySwitch() { - return FEATURE_FLAGS.waitForTransitionOnDisplaySwitch(); + + + + public static boolean vdmForceAppUniversalResizableApi() { + + return FEATURE_FLAGS.vdmForceAppUniversalResizableApi(); } - + + + public static boolean wallpaperOffsetAsync() { + return FEATURE_FLAGS.wallpaperOffsetAsync(); } - - public static boolean windowSessionRelayoutInfo() { - return FEATURE_FLAGS.windowSessionRelayoutInfo(); - } - - public static boolean windowTokenConfigThreadSafe() { - return FEATURE_FLAGS.windowTokenConfigThreadSafe(); + + + + public static boolean wlinfoOncreate() { + + return FEATURE_FLAGS.wlinfoOncreate(); } private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); diff --git a/flags/src/com/android/wm/shell/CustomFeatureFlags.java b/flags/src/com/android/wm/shell/CustomFeatureFlags.java index e8a55c45914..ce1e21d693a 100644 --- a/flags/src/com/android/wm/shell/CustomFeatureFlags.java +++ b/flags/src/com/android/wm/shell/CustomFeatureFlags.java @@ -1,13 +1,13 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. + import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; - /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -18,124 +18,216 @@ public CustomFeatureFlags(BiPredicate> getValueI } @Override - public boolean animateBubbleSizeChange() { - return getValue(Flags.FLAG_ANIMATE_BUBBLE_SIZE_CHANGE, - FeatureFlags::animateBubbleSizeChange); + public boolean bubbleViewInfoExecutors() { + return getValue(Flags.FLAG_BUBBLE_VIEW_INFO_EXECUTORS, + FeatureFlags::bubbleViewInfoExecutors); } @Override - - public boolean enableAppPairs() { - return getValue(Flags.FLAG_ENABLE_APP_PAIRS, - FeatureFlags::enableAppPairs); + + public boolean enableAutoTaskStackController() { + return getValue(Flags.FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER, + FeatureFlags::enableAutoTaskStackController); } @Override - + public boolean enableBubbleAnything() { return getValue(Flags.FLAG_ENABLE_BUBBLE_ANYTHING, - FeatureFlags::enableBubbleAnything); + FeatureFlags::enableBubbleAnything); } @Override - + public boolean enableBubbleBar() { return getValue(Flags.FLAG_ENABLE_BUBBLE_BAR, - FeatureFlags::enableBubbleBar); + FeatureFlags::enableBubbleBar); + } + + @Override + + public boolean enableBubbleBarOnPhones() { + return getValue(Flags.FLAG_ENABLE_BUBBLE_BAR_ON_PHONES, + FeatureFlags::enableBubbleBarOnPhones); } @Override - + public boolean enableBubbleStashing() { return getValue(Flags.FLAG_ENABLE_BUBBLE_STASHING, - FeatureFlags::enableBubbleStashing); + FeatureFlags::enableBubbleStashing); + } + + @Override + + public boolean enableBubbleTaskViewListener() { + return getValue(Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER, + FeatureFlags::enableBubbleTaskViewListener); + } + + @Override + + public boolean enableBubbleToFullscreen() { + return getValue(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, + FeatureFlags::enableBubbleToFullscreen); } @Override - + public boolean enableBubblesLongPressNavHandle() { return getValue(Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, - FeatureFlags::enableBubblesLongPressNavHandle); + FeatureFlags::enableBubblesLongPressNavHandle); + } + + @Override + + public boolean enableCreateAnyBubble() { + return getValue(Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE, + FeatureFlags::enableCreateAnyBubble); + } + + @Override + + public boolean enableDynamicInsetsForAppLaunch() { + return getValue(Flags.FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH, + FeatureFlags::enableDynamicInsetsForAppLaunch); + } + + @Override + + public boolean enableFlexibleSplit() { + return getValue(Flags.FLAG_ENABLE_FLEXIBLE_SPLIT, + FeatureFlags::enableFlexibleSplit); + } + + @Override + + public boolean enableFlexibleTwoAppSplit() { + return getValue(Flags.FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT, + FeatureFlags::enableFlexibleTwoAppSplit); } @Override - - public boolean enableLeftRightSplitInPortrait() { - return getValue(Flags.FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT, - FeatureFlags::enableLeftRightSplitInPortrait); + + public boolean enableGsf() { + return getValue(Flags.FLAG_ENABLE_GSF, + FeatureFlags::enableGsf); + } + + @Override + + public boolean enableMagneticSplitDivider() { + return getValue(Flags.FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER, + FeatureFlags::enableMagneticSplitDivider); } @Override - + public boolean enableNewBubbleAnimations() { return getValue(Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, - FeatureFlags::enableNewBubbleAnimations); + FeatureFlags::enableNewBubbleAnimations); } @Override - + public boolean enableOptionalBubbleOverflow() { return getValue(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, - FeatureFlags::enableOptionalBubbleOverflow); + FeatureFlags::enableOptionalBubbleOverflow); } @Override - - public boolean enablePip2Implementation() { - return getValue(Flags.FLAG_ENABLE_PIP2_IMPLEMENTATION, - FeatureFlags::enablePip2Implementation); + + public boolean enablePip2() { + return getValue(Flags.FLAG_ENABLE_PIP2, + FeatureFlags::enablePip2); } @Override - + public boolean enablePipUmoExperience() { return getValue(Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, - FeatureFlags::enablePipUmoExperience); + FeatureFlags::enablePipUmoExperience); } @Override - + + public boolean enableRecentsBookendTransition() { + return getValue(Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION, + FeatureFlags::enableRecentsBookendTransition); + } + + @Override + public boolean enableRetrievableBubbles() { return getValue(Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, - FeatureFlags::enableRetrievableBubbles); + FeatureFlags::enableRetrievableBubbles); } @Override - - public boolean enableSplitContextual() { - return getValue(Flags.FLAG_ENABLE_SPLIT_CONTEXTUAL, - FeatureFlags::enableSplitContextual); + + public boolean enableShellTopTaskTracking() { + return getValue(Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING, + FeatureFlags::enableShellTopTaskTracking); } @Override - + + public boolean enableTaskViewControllerCleanup() { + return getValue(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP, + FeatureFlags::enableTaskViewControllerCleanup); + } + + @Override + public boolean enableTaskbarNavbarUnification() { return getValue(Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, - FeatureFlags::enableTaskbarNavbarUnification); + FeatureFlags::enableTaskbarNavbarUnification); } @Override - + + public boolean enableTaskbarOnPhones() { + return getValue(Flags.FLAG_ENABLE_TASKBAR_ON_PHONES, + FeatureFlags::enableTaskbarOnPhones); + } + + @Override + public boolean enableTinyTaskbar() { return getValue(Flags.FLAG_ENABLE_TINY_TASKBAR, - FeatureFlags::enableTinyTaskbar); + FeatureFlags::enableTinyTaskbar); } @Override - + + public boolean fixMissingUserChangeCallbacks() { + return getValue(Flags.FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS, + FeatureFlags::fixMissingUserChangeCallbacks); + } + + @Override + public boolean onlyReuseBubbledTaskWhenLaunchedFromBubble() { return getValue(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, - FeatureFlags::onlyReuseBubbledTaskWhenLaunchedFromBubble); + FeatureFlags::onlyReuseBubbledTaskWhenLaunchedFromBubble); + } + + @Override + + public boolean taskViewRepository() { + return getValue(Flags.FLAG_TASK_VIEW_REPOSITORY, + FeatureFlags::taskViewRepository); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } + private boolean isOptimizationEnabled() { return false; } @@ -146,29 +238,70 @@ protected boolean getValue(String flagName, Predicate getter) { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_ANIMATE_BUBBLE_SIZE_CHANGE, - Flags.FLAG_ENABLE_APP_PAIRS, - Flags.FLAG_ENABLE_BUBBLE_ANYTHING, - Flags.FLAG_ENABLE_BUBBLE_BAR, - Flags.FLAG_ENABLE_BUBBLE_STASHING, - Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, - Flags.FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT, - Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, - Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, - Flags.FLAG_ENABLE_PIP2_IMPLEMENTATION, - Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, - Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, - Flags.FLAG_ENABLE_SPLIT_CONTEXTUAL, - Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, - Flags.FLAG_ENABLE_TINY_TASKBAR, - Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE + Flags.FLAG_BUBBLE_VIEW_INFO_EXECUTORS, + Flags.FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER, + Flags.FLAG_ENABLE_BUBBLE_ANYTHING, + Flags.FLAG_ENABLE_BUBBLE_BAR, + Flags.FLAG_ENABLE_BUBBLE_BAR_ON_PHONES, + Flags.FLAG_ENABLE_BUBBLE_STASHING, + Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER, + Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, + Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, + Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE, + Flags.FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH, + Flags.FLAG_ENABLE_FLEXIBLE_SPLIT, + Flags.FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT, + Flags.FLAG_ENABLE_GSF, + Flags.FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER, + Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, + Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, + Flags.FLAG_ENABLE_PIP2, + Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, + Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION, + Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, + Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING, + Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP, + Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, + Flags.FLAG_ENABLE_TASKBAR_ON_PHONES, + Flags.FLAG_ENABLE_TINY_TASKBAR, + Flags.FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS, + Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, + Flags.FLAG_TASK_VIEW_REPOSITORY ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_ENABLE_PIP2_IMPLEMENTATION, - "" - ) + Arrays.asList( + Flags.FLAG_BUBBLE_VIEW_INFO_EXECUTORS, + Flags.FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER, + Flags.FLAG_ENABLE_BUBBLE_ANYTHING, + Flags.FLAG_ENABLE_BUBBLE_BAR, + Flags.FLAG_ENABLE_BUBBLE_BAR_ON_PHONES, + Flags.FLAG_ENABLE_BUBBLE_STASHING, + Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER, + Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, + Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, + Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE, + Flags.FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH, + Flags.FLAG_ENABLE_FLEXIBLE_SPLIT, + Flags.FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT, + Flags.FLAG_ENABLE_GSF, + Flags.FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER, + Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, + Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, + Flags.FLAG_ENABLE_PIP2, + Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, + Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION, + Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, + Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING, + Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP, + Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, + Flags.FLAG_ENABLE_TASKBAR_ON_PHONES, + Flags.FLAG_ENABLE_TINY_TASKBAR, + Flags.FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS, + Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, + Flags.FLAG_TASK_VIEW_REPOSITORY, + "" + ) ); } diff --git a/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java b/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java index c4594f283fb..ade311576cf 100644 --- a/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; - /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/wm/shell/FeatureFlags.java b/flags/src/com/android/wm/shell/FeatureFlags.java index 5c5ba5bcc03..5441eaed381 100644 --- a/flags/src/com/android/wm/shell/FeatureFlags.java +++ b/flags/src/com/android/wm/shell/FeatureFlags.java @@ -1,53 +1,123 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. + /** @hide */ public interface FeatureFlags { - - boolean animateBubbleSizeChange(); - - - boolean enableAppPairs(); - - + + + boolean bubbleViewInfoExecutors(); + + + + boolean enableAutoTaskStackController(); + + + boolean enableBubbleAnything(); - - + + + boolean enableBubbleBar(); - - + + + + boolean enableBubbleBarOnPhones(); + + + boolean enableBubbleStashing(); - - + + + + boolean enableBubbleTaskViewListener(); + + + + boolean enableBubbleToFullscreen(); + + + boolean enableBubblesLongPressNavHandle(); - - - boolean enableLeftRightSplitInPortrait(); - - + + + + boolean enableCreateAnyBubble(); + + + + boolean enableDynamicInsetsForAppLaunch(); + + + + boolean enableFlexibleSplit(); + + + + boolean enableFlexibleTwoAppSplit(); + + + + boolean enableGsf(); + + + + boolean enableMagneticSplitDivider(); + + + boolean enableNewBubbleAnimations(); - - + + + boolean enableOptionalBubbleOverflow(); - - boolean enablePip2Implementation(); - - + + + + boolean enablePip2(); + + + boolean enablePipUmoExperience(); - - + + + + boolean enableRecentsBookendTransition(); + + + boolean enableRetrievableBubbles(); - - - boolean enableSplitContextual(); - - + + + + boolean enableShellTopTaskTracking(); + + + + boolean enableTaskViewControllerCleanup(); + + + boolean enableTaskbarNavbarUnification(); - - + + + + boolean enableTaskbarOnPhones(); + + + boolean enableTinyTaskbar(); - - + + + + boolean fixMissingUserChangeCallbacks(); + + + boolean onlyReuseBubbledTaskWhenLaunchedFromBubble(); + + + + boolean taskViewRepository(); } diff --git a/flags/src/com/android/wm/shell/FeatureFlagsImpl.java b/flags/src/com/android/wm/shell/FeatureFlagsImpl.java index a5bba4b5ee8..8620fce7338 100644 --- a/flags/src/com/android/wm/shell/FeatureFlagsImpl.java +++ b/flags/src/com/android/wm/shell/FeatureFlagsImpl.java @@ -1,394 +1,209 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. -import com.android.quickstep.util.DeviceConfigHelper; - -import java.nio.file.Files; -import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); - private static volatile boolean isCached = false; - private static volatile boolean multitasking_is_cached = false; - private static boolean animateBubbleSizeChange = false; - private static boolean enableAppPairs = true; - private static boolean enableBubbleAnything = false; - private static boolean enableBubbleBar = false; - private static boolean enableBubbleStashing = false; - private static boolean enableBubblesLongPressNavHandle = false; - private static boolean enableLeftRightSplitInPortrait = true; - private static boolean enableNewBubbleAnimations = false; - private static boolean enableOptionalBubbleOverflow = true; - private static boolean enablePipUmoExperience = false; - private static boolean enableRetrievableBubbles = false; - private static boolean enableSplitContextual = true; - private static boolean enableTaskbarNavbarUnification = true; - private static boolean enableTinyTaskbar = false; - private static boolean onlyReuseBubbledTaskWhenLaunchedFromBubble = false; - + @Override - private void init() { - boolean foundPackage = true; - animateBubbleSizeChange = foundPackage; + public boolean bubbleViewInfoExecutors() { + return true; + } + @Override - enableAppPairs = foundPackage; + public boolean enableAutoTaskStackController() { + return false; + } - enableBubbleAnything = foundPackage; + @Override - enableBubbleBar = foundPackage; + public boolean enableBubbleAnything() { + return false; + } + @Override - enableBubbleStashing = foundPackage; + public boolean enableBubbleBar() { + return false; + } - enableBubblesLongPressNavHandle = foundPackage ; + @Override - enableLeftRightSplitInPortrait = foundPackage; + public boolean enableBubbleBarOnPhones() { + return false; + } + @Override - enableNewBubbleAnimations = foundPackage; + public boolean enableBubbleStashing() { + return false; + } - enableOptionalBubbleOverflow = foundPackage; + @Override + public boolean enableBubbleTaskViewListener() { + return false; + } - enablePipUmoExperience = foundPackage; + @Override - enableRetrievableBubbles = foundPackage; + public boolean enableBubbleToFullscreen() { + return false; + } + @Override - enableSplitContextual = foundPackage; + public boolean enableBubblesLongPressNavHandle() { + return false; + } - enableTaskbarNavbarUnification = foundPackage; + @Override - enableTinyTaskbar = foundPackage; + public boolean enableCreateAnyBubble() { + return false; + } + @Override - onlyReuseBubbledTaskWhenLaunchedFromBubble = foundPackage ; - isCached = true; + public boolean enableDynamicInsetsForAppLaunch() { + return false; } + @Override - - private void load_overrides_multitasking() { - try { - var properties = DeviceConfigHelper.Companion.getPrefs(); - animateBubbleSizeChange = - properties.getBoolean(Flags.FLAG_ANIMATE_BUBBLE_SIZE_CHANGE, false); - enableAppPairs = - properties.getBoolean(Flags.FLAG_ENABLE_APP_PAIRS, true); - enableBubbleAnything = - properties.getBoolean(Flags.FLAG_ENABLE_BUBBLE_ANYTHING, false); - enableBubbleBar = - properties.getBoolean(Flags.FLAG_ENABLE_BUBBLE_BAR, false); - enableBubbleStashing = - properties.getBoolean(Flags.FLAG_ENABLE_BUBBLE_STASHING, false); - enableBubblesLongPressNavHandle = - properties.getBoolean(Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, false); - enableLeftRightSplitInPortrait = - properties.getBoolean(Flags.FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT, true); - enableNewBubbleAnimations = - properties.getBoolean(Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, false); - enableOptionalBubbleOverflow = - properties.getBoolean(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, true); - enablePipUmoExperience = - properties.getBoolean(Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, false); - enableRetrievableBubbles = - properties.getBoolean(Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, false); - enableSplitContextual = - properties.getBoolean(Flags.FLAG_ENABLE_SPLIT_CONTEXTUAL, true); - enableTaskbarNavbarUnification = - properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, true); - enableTinyTaskbar = - properties.getBoolean(Flags.FLAG_ENABLE_TINY_TASKBAR, false); - onlyReuseBubbledTaskWhenLaunchedFromBubble = - properties.getBoolean(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, false); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value from namespace multitasking " - + "from DeviceConfig. It could be that the code using flag " - + "executed before SettingsProvider initialization. Please use " - + "fixed read-only flag by adding is_fixed_read_only: true in " - + "flag declaration.", - e - ); - } - multitasking_is_cached = true; + public boolean enableFlexibleSplit() { + return false; } @Override - - - public boolean animateBubbleSizeChange() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return animateBubbleSizeChange; + + public boolean enableFlexibleTwoAppSplit() { + return false; } @Override - - - public boolean enableAppPairs() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableAppPairs; + + public boolean enableGsf() { + return true; } @Override - - - public boolean enableBubbleAnything() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableBubbleAnything; + + public boolean enableMagneticSplitDivider() { + return false; } @Override - - - public boolean enableBubbleBar() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableBubbleBar; + + public boolean enableNewBubbleAnimations() { + return false; } @Override - - - public boolean enableBubbleStashing() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableBubbleStashing; + + public boolean enableOptionalBubbleOverflow() { + return false; } @Override - - - public boolean enableBubblesLongPressNavHandle() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableBubblesLongPressNavHandle; + + public boolean enablePip2() { + return false; } @Override - - - public boolean enableLeftRightSplitInPortrait() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableLeftRightSplitInPortrait; + + public boolean enablePipUmoExperience() { + return false; } @Override - - - public boolean enableNewBubbleAnimations() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableNewBubbleAnimations; + + public boolean enableRecentsBookendTransition() { + return false; } @Override - - - public boolean enableOptionalBubbleOverflow() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableOptionalBubbleOverflow; + + public boolean enableRetrievableBubbles() { + return false; } @Override - - - public boolean enablePip2Implementation() { - return false; + + public boolean enableShellTopTaskTracking() { + return false; } @Override - - - public boolean enablePipUmoExperience() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enablePipUmoExperience; + + public boolean enableTaskViewControllerCleanup() { + return true; } @Override - - - public boolean enableRetrievableBubbles() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableRetrievableBubbles; + + public boolean enableTaskbarNavbarUnification() { + return true; } @Override - - - public boolean enableSplitContextual() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableSplitContextual; + + public boolean enableTaskbarOnPhones() { + return true; } @Override - - - public boolean enableTaskbarNavbarUnification() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableTaskbarNavbarUnification; + + public boolean enableTinyTaskbar() { + return false; } @Override - - - public boolean enableTinyTaskbar() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return enableTinyTaskbar; + + public boolean fixMissingUserChangeCallbacks() { + return false; } @Override - - + + public boolean onlyReuseBubbledTaskWhenLaunchedFromBubble() { - if (isReadFromNew) { - if (!isCached) { - init(); - } - } else { - if (!multitasking_is_cached) { - load_overrides_multitasking(); - } - } - return onlyReuseBubbledTaskWhenLaunchedFromBubble; + return true; + } + @Override + + + public boolean taskViewRepository() { + return false; } } - diff --git a/flags/src/com/android/wm/shell/Flags.java b/flags/src/com/android/wm/shell/Flags.java index 5dd4ab74708..d5dbf382c44 100644 --- a/flags/src/com/android/wm/shell/Flags.java +++ b/flags/src/com/android/wm/shell/Flags.java @@ -1,119 +1,271 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. + + /** @hide */ public final class Flags { /** @hide */ - public static final String FLAG_ANIMATE_BUBBLE_SIZE_CHANGE = "com.android.wm.shell.animate_bubble_size_change"; + public static final String FLAG_BUBBLE_VIEW_INFO_EXECUTORS = "com.android.wm.shell.bubble_view_info_executors"; /** @hide */ - public static final String FLAG_ENABLE_APP_PAIRS = "com.android.wm.shell.enable_app_pairs"; + public static final String FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER = "com.android.wm.shell.enable_auto_task_stack_controller"; /** @hide */ public static final String FLAG_ENABLE_BUBBLE_ANYTHING = "com.android.wm.shell.enable_bubble_anything"; /** @hide */ public static final String FLAG_ENABLE_BUBBLE_BAR = "com.android.wm.shell.enable_bubble_bar"; /** @hide */ + public static final String FLAG_ENABLE_BUBBLE_BAR_ON_PHONES = "com.android.wm.shell.enable_bubble_bar_on_phones"; + /** @hide */ public static final String FLAG_ENABLE_BUBBLE_STASHING = "com.android.wm.shell.enable_bubble_stashing"; /** @hide */ + public static final String FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER = "com.android.wm.shell.enable_bubble_task_view_listener"; + /** @hide */ + public static final String FLAG_ENABLE_BUBBLE_TO_FULLSCREEN = "com.android.wm.shell.enable_bubble_to_fullscreen"; + /** @hide */ public static final String FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE = "com.android.wm.shell.enable_bubbles_long_press_nav_handle"; /** @hide */ - public static final String FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT = "com.android.wm.shell.enable_left_right_split_in_portrait"; + public static final String FLAG_ENABLE_CREATE_ANY_BUBBLE = "com.android.wm.shell.enable_create_any_bubble"; + /** @hide */ + public static final String FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH = "com.android.wm.shell.enable_dynamic_insets_for_app_launch"; + /** @hide */ + public static final String FLAG_ENABLE_FLEXIBLE_SPLIT = "com.android.wm.shell.enable_flexible_split"; + /** @hide */ + public static final String FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT = "com.android.wm.shell.enable_flexible_two_app_split"; + /** @hide */ + public static final String FLAG_ENABLE_GSF = "com.android.wm.shell.enable_gsf"; + /** @hide */ + public static final String FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER = "com.android.wm.shell.enable_magnetic_split_divider"; /** @hide */ public static final String FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS = "com.android.wm.shell.enable_new_bubble_animations"; /** @hide */ public static final String FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW = "com.android.wm.shell.enable_optional_bubble_overflow"; /** @hide */ - public static final String FLAG_ENABLE_PIP2_IMPLEMENTATION = "com.android.wm.shell.enable_pip2_implementation"; + public static final String FLAG_ENABLE_PIP2 = "com.android.wm.shell.enable_pip2"; /** @hide */ public static final String FLAG_ENABLE_PIP_UMO_EXPERIENCE = "com.android.wm.shell.enable_pip_umo_experience"; /** @hide */ + public static final String FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION = "com.android.wm.shell.enable_recents_bookend_transition"; + /** @hide */ public static final String FLAG_ENABLE_RETRIEVABLE_BUBBLES = "com.android.wm.shell.enable_retrievable_bubbles"; /** @hide */ - public static final String FLAG_ENABLE_SPLIT_CONTEXTUAL = "com.android.wm.shell.enable_split_contextual"; + public static final String FLAG_ENABLE_SHELL_TOP_TASK_TRACKING = "com.android.wm.shell.enable_shell_top_task_tracking"; + /** @hide */ + public static final String FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP = "com.android.wm.shell.enable_task_view_controller_cleanup"; /** @hide */ public static final String FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION = "com.android.wm.shell.enable_taskbar_navbar_unification"; /** @hide */ + public static final String FLAG_ENABLE_TASKBAR_ON_PHONES = "com.android.wm.shell.enable_taskbar_on_phones"; + /** @hide */ public static final String FLAG_ENABLE_TINY_TASKBAR = "com.android.wm.shell.enable_tiny_taskbar"; /** @hide */ + public static final String FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS = "com.android.wm.shell.fix_missing_user_change_callbacks"; + /** @hide */ public static final String FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE = "com.android.wm.shell.only_reuse_bubbled_task_when_launched_from_bubble"; - - - public static boolean animateBubbleSizeChange() { - return FEATURE_FLAGS.animateBubbleSizeChange(); - } - - - public static boolean enableAppPairs() { - return FEATURE_FLAGS.enableAppPairs(); - } - - + /** @hide */ + public static final String FLAG_TASK_VIEW_REPOSITORY = "com.android.wm.shell.task_view_repository"; + + + + public static boolean bubbleViewInfoExecutors() { + + return FEATURE_FLAGS.bubbleViewInfoExecutors(); + } + + + + public static boolean enableAutoTaskStackController() { + + return FEATURE_FLAGS.enableAutoTaskStackController(); + } + + + public static boolean enableBubbleAnything() { + return FEATURE_FLAGS.enableBubbleAnything(); } - - + + + public static boolean enableBubbleBar() { + return FEATURE_FLAGS.enableBubbleBar(); } - - + + + + public static boolean enableBubbleBarOnPhones() { + + return FEATURE_FLAGS.enableBubbleBarOnPhones(); + } + + + public static boolean enableBubbleStashing() { + return FEATURE_FLAGS.enableBubbleStashing(); } - - + + + + public static boolean enableBubbleTaskViewListener() { + + return FEATURE_FLAGS.enableBubbleTaskViewListener(); + } + + + + public static boolean enableBubbleToFullscreen() { + + return FEATURE_FLAGS.enableBubbleToFullscreen(); + } + + + public static boolean enableBubblesLongPressNavHandle() { + return FEATURE_FLAGS.enableBubblesLongPressNavHandle(); } - - - public static boolean enableLeftRightSplitInPortrait() { - return FEATURE_FLAGS.enableLeftRightSplitInPortrait(); + + + + public static boolean enableCreateAnyBubble() { + + return FEATURE_FLAGS.enableCreateAnyBubble(); + } + + + + public static boolean enableDynamicInsetsForAppLaunch() { + + return FEATURE_FLAGS.enableDynamicInsetsForAppLaunch(); + } + + + + public static boolean enableFlexibleSplit() { + + return FEATURE_FLAGS.enableFlexibleSplit(); } - - + + + + public static boolean enableFlexibleTwoAppSplit() { + + return FEATURE_FLAGS.enableFlexibleTwoAppSplit(); + } + + + + public static boolean enableGsf() { + + return FEATURE_FLAGS.enableGsf(); + } + + + + public static boolean enableMagneticSplitDivider() { + + return FEATURE_FLAGS.enableMagneticSplitDivider(); + } + + + public static boolean enableNewBubbleAnimations() { + return FEATURE_FLAGS.enableNewBubbleAnimations(); } - - + + + public static boolean enableOptionalBubbleOverflow() { + return FEATURE_FLAGS.enableOptionalBubbleOverflow(); } - - public static boolean enablePip2Implementation() { - return FEATURE_FLAGS.enablePip2Implementation(); + + + + public static boolean enablePip2() { + + return FEATURE_FLAGS.enablePip2(); } - - + + + public static boolean enablePipUmoExperience() { + return FEATURE_FLAGS.enablePipUmoExperience(); } - - + + + + public static boolean enableRecentsBookendTransition() { + + return FEATURE_FLAGS.enableRecentsBookendTransition(); + } + + + public static boolean enableRetrievableBubbles() { + return FEATURE_FLAGS.enableRetrievableBubbles(); } - - - public static boolean enableSplitContextual() { - return FEATURE_FLAGS.enableSplitContextual(); + + + + public static boolean enableShellTopTaskTracking() { + + return FEATURE_FLAGS.enableShellTopTaskTracking(); + } + + + + public static boolean enableTaskViewControllerCleanup() { + + return FEATURE_FLAGS.enableTaskViewControllerCleanup(); } - - + + + public static boolean enableTaskbarNavbarUnification() { + return FEATURE_FLAGS.enableTaskbarNavbarUnification(); } - - + + + + public static boolean enableTaskbarOnPhones() { + + return FEATURE_FLAGS.enableTaskbarOnPhones(); + } + + + public static boolean enableTinyTaskbar() { + return FEATURE_FLAGS.enableTinyTaskbar(); } - - + + + + public static boolean fixMissingUserChangeCallbacks() { + + return FEATURE_FLAGS.fixMissingUserChangeCallbacks(); + } + + + public static boolean onlyReuseBubbledTaskWhenLaunchedFromBubble() { + return FEATURE_FLAGS.onlyReuseBubbledTaskWhenLaunchedFromBubble(); } + + + public static boolean taskViewRepository() { + + return FEATURE_FLAGS.taskViewRepository(); + } + private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } diff --git a/go/AndroidManifest-launcher.xml b/go/AndroidManifest-launcher.xml index 8e427d4c31f..cc4072e2624 100644 --- a/go/AndroidManifest-launcher.xml +++ b/go/AndroidManifest-launcher.xml @@ -56,6 +56,7 @@ android:enabled="true"> + diff --git a/go/quickstep/res/values-af/strings.xml b/go/quickstep/res/values-af/strings.xml index 501d297a8d8..f4ce476582e 100644 --- a/go/quickstep/res/values-af/strings.xml +++ b/go/quickstep/res/values-af/strings.xml @@ -1,7 +1,7 @@ - "Deel program" + "Deel app" "Luister" "Vertaal" "Lens" @@ -9,12 +9,12 @@ "KANSELLEER" "INSTELLINGS" "Vertaal of luister na teks op skerm" - "Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na ""Instellings > Programme > Verstekprogramme > Digitale Assistent-program"" om te verander watter inligting jy deel." + "Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na ""Instellings > Apps > Verstekapps > Digitale Assistent-app"" om te verander watter inligting jy deel." "Kies \'n assistent om hierdie kenmerk te gebruik" "Kies \'n digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal" "Verander jou assistent om hierdie kenmerk te gebruik" "Verander jou digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal" "Tik hier om na teks op hierdie skerm te luister" "Tik hier om teks op hierdie skerm te vertaal" - "Hierdie program kan nie gedeel word nie" + "Hierdie app kan nie gedeel word nie" diff --git a/go/quickstep/res/values-be/strings.xml b/go/quickstep/res/values-be/strings.xml index 83374bb397e..de8766f7088 100644 --- a/go/quickstep/res/values-be/strings.xml +++ b/go/quickstep/res/values-be/strings.xml @@ -4,7 +4,7 @@ "Абагуліць праграму" "Праслухаць" "Перакласці" - "Аб\'ектыў" + "Аб’ектыў" "ЗРАЗУМЕЛА" "СКАСАВАЦЬ" "НАЛАДЫ" diff --git a/go/quickstep/res/values-fa/strings.xml b/go/quickstep/res/values-fa/strings.xml index 8453d4e3e2d..f0e4a57712d 100644 --- a/go/quickstep/res/values-fa/strings.xml +++ b/go/quickstep/res/values-fa/strings.xml @@ -5,7 +5,7 @@ "گوش دادن" "ترجمه" "لنز" - "متوجه‌ام" + "متوجهم" "لغو" "تنظیمات" "ترجمه نوشتار روی صفحه‌نمایش یا گوش دادن به آن" diff --git a/go/quickstep/res/values-iw/strings.xml b/go/quickstep/res/values-iw/strings.xml index ddb8ddd9a9f..db661066e0d 100644 --- a/go/quickstep/res/values-iw/strings.xml +++ b/go/quickstep/res/values-iw/strings.xml @@ -14,7 +14,7 @@ "כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לבחור אפליקציית עוזר דיגיטלי ב\'הגדרות\'" "צריך לשנות את העוזר הדיגיטלי כדי להשתמש בתכונה הזו" "כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לשנות את אפליקציית העוזר הדיגיטלי ב\'הגדרות\'" - "צריך להקיש כאן כדי להאזין לטקסט שבמסך הזה" - "צריך להקיש כאן כדי לתרגם את הטקסט שבמסך הזה" + "צריך ללחוץ כאן כדי להאזין לטקסט שבמסך הזה" + "צריך ללחוץ כאן כדי לתרגם את הטקסט שבמסך הזה" "אי אפשר לשתף את האפליקציה הזו" diff --git a/go/quickstep/res/values-ne/strings.xml b/go/quickstep/res/values-ne/strings.xml index e66f06373d5..4f771c3ec40 100644 --- a/go/quickstep/res/values-ne/strings.xml +++ b/go/quickstep/res/values-ne/strings.xml @@ -9,11 +9,11 @@ "रद्द गर्नुहोस्" "सेटिङ" "स्क्रिनमा देखिने पाठ अनुवाद गर्नुहोस् वा पढेर सुनाउनुहोस्" - "तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न ""सेटिङ > एप > डिफल्ट एप > डिजिटल सहायक एप"" मा जानुहोस्।" + "तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न ""सेटिङ > एप > डिफल्ट एप > डिजिटल एसिस्टेन्ट एप"" मा जानुहोस्।" "तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने कुनै सहायक छनौट गर्नुहोस्" - "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप छनौट गर्नुहोस्" + "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल एसिस्टेन्ट एप छनौट गर्नुहोस्" "तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने आफ्नो सहायक परिवर्तन गर्नुहोस्" - "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप परिर्वर्तन गर्नुहोस्" + "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल एसिस्टेन्ट एप परिर्वर्तन गर्नुहोस्" "तपाईं यो स्क्रिनमा देखिने पाठ सुन्न चाहनुहुन्छ यहाँ ट्याप गर्नुहोस्" "तपाईं यो स्क्रिनमा देखिने पाठ अनुवाद गर्न चाहनुहुन्छ यहाँ ट्याप गर्नुहोस्" "यो एप अरूलाई चलाउन दिन मिल्दैन" diff --git a/go/quickstep/res/values/styles.xml b/go/quickstep/res/values/styles.xml index c659331bde3..2524c760f40 100644 --- a/go/quickstep/res/values/styles.xml +++ b/go/quickstep/res/values/styles.xml @@ -16,7 +16,7 @@ --> - + \ No newline at end of file diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml index ca44a6914c2..ea6f39c1e12 100644 --- a/quickstep/res/values-nl/strings.xml +++ b/quickstep/res/values-nl/strings.xml @@ -22,11 +22,13 @@ "Vastzetten" "Vrije vorm" "Desktop" + "Verplaatsen naar extern scherm" + "Sluiten" + "Desktop" "Geen recente items" "Instellingen voor app-gebruik" "Alles wissen" "Recente apps" - "Taak gesloten" "%1$s, %2$s" "< 1 minuut" "Nog %1$s vandaag" @@ -45,6 +47,7 @@ "App-suggesties staan aan" "App-suggesties staan uit" "Voorspelde app: %1$s" + "Tutorial voor navigatie met gebaren" "Het apparaat draaien" "Draai het apparaat om de tutorial voor navigatie met gebaren af te ronden" "Swipe vanaf de rechter- of linkerrand" @@ -56,17 +59,15 @@ "Open Instellingen om de gevoeligheid van Terug te wijzigen" "Swipe om terug te gaan" "Swipe vanaf de linker- of rechterrand naar het midden om terug te gaan naar het vorige scherm." - "Als je wilt teruggaan naar het laatste scherm, swipe je met 2 vingers vanaf de linker- of rechterrand naar het midden van het scherm." "Terug" "Swipe vanaf de linker- of rechterrand naar het midden van het scherm" "Swipe vanaf de onderrand van het scherm omhoog" "Pauzeer niet voordat je loslaat" "Swipe recht omhoog" "Je weet nu hoe je het gebaar Naar startscherm maakt. Ontdek als volgende hoe je kunt teruggaan." - "Je weet nu hoe je teruggaat naar het startscherm" + "Je weet nu hoe je het gebaar Naar het startscherm maakt" "Swipe om naar het startscherm te gaan" "Swipe omhoog vanaf de onderkant van het scherm. Met dit gebaar ga je altijd naar het startscherm." - "Swipe met 2 vingers omhoog vanaf de onderkant van het scherm. Met dit gebaar ga je altijd naar het startscherm." "Naar het startscherm" "Swipe omhoog vanaf de onderkant van het scherm" "Goed gedaan" @@ -77,7 +78,6 @@ "Je weet nu hoe je het gebaar Schakelen tussen apps maakt" "Swipe om tussen apps te schakelen" "Swipe omhoog vanaf de onderkant van het scherm, houd vast en laat los om tussen apps te schakelen." - "Swipe met 2 vingers omhoog vanaf de onderkant van het scherm, houd vast en laat los om tussen apps te schakelen." "Schakelen tussen apps" "Swipe omhoog vanaf de onderkant van het scherm, houd je vinger op het scherm en laat dan los" "Goed bezig" @@ -99,7 +99,7 @@ "App-paar opslaan" "Tik op nog een app om je scherm te splitsen" "Kies een andere app om gesplitst scherm te gebruiken" - "Annuleren" + "Annuleren" "Sluit de selectie voor gesplitst scherm" "Kies andere app om gesplitst scherm te gebruiken" "Deze actie wordt niet toegestaan door de app of je organisatie" @@ -130,18 +130,37 @@ "Snelle instellingen" "Taakbalk" "Taakbalk wordt getoond" - "Taakbalk is verborgen" + "Taakbalk en bubbels links getoond" + "Taakbalk en bubbels rechts getoond" "Navigatiebalk" "Taakbalk altijd tonen" "Navigatiemodus wijzigen" "Scheiding voor Taakbalk" + "Taakbalkoverloop" "Naar boven/links verplaatsen" "Naar beneden/rechts verplaatsen" - "{count,plural, =1{Nog # app tonen.}other{Nog # apps tonen.}}" - "{count,plural, =1{# desktop-app tonen.}other{# desktop-apps tonen.}}" + "App openen als ballon" + "Recente apps" + "Lijst met recente apps" + "{count,plural, =1{extra app}other{extra apps}}" + "Desktop" "%1$s en %2$s" + "%1$s, item %2$d van %3$d" + "Naar links scrollen" + "Naar rechts scrollen" "Bubbel" "Overloop" "%1$s van %2$s" "%1$s en nog %2$d" + "Naar links verplaatsen" + "Naar rechts verplaatsen" + "Alles sluiten" + "%1$s uitvouwen" + "%1$s samenvouwen" + "Circle to Search" + "Icoon van app" + "Titel van app" + "Knop Sluiten" + "Vastzetten op taakbalk" + "Losmaken van taakbalk" diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml index bf0bdc83236..e3664bc00fa 100644 --- a/quickstep/res/values-or/strings.xml +++ b/quickstep/res/values-or/strings.xml @@ -22,11 +22,13 @@ "ପିନ୍‍" "ଫ୍ରିଫର୍ମ" "ଡେସ୍କଟପ" + "ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମୁଭ କରନ୍ତୁ" + "ବନ୍ଦ କରନ୍ତୁ" + "ଡେସ୍କଟପ" "ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ" "ଆପ ବ୍ୟବହାର ସେଟିଂସ" "ସବୁ ଖାଲି କରନ୍ତୁ" "ବର୍ତ୍ତମାନର ଆପ୍‌" - "ଟାସ୍କ ବନ୍ଦ ହୋଇଯାଇଛି" "%1$s %2$s" "< 1 ମିନିଟ୍" "ଆଜି %1$s ବାକି ଅଛି" @@ -45,18 +47,18 @@ "ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ସକ୍ଷମ କରାଯାଇଛି" "ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ଅକ୍ଷମ କରାଯାଇଛି" "ପୂର୍ବାନୁମାନ କରାଯାଇଥିବା ଆପ୍: %1$s" + "ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ" "ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ" "ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ ସମ୍ପୂର୍ଣ୍ଣ କରିବାକୁ ଦୟାକରି ଆପଣଙ୍କ ଡିଭାଇସ ରୋଟେଟ କରନ୍ତୁ" - "ଆପଣ ସ୍କ୍ରିନର ଏକଦମ୍-ଡାହାଣ ବା ବାମ ଧାରରୁ ସ୍ୱାଇପ୍ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" + "ଆପଣ ସ୍କ୍ରିନର ଏକଦମ-ଡାହାଣ ବା ବାମ ଧାରରୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" "ଆପଣ ସ୍କ୍ରିନର ଡାହାଣ ବା ବାମ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରି ଛାଡ଼ି ଦେଉଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" "ଆପଣ ଡାହାଣରୁ ସ୍ୱାଇପ୍ କରି ପଛକୁ କିପରି ଫେରିବେ ତାହା ଜାଣିଲେ। ତା\'ପରେ, ଆପକୁ କିପରି ସ୍ୱିଚ୍ କରିବେ ତାହା ଜାଣନ୍ତୁ।" "ଆପଣ \'ପଛକୁ ଫେରନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି। ତା\'ପରେ, ଆପଗୁଡ଼ିକୁ କିପରି ସ୍ୱିଚ୍ କରିବେ ତାହା ଜାଣନ୍ତୁ।" - "ଆପଣ \'ପଛକୁ ଫେରନ୍ତୁ\' ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି" + "ଆପଣ ପଛକୁ ଫେରନ୍ତୁ ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି" "ଆପଣ ସ୍କ୍ରିନର ତଳଭାଗର ଅତି ନିକଟରୁ ସ୍ୱାଇପ କରୁନଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" "ପଛକୁ ଫେରିବା ଜେଶ୍ଚରର ସମ୍ବେଦନଶୀଳତା ବଦଳାଇବାକୁ ସେଟିଂସକୁ ଯାଆନ୍ତୁ" "ପଛକୁ ଫେରିବା ପାଇଁ ସ୍ୱାଇପ୍ କରନ୍ତୁ" "ପୂର୍ବ ସ୍କ୍ରିନକୁ ଫେରିବା ପାଇଁ, ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରନ୍ତୁ।" - "ପୂର୍ବ ସ୍କ୍ରିନକୁ ଫେରିବା ପାଇଁ, ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ 2ଟି ଆଙ୍ଗୁଠିରେ ସ୍ୱାଇପ କରନ୍ତୁ।" "ପଛକୁ ଫେରନ୍ତୁ" "ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରନ୍ତୁ" "ଆପଣ ସ୍କ୍ରିନର ତଳ ଧାରରୁ ଉପରକୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" @@ -66,20 +68,18 @@ "ଆପଣ \'ମୂଳପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" "ହୋମକୁ ଯିବା ପାଇଁ ସ୍ୱାଇପ କରନ୍ତୁ" "ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଏହି ଜେଶ୍ଚର ସର୍ବଦା ଆପଣଙ୍କୁ ହୋମ ସ୍କ୍ରିନକୁ ନେଇଥାଏ।" - "2ଟି ଆଙ୍ଗୁଠିରେ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଏହି ଜେଶ୍ଚର ସର୍ବଦା ଆପଣଙ୍କୁ ହୋମ ସ୍କ୍ରିନକୁ ନେଇଥାଏ।" "ହୋମକୁ ଯାଆନ୍ତୁ" "ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ" "ବଢ଼ିଆ କାମ!" "ଆପଣ ସ୍କ୍ରିନର ତଳ ଧାରରୁ ଉପରକୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" "ୱିଣ୍ଡୋକୁ ରିଲିଜ୍ କରିବା ପୂର୍ବରୁ ଅଧିକ ସମୟ ଧରି ରଖିବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ।" - "ଆପଣ ସିଧା ଉପରକୁ ସ୍ୱାଇପ୍ କରି ତା\'ପରେ ବିରତ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" + "ଆପଣ ସିଧା ଉପରକୁ ସ୍ୱାଇପ କରି ତା\'ପରେ ବିରତ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" "ଜେଶ୍ଚରଗୁଡ଼ିକୁ କିପରି ବ୍ୟବହାର କରାଯିବ ଆପଣ ତାହା ଶିଖିଛନ୍ତି। ଜେଶ୍ଚରଗୁଡ଼ିକୁ ବନ୍ଦ କରିବାକୁ, ସେଟିଂସକୁ ଯାଆନ୍ତୁ।" - "ଆପଣ \'ଆପଗୁଡ଼ିକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" + "ଆପଣ ଆପ୍ସ ସୁଇଚ କରନ୍ତୁ ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" "ଆପଗୁଡ଼ିକୁ ସ୍ୱିଚ୍ କରିବା ପାଇଁ ସ୍ୱାଇପ୍ କରନ୍ତୁ" "ଆପ୍ସ ମଧ୍ୟରେ ସୁଇଚ କରିବାକୁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ, ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କରନ୍ତୁ।" - "ଆପ୍ସ ମଧ୍ୟରେ ସ୍ୱିଚ କରିବାକୁ, 2ଟି ଆଙ୍ଗୁଠିରେ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କର।" "ଆପ୍ସ ସୁଇଚ କରନ୍ତୁ" - "ଆପଣଙ୍କ ସ୍କ୍ରିନ୍‌ର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ୍ କରନ୍ତୁ" + "ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କରନ୍ତୁ" "ବହୁତ ବଢ଼ିଆ!" "ସବୁ ପ୍ରସ୍ତୁତ" "ହୋଇଗଲା" @@ -99,7 +99,7 @@ "ଆପ ପେୟାର ସେଭ କରନ୍ତୁ" "ସ୍ପ୍ଲିଟସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପରେ ଟାପ କର" "ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପ ବାଛନ୍ତୁ" - "ବାତିଲ କରନ୍ତୁ" + "ବାତିଲ କରନ୍ତୁ" "ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଚୟନରୁ ବାହାରି ଯାଆନ୍ତୁ" "ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପ ବାଛନ୍ତୁ" "ଆପ୍ କିମ୍ବା ଆପଣଙ୍କ ସଂସ୍ଥା ଦ୍ୱାରା ଏହି କାର୍ଯ୍ୟକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ" @@ -130,18 +130,37 @@ "କୁଇକ ସେଟିଂସ" "ଟାସ୍କବାର" "ଟାସ୍କବାର ଦେଖାଯାଇଛି" - "ଟାସ୍କବାର ଲୁଚାଯାଇଛି" + "ଟାସ୍କବାର ଓ ବବଲ ବାମରେ ଦେଖାଯାଇଛି" + "ଟାସ୍କବାର ଓ ବବଲ ଡାହାଣରେ ଦେଖାଯାଇଛି" "ନାଭିଗେସନ ବାର" "ସର୍ବଦା ଟାସ୍କବାର ଦେଖାନ୍ତୁ" "ନାଭିଗେସନ ମୋଡ ପରିବର୍ତ୍ତନ କରନ୍ତୁ" "ଟାସ୍କବାର ଡିଭାଇଡର" + "ଟାସ୍କବାର ଓଭରଫ୍ଲୋ" "ଶୀର୍ଷ/ବାମକୁ ମୁଭ କରନ୍ତୁ" "ନିମ୍ନ/ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ" - "{count,plural, =1{ଅଧିକ #ଟି ଆପ ଦେଖାନ୍ତୁ।}other{ଅଧିକ #ଟି ଆପ୍ସ ଦେଖାନ୍ତୁ।}}" - "{count,plural, =1{# ଡେସ୍କଟପ ଆପ ଦେଖାନ୍ତୁ।}other{# ଡେସ୍କଟପ ଆପ୍ସ ଦେଖାନ୍ତୁ।}}" + "ଏକ ବବଲ ଭାବେ ଆପ ଖୋଲନ୍ତୁ" + "ବର୍ତ୍ତମାନର ଆପ" + "ବର୍ତ୍ତମାନର ଆପ ତାଲିକା" + "{count,plural, =1{ଅଧିକ ଆପ}other{ଅଧିକ ଆପ୍ସ}}" + "ଡେସ୍କଟପ" "%1$s ଏବଂ %2$s" + "%1$s, %3$d%2$d ଆଇଟମ" + "ବାମକୁ ସ୍କ୍ରୋଲ କରନ୍ତୁ" + "ଡାହାଣକୁ ସ୍କ୍ରୋଲ କରନ୍ତୁ" "ବବଲ" "ଓଭରଫ୍ଲୋ" "%2$sରୁ %1$s" "%1$s ଏବଂ ଅଧିକ %2$d" + "ବାମକୁ ମୁଭ କରନ୍ତୁ" + "ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ" + "ସବୁ ଖାରଜ କରନ୍ତୁ" + "%1$s ବିସ୍ତାର କରନ୍ତୁ" + "%1$s ସଙ୍କୁଚିତ କରନ୍ତୁ" + "ସର୍ଚ୍ଚ କରିବାକୁ ସର୍କଲ କରନ୍ତୁ" + "ଆପ ଆଇକନ" + "ଆପ ଟାଇଟେଲ" + "\"ବନ୍ଦ କରନ୍ତୁ\" ବଟନ" + "ଟାସ୍କବାରରେ ପିନ କର" + "ଟାସ୍କବାରରୁ ଅନପିନ କର" diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml index fc603969a77..bcf1683d774 100644 --- a/quickstep/res/values-pa/strings.xml +++ b/quickstep/res/values-pa/strings.xml @@ -22,11 +22,13 @@ "ਪਿੰਨ ਕਰੋ" "ਫ੍ਰੀਫਾਰਮ" "ਡੈਸਕਟਾਪ" + "ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਜਾਓ" + "ਬੰਦ ਕਰੋ" + "ਡੈਸਕਟਾਪ" "ਕੋਈ ਹਾਲੀਆ ਆਈਟਮ ਨਹੀਂ" "ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ" "ਸਭ ਕਲੀਅਰ ਕਰੋ" "ਹਾਲੀਆ ਐਪਾਂ" - "ਕਾਰਜ ਬੰਦ" "%1$s, %2$s" "< 1 ਮਿੰਟ" "ਅੱਜ %1$s ਬਾਕੀ" @@ -45,6 +47,7 @@ "ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ" "ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ" "ਪੂਰਵ ਅਨੁਮਾਨਿਤ ਐਪ: %1$s" + "ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਸੰਬੰਧੀ ਟਿਊਟੋਰੀਅਲ" "ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ" "ਕਿਰਪਾ ਕਰਕੇ ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਪੂਰਾ ਕਰਨ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ" "ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸੱਜੇ ਜਾਂ ਖੱਬੇ ਪਾਸੇ ਦੇ ਬਿਲਕੁਲ ਕਿਨਾਰੇ ਤੋਂ ਸਵਾਈਪ ਕਰਦੇ ਹੋ" @@ -56,7 +59,6 @@ "ਪਿੱਛੇ ਜਾਣ ਦੇ ਸੰਕੇਤ ਦੀ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਬਦਲਣ ਲਈ, ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ" "ਪਿੱਛੇ ਜਾਣ ਲਈ ਸਵਾਈਪ ਕਰੋ" "ਪਿਛਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਵਾਪਸ ਜਾਣ ਲਈ, ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ ਸਵਾਈਪ ਕਰੋ।" - "ਪਿਛਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਵਾਪਸ ਜਾਣ ਲਈ, ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ 2 ਉਂਗਲਾਂ ਨਾਲ ਸਵਾਈਪ ਕਰੋ।" "ਵਾਪਸ ਜਾਓ" "ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ ਸਵਾਈਪ ਕਰੋ" "ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਲੇ ਕਿਨਾਰੇ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ" @@ -66,7 +68,6 @@ "ਤੁਸੀਂ \'ਹੋਮ \'ਤੇ ਜਾਓ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ" "ਹੋਮ \'ਤੇ ਜਾਣ ਲਈ ਸਵਾਈਪ ਕਰੋ" "ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ। ਇਹ ਇਸ਼ਾਰਾ ਹਮੇਸ਼ਾਂ ਤੁਹਾਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਲੈ ਜਾਂਦਾ ਹੈ।" - "ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ 2 ਉਂਗਲਾਂ ਨਾਲ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ। ਇਹ ਇਸ਼ਾਰਾ ਹਮੇਸ਼ਾਂ ਤੁਹਾਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਲੈ ਜਾਂਦਾ ਹੈ।" "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਓ" "ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ" "ਬਹੁਤ ਵਧੀਆ!" @@ -74,10 +75,9 @@ "ਛੱਡਣ ਤੋਂ ਪਹਿਲਾਂ ਵਿੰਡੋ ਨੂੰ ਕੁਝ ਸਮੇਂ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ" "ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸਿੱਧਾ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ, ਫਿਰ ਰੋਕੋ" "ਤੁਸੀਂ ਇਸ਼ਾਰੇ ਵਰਤਣ ਬਾਰੇ ਜਾਣਿਆ। ਇਸ਼ਾਰੇ ਬੰਦ ਕਰਨ ਲਈ, ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ।" - "ਤੁਸੀਂ \'ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰੋ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ" + "ਤੁਸੀਂ \'ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰੋ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ" "ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਸਵਾਈਪ ਕਰੋ" "ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰਨ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਤੇ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ ਅਤੇ ਫਿਰ ਛੱਡੋ।" - "ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰਨ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ 2 ਉਂਗਲਾਂ ਨਾਲ ਉੱਤੇ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ ਅਤੇ ਫਿਰ ਛੱਡੋ।" "ਐਪਾਂ ਸਵਿੱਚ ਕਰੋ" "ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ, ਅਤੇ ਫਿਰ ਛੱਡੋ" "ਬਹੁਤ ਵਧੀਆ!" @@ -90,7 +90,7 @@ "ਪੂਰੀ ਤਰ੍ਹਾਂ ਤਿਆਰ!" "ਹੋਮ \'ਤੇ ਜਾਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ" "ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਣ ਲਈ ਹੋਮ ਬਟਨ \'ਤੇ ਟੈਪ ਕਰੋ" - "ਤੁਸੀਂ ਆਪਣਾ %1$s ਵਰਤਣ ਲਈ ਤਿਆਰ ਹੋ" + "ਹੁਣ ਤੁਹਾਡਾ %1$s ਵਰਤੋਂ ਲਈ ਤਿਆਰ ਹੈ" "ਡੀਵਾਈਸ" "ਸਿਸਟਮ ਨੈਵੀਗੇਸ਼ਨ ਸੈਟਿੰਗਾਂ" "ਸਾਂਝਾ ਕਰੋ" @@ -99,7 +99,7 @@ "ਐਪ ਜੋੜਾਬੱਧ ਰੱਖਿਅਤ ਕਰੋ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨੂੰ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ \'ਤੇ ਟੈਪ ਕਰੋ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਚੁਣੋ" - "ਰੱਦ ਕਰੋ" + "ਰੱਦ ਕਰੋ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਚੋਣ ਤੋਂ ਬਾਹਰ ਜਾਓ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਚੁਣੋ" "ਐਪ ਜਾਂ ਤੁਹਾਡੀ ਸੰਸਥਾ ਵੱਲੋਂ ਇਸ ਕਾਰਵਾਈ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ" @@ -130,18 +130,37 @@ "ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ" "ਟਾਸਕਬਾਰ" "ਟਾਸਕਬਾਰ ਨੂੰ ਦਿਖਾਇਆ ਗਿਆ" - "ਟਾਸਕਬਾਰ ਨੂੰ ਲੁਕਾਇਆ ਗਿਆ" + "ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਖੱਬੇ ਦਿਖਾਇਆ" + "ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਸੱਜੇ ਦਿਖਾਇਆ" "ਨੈਵੀਗੇਸ਼ਨ ਵਾਲੀ ਪੱਟੀ" "ਹਮੇਸ਼ਾਂ ਟਾਸਕਬਾਰ ਦਿਖਾਓ" "ਨੈਵੀਗੇਸ਼ਨ ਮੋਡ ਬਦਲੋ" "ਟਾਸਕਬਾਰ ਵਿਭਾਜਕ" + "ਟਾਸਕਬਾਰ ਓਵਰਫ਼ਲੋ" "ਸਿਖਰਲੇ/ਖੱਬੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ" "ਹੇਠਾਂ/ਸੱਜੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ" - "{count,plural, =1{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}one{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}other{# ਹੋਰ ਐਪਾਂ ਦਿਖਾਓ।}}" - "{count,plural, =1{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}one{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}other{# ਡੈਸਕਟਾਪ ਐਪਾਂ ਦਿਖਾਓ।}}" + "ਐਪ ਨੂੰ ਬਬਲ ਵਜੋਂ ਖੋਲ੍ਹੋ" + "ਹਾਲੀਆ ਐਪਾਂ" + "ਹਾਲੀਆ ਐਪ ਸੂਚੀ" + "{count,plural, =1{ਹੋਰ ਐਪ}one{ਹੋਰ ਐਪ}other{ਹੋਰ ਐਪਾਂ}}" + "ਡੈਸਕਟਾਪ" "%1$s ਅਤੇ %2$s" + "%1$s, %3$d ਵਿੱਚੋਂ %2$d ਆਈਟਮ" + "ਖੱਬੇ ਪਾਸੇ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ" + "ਸੱਜੇ ਪਾਸੇ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ" "ਬਬਲ" "ਓਵਰਫ਼ਲੋ" "%2$s ਤੋਂ %1$s" "%1$s ਅਤੇ %2$d ਹੋਰ" + "ਖੱਬੇ ਲਿਜਾਓ" + "ਸੱਜੇ ਲਿਜਾਓ" + "ਸਭ ਖਾਰਜ ਕਰੋ" + "%1$s ਦਾ ਵਿਸਤਾਰ ਕਰੋ" + "%1$s ਨੂੰ ਸਮੇਟੋ" + "ਖੋਜਣ ਲਈ ਚੱਕਰ ਬਣਾਓ" + "ਐਪ ਪ੍ਰਤੀਕ" + "ਐਪ ਸਿਰਲੇਖ" + "\'ਬੰਦ ਕਰੋ\' ਬਟਨ" + "ਟਾਸਕਬਾਰ \'ਤੇ ਪਿੰਨ ਕਰੋ" + "ਟਾਸਕਬਾਰ ਤੋਂ ਅਣਪਿੰਨ ਕਰੋ" diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml index 13c8c1c48ba..14b60c49d73 100644 --- a/quickstep/res/values-pl/strings.xml +++ b/quickstep/res/values-pl/strings.xml @@ -22,11 +22,13 @@ "Przypnij" "Tryb dowolny" "Pulpit" + "Przenieś na wyświetlacz zewnętrzny" + "Zamknij" + "Pulpit" "Brak ostatnich elementów" "Ustawienia użycia aplikacji" "Wyczyść wszystko" "Ostatnie aplikacje" - "Zadanie zamknięte" "%1$s, %2$s" "> 1 min" "Na dziś zostało %1$s" @@ -45,6 +47,7 @@ "Włączono sugestie aplikacji" "Sugestie aplikacji są wyłączone" "Przewidywana aplikacja: %1$s" + "Samouczek dotyczący nawigacji przy użyciu gestów" "Obróć urządzenie" "Obróć urządzenie, aby ukończyć samouczek nawigacji przy użyciu gestów" "Pamiętaj, aby przesuwać palcem od samej krawędzi (prawej lub lewej)" @@ -56,7 +59,6 @@ "Czułość gestu cofania możesz zmienić w Ustawieniach" "Przesuń palcem, aby przejść wstecz" "Aby wrócić do poprzedniego ekranu, przesuń palcem od lewej lub prawej krawędzi do środka ekranu." - "Aby wrócić do poprzedniego ekranu, przesuń 2 palcami od lewej lub prawej krawędzi do środka ekranu." "Przejście wstecz" "Przesuń palcem od lewej lub prawej krawędzi do środka ekranu" "Pamiętaj, aby przesuwać palcem od dolnej krawędzi ekranu" @@ -66,7 +68,6 @@ "Gest przechodzenia na ekran główny został opanowany" "Przesuń palcem, aby przejść na ekran główny" "Przesuń palcem od dołu ekranu. Ten gest zawsze powoduje przejście na ekran główny." - "Przesuń 2 palcami od dołu ekranu. Ten gest zawsze powoduje przejście do ekranu głównego." "Przejście na ekran główny" "Przesuń palcem z dołu ekranu w górę" "Brawo!" @@ -77,8 +78,7 @@ "Gest przełączania aplikacji został opanowany" "Przesuń palcem, aby przełączać aplikacje" "Aby przełączać się między aplikacjami, przesuń palcem od dołu do góry ekranu, przytrzymaj i puść." - "Aby przełączać się między aplikacjami, przesuń 2 palcami od dołu ekranu, przytrzymaj i puść." - "Przełącz aplikację" + "Przełączanie aplikacji" "Przesuń palcem z dołu ekranu w górę, przytrzymaj i puść" "Brawo!" "Wszystko gotowe" @@ -99,7 +99,7 @@ "Zapisz parę" "Aby podzielić ekran, kliknij drugą aplikację" "Aby podzielić ekran, wybierz drugą aplikację" - "Anuluj" + "Anuluj" "Wyjdź z wyboru podzielonego ekranu" "Wybierz drugą aplikację, aby podzielić ekran" "Nie możesz wykonać tego działania, bo nie zezwala na to aplikacja lub Twoja organizacja" @@ -130,18 +130,37 @@ "Szybkie ustawienia" "Pasek aplikacji" "Pasek aplikacji widoczny" - "Pasek aplikacji ukryty" + "Pasek i dymki po lewej" + "Pasek i dymki po prawej" "Pasek nawigacyjny" "Zawsze pokazuj pasek aplikacji" "Zmień tryb nawigacji" "Linia dzielenia paska aplikacji" + "Rozwijany pasek aplikacji" "Przesuń w górny lewy róg" "Przesuń w dolny prawy róg" - "{count,plural, =1{Pokaż jeszcze # aplikację.}few{Pokaż jeszcze # aplikacje.}many{Pokaż jeszcze # aplikacji.}other{Pokaż jeszcze # aplikacji.}}" - "{count,plural, =1{Pokaż # aplikację komputerową.}few{Pokaż # aplikacje komputerowe.}many{Pokaż # aplikacji komputerowych.}other{Pokaż # aplikacji komputerowej.}}" + "Otwórz aplikację jako dymek" + "Ostatnie aplikacje" + "Lista ostatnich aplikacji" + "{count,plural, =1{inna aplikacja}few{inne aplikacje}many{innych aplikacji}other{innej aplikacji}}" + "Pulpit" "%1$s%2$s" + "%1$s, element %2$d%3$d" + "Przewiń w lewo" + "Przewiń w prawo" "Dymek" "Rozwijany" "%1$s z aplikacji %2$s" "%1$s i jeszcze %2$d" + "Przenieś w lewo" + "Przenieś w prawo" + "Zamknij wszystkie" + "rozwiń dymek: %1$s" + "zwiń dymek: %1$s" + "Zaznacz, aby wyszukać" + "Ikona aplikacji" + "Tytuł aplikacji" + "Przycisk Zamknij" + "Przypnij do paska" + "Odepnij od paska" diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml index e4d07bd97a2..2225496c18a 100644 --- a/quickstep/res/values-pt-rPT/strings.xml +++ b/quickstep/res/values-pt-rPT/strings.xml @@ -22,11 +22,13 @@ "Fixar" "Forma livre" "Computador" + "Mover para o ecrã externo" + "Fechar" + "Computador" "Nenhum item recente" "Definições de utilização de aplicações" "Limpar tudo" "Apps recentes" - "Tarefa fechada" "%1$s, %2$s" "< 1 minuto" "Resta(m) %1$s hoje." @@ -45,9 +47,10 @@ "Sugestões de apps ativadas" "As sugestões de apps estão desativadas" "App prevista: %1$s" + "Tutorial da navegação por gestos" "Rode o dispositivo" "Rode o seu dispositivo para concluir o tutorial de navegação por gestos" - "Deslize rapidamente a partir da extremidade mais à direita ou mais à esquerda" + "Deslize a partir da extremidade mais à direita ou mais à esquerda" "Deslize rapidamente a partir da extremidade esquerda ou direita até ao centro do ecrã e solte" "Aprendeu a deslizar a partir da direita para retroceder. A seguir, saiba como alternar entre apps." "Concluiu o gesto para retroceder. A seguir, saiba como alternar entre apps." @@ -56,30 +59,27 @@ "Altere a sensibilidade do gesto para voltar nas Definições." "Deslize rapidamente com o dedo para retroceder" "Para voltar ao último ecrã, deslize rapidamente do limite esquerdo ou direito até ao centro do ecrã." - "Para voltar ao último ecrã, deslize rapidamente com 2 dedos a partir da extremidade esquerda ou direita até ao centro do ecrã." "Retroceder" - "Deslize rapidamente a partir da extremidade esquerda ou direita para o meio do ecrã" - "Deslize rapidamente com o dedo a partir do limite inferior do ecrã" + "Deslize a partir da extremidade esquerda ou direita até ao centro do ecrã" + "Deslize a partir do limite inferior do ecrã" "Não faça uma pausa antes de soltar" "Deslize rapidamente com o dedo para cima" "Concluiu o gesto para aceder ao ecrã principal. A seguir, saiba como retroceder." "Concluiu o gesto para aceder ao ecrã principal" "Deslize rapidamente com o dedo para aceder ao ecrã principal" "Deslize rapidamente para cima a partir da parte inferior. Este gesto abre sempre o ecrã principal." - "Deslize rapidamente para cima com 2 dedos no fundo do ecrã. Este gesto abre sempre o ecrã principal." "Aceda ao ecrã principal" - "Deslize rapidamente para cima a partir da parte inferior do ecrã" + "Deslize para cima a partir da parte inferior do ecrã" "Muito bem!" - "Deslize rapidamente com o dedo a partir do limite inferior do ecrã" + "Deslize a partir do limite inferior do ecrã" "Experimente premir a janela durante mais tempo antes de soltar" - "Garanta que desliza rapidamente com o dedo para cima e, em seguida, faz uma pausa" + "Deslize para cima e pause" "Aprendeu a utilizar gestos. Para desativar os gestos, aceda às Definições." "Concluiu o gesto para alternar entre apps" "Deslize rapidamente com o dedo para alternar entre apps" "Para alternar entre apps, deslize para cima sem soltar a partir da parte inferior do ecrã e solte." - "Para mudar de app, deslize rapidamente para cima com 2 dedos sem soltar no fundo do ecrã e solte." "Mude de app" - "Deslize rapidamente para cima a partir da parte inferior do ecrã sem soltar e, em seguida, solte" + "Deslize para cima a partir da parte inferior do ecrã, detenha o gesto e solte" "Muito bem!" "Está tudo pronto" "Concluído" @@ -99,7 +99,7 @@ "Guardar par de apps" "Toque noutra app para usar o ecrã dividido" "Escolha outra app para usar o ecrã dividido" - "Cancelar" + "Cancelar" "Saia da seleção de ecrã dividido" "Escolher outra app para usar o ecrã dividido" "Esta ação não é permitida pela app ou a sua entidade." @@ -130,18 +130,37 @@ "Definiç. rápidas" "Barra de tarefas" "Barra de tarefas apresentada" - "Barra de tarefas ocultada" + "Barra de tarefas/balões à esq." + "Barra de tarefas/balões à dir." "Barra de navegação" "Ver sempre Barra de tarefas" "Alterar modo de navegação" "Divisor da Barra de tarefas" + "Menu adicional da Barra de tarefas" "Mover para a parte superior esquerda" "Mover para a part superior direita" - "{count,plural, =1{Mostrar mais # app.}other{Mostrar mais # apps.}}" - "{count,plural, =1{Mostrar # app para computador.}other{Mostrar # apps para computador.}}" + "Abrir app como um balão" + "Apps recentes" + "Lista de apps recentes" + "{count,plural, =1{outra app}other{outras apps}}" + "Computador" "%1$s e %2$s" + "%1$s, item %2$d de %3$d" + "Deslocar para a esquerda" + "Deslocar para a direita" "Balão" "Menu adicional" "%1$s da app %2$s" "%1$s e mais %2$d pessoas" + "Mover para a esquerda" + "Mover para a direita" + "Ignorar tudo" + "expandir %1$s" + "reduzir %1$s" + "Circundar para Pesquisar" + "Ícone da app" + "Título da app" + "Botão Fechar" + "Afixar na barra tar." + "Desaf. da barra tar." diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml index 4fec4f8511c..e4c3cf1996f 100644 --- a/quickstep/res/values-pt/strings.xml +++ b/quickstep/res/values-pt/strings.xml @@ -21,12 +21,14 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fixar" "Forma livre" - "Computador" + "Modo área de trabalho" + "Mover para a tela externa" + "Fechar" + "Computador" "Nenhum item recente" "Configurações de uso do app" "Remover tudo" "Apps recentes" - "Tarefa encerrada" "%1$s, %2$s" "< 1 min" "%1$s restante(s) hoje" @@ -45,6 +47,7 @@ "O recurso \"sugestões de apps\" está ativado" "O recurso \"sugestões de apps\" está desativado" "App previsto: %1$s" + "Tutorial da navegação por gestos" "Gire o dispositivo" "Gire o dispositivo para concluir o tutorial da navegação por gestos" "Deslize da borda direita ou esquerda" @@ -56,7 +59,6 @@ "Mude a sensibilidade do gesto de voltar nas configurações" "Deslize para voltar" "Para voltar à tela anterior, deslize da borda esquerda ou direita até o meio da tela." - "Para voltar à tela anterior, deslize da borda esquerda ou direita até o meio da tela com dois dedos." "Volte" "Deslize da borda esquerda ou direita até o meio da tela" "Deslize da borda inferior da tela para cima" @@ -66,18 +68,16 @@ "Você concluiu o gesto para acessar a tela inicial" "Deslizar para voltar à tela inicial" "Deslize de baixo para cima na tela. Esse gesto sempre leva você para a tela inicial." - "Deslize de baixo para cima na tela com dois dedos. Esse gesto sempre leva você para a tela inicial." "Vá para a página inicial" "Deslize de baixo para cima na tela" "Muito bem!" "Deslize da borda inferior da tela para cima" "Mantenha a janela pressionada por mais tempo antes de soltar" - "Deslize para cima e pare" + "Deslize para cima em linha reta e pare" "Você aprendeu. Para desativar os gestos, acesse as Configurações." "Você concluiu o gesto para mudar de app" "Deslizar para trocar de app" "Para mudar de app, deslize de baixo para cima, mantenha a tela pressionada por um tempo e solte." - "Para mudar de app, deslize de baixo para cima na tela com dois dedos, segure por um tempo e solte." "Mude de app" "Deslize de baixo para cima na tela, segure e depois solte" "Muito bem!" @@ -89,8 +89,8 @@ "Tutorial %1$d/%2$d" "Tudo pronto!" "Deslize para cima para acessar a tela inicial" - "Toque no botão home para ir para a tela inicial" - "Você já pode começar a usar seu %1$s" + "Toque no botão home para acessar a tela inicial" + "O %1$s já pode ser usado" "dispositivo" "Configurações de navegação do sistema" "Compartilhar" @@ -99,7 +99,7 @@ "Salvar par de apps" "Toque em outro app para usar a tela dividida" "Escolha outro app para usar na tela dividida" - "Cancelar" + "Cancelar" "Sair da seleção de tela dividida" "Escolha outro app para usar na tela dividida" "Essa ação não é permitida pelo app ou pela organização" @@ -118,7 +118,7 @@ "Sempre mostrar a Barra de tarefas" "Toque e pressione o divisor para sempre mostrar a Barra de tarefas na parte de baixo da tela" "Toque na tecla de ação e pressione para pesquisar o que está na tela" - "O produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <a href="%1$s">Política de Privacidade</a> e aos <a href="%2$s">Termos de Serviço</a> do Google." + "Este produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <a href="%1$s">Política de Privacidade</a> e aos <a href="%2$s">Termos de Serviço</a> do Google." "Fechar" "Concluído" "Início" @@ -130,18 +130,37 @@ "Config. rápidas" "Barra de tarefas" "Barra de tarefas visível" - "Barra de tarefas oculta" + "Barra de tar. e balões à esq." + "Barra de tar. e balões à dir." "Barra de navegação" "Sempre mostrar a Barra de tarefas" "Mudar o modo de navegação" "Separador da Barra de tarefas" + "Barra de tarefas flutuante" "Mover para cima/para a esquerda" "Mover para baixo/para a direita" - "{count,plural, =1{Mostrar mais # app.}one{Mostrar mais # app.}other{Mostrar mais # apps.}}" - "{count,plural, =1{Mostrar # app para computador.}one{Mostrar # app para computador.}other{Mostrar # apps para computador.}}" + "Abrir o app como um balão" + "Apps recentes" + "Lista de apps recentes" + "{count,plural, =1{outro app}one{outro app}other{outros apps}}" + "Computador" "%1$s e %2$s" + "%1$s, item %2$d de %3$d" + "Rolar para a esquerda" + "Rolar para a direita" "Balão" "Balão flutuante" "%1$s do app %2$s" "%1$s e mais %2$d" + "Mover para esquerda" + "Mover para direita" + "Dispensar todos" + "abrir %1$s" + "fechar %1$s" + "Circule para pesquisar" + "Ícone do app" + "Título do app" + "Botão \"Fechar\"" + "Fixar na barra de tarefas" + "Liberar da barra de tarefas" diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml index c839602e897..27f80fbb5c5 100644 --- a/quickstep/res/values-ro/strings.xml +++ b/quickstep/res/values-ro/strings.xml @@ -21,12 +21,14 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fixează" "Formă liberă" - "Computer" + "Desktop" + "Mută pe ecranul extern" + "Închide" + "Computer" "Niciun element recent" "Setări de utilizare a aplicației" "Șterge tot" "Aplicații recente" - "Activitatea s-a încheiat" "%1$s, %2$s" "< 1 minut" "Au mai rămas %1$s astăzi" @@ -45,6 +47,7 @@ "Sugestiile de aplicații au fost activate" "Sugestiile de aplicații au fost dezactivate" "Aplicația estimată: %1$s" + "Tutorial de navigare prin gesturi" "Rotește dispozitivul" "Rotește dispozitivul pentru a încheia tutorialul de navigare prin gesturi" "Glisează dinspre marginea dreaptă îndepărtată sau dinspre marginea stângă îndepărtată" @@ -56,7 +59,6 @@ "Schimbă sensibilitatea gestului „Înapoi” accesând Setările" "Glisează pentru a reveni" "Pentru a reveni la ultimul ecran, glisează de la marginea stângă sau dreaptă spre mijlocul ecranului." - "Pentru a reveni la ultimul ecran, glisează cu două degete dinspre marginea stângă sau dreaptă spre mijlocul ecranului." "Înapoi" "Glisează dinspre marginea stângă sau dreaptă până la jumătatea ecranului" "Glisează în sus dinspre marginea de jos a ecranului" @@ -66,7 +68,6 @@ "Ai finalizat gestul „accesează ecranul de pornire”" "Glisează pentru a accesa ecranul de pornire" "Glisează în sus din partea de jos a ecranului. Cu acest gest accesezi mereu ecranul de pornire." - "Glisează în sus cu două degete din partea de jos. Cu acest gest accesezi mereu ecranul de pornire." "Înapoi la ecranul de pornire" "Glisează în sus din partea de jos a ecranului" "Excelent!" @@ -77,7 +78,6 @@ "Ai finalizat gestul „comută între aplicații”" "Glisează pentru a comuta între aplicații" "Ca să comuți între aplicații, glisează de jos în sus, ține degetul pe ecran, apoi ridică-l." - "Ca să comuți între aplicații, glisează cu 2 degete de jos în sus, ține-le pe ecran, apoi ridică-le." "Comută între aplicații" "Glisează în sus din partea de jos a ecranului, ține apăsat, apoi eliberează" "Felicitări!" @@ -99,7 +99,7 @@ "Salvează perechea de aplicații" "Atinge altă aplicație pentru ecranul împărțit" "Alege altă aplicație pentru a folosi ecranul împărțit" - "Anulează" + "Anulează" "Ieși din selecția cu ecran împărțit" "Alege altă aplicație pentru ecranul împărțit" "Această acțiune nu este permisă de aplicație sau de organizația ta" @@ -130,18 +130,37 @@ "Setări rapide" "Bară de activități" "Bara de activități este afișată" - "Bara de activități este ascunsă" + "Bară și baloane stânga afișate" + "Bară & baloane dreapta afișate" "Bară de navigare" "Afișează mereu bara" "Schimbă modul de navigare" "Separator pentru bara de activități" + "Meniu suplimentar pentru bara de activități" "Mută în stânga sus" "Mută în dreapta jos" - "{count,plural, =1{Afișează încă # aplicație}few{Afișează încă # aplicații}other{Afișează încă # de aplicații}}" - "{count,plural, =1{Afișează # aplicație pentru computer.}few{Afișează # aplicații pentru computer.}other{Afișează # de aplicații pentru computer.}}" + "Deschide aplicația ca balon" + "Aplicații recente" + "Lista de aplicații recente" + "{count,plural, =1{aplicație suplimentară}few{mai multe aplicații}other{mai multe aplicații}}" + "Computer" "%1$s și %2$s" + "%1$s, articolul %2$d din %3$d" + "Derulează la stânga" + "Derulează la dreapta" "Balon" "Suplimentar" "%1$s de la %2$s" "%1$s și încă %2$d" + "Deplasează spre stânga" + "Deplasează spre dreapta" + "Închide-le pe toate" + "extinde %1$s" + "restrânge %1$s" + "Încercuiește și caută" + "Pictograma aplicației" + "Titlul aplicației" + "Buton de închidere" + "Fixează pe bara de activități" + "Anulează fixarea din bara de activități" diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml index da49ad32b87..605c5654ef0 100644 --- a/quickstep/res/values-ru/strings.xml +++ b/quickstep/res/values-ru/strings.xml @@ -21,12 +21,14 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Закрепить" "Произвольная форма" - "Включить режим для ПК" + "Мультиоконный режим" + "Перенести на внешний дисплей" + "Закрыть" + "Мультиоконный режим" "Здесь пока ничего нет." "Настройки использования приложения" "Очистить все" "Недавние приложения" - "Задача закрыта" "%1$s: %2$s" "< 1 мин." "Осталось сегодня: %1$s" @@ -45,6 +47,7 @@ "Функция \"Рекомендуемые приложения\" включена." "Функция \"Рекомендуемые приложения\" отключена." "Рекомендуемое приложение: %1$s" + "Руководство: навигация с помощью жестов" "Поверните устройство" "Чтобы перейти к руководству по жестам, нужно повернуть устройство." "Проведите справа налево или слева направо от самого края экрана." @@ -56,7 +59,6 @@ "Уровень чувствительности можно изменить в настройках." "Возврат к предыдущему экрану" "Чтобы вернуться к предыдущему экрану, проведите от левого или правого края дисплея к центру." - "Чтобы вернуться на предыдущий экран, проведите двумя пальцами от левого или правого края экрана к центру." "Как вернуться к предыдущему экрану" "Проведите от левого или правого края экрана к центру." "Проведите снизу вверх от самого края экрана." @@ -66,18 +68,16 @@ "Вы выполнили жест для перехода на главный экран." "Переход на главный экран" "Проведите вверх от нижнего края дисплея. Этот жест открывает главный экран." - "Проведите двумя пальцами вверх от нижнего края экрана. Этот жест открывает главный экран." "Как перейти на главный экран" "Проведите вверх от нижнего края экрана." "У вас получилось!" "Проведите снизу вверх от самого края экрана." "Прежде чем отпустить палец, задержите его на экране немного дольше." - "Проведите по экрану ровно вверх и задержите палец в конце." + "Проведите по экрану вверх и задержите палец." "Теперь вы знаете, как использовать жесты. Чтобы отключить их, перейдите в настройки." "Вы выполнили жест для переключения между приложениями." "Переключение между приложениями" "Чтобы переключиться между приложениями‚ проведите по экрану снизу вверх, задержите палец, а затем отпустите." - "Чтобы сменить приложение, проведите двумя пальцами снизу вверх, задержите пальцы, а затем отпустите." "Как переключаться между приложениями" "Проведите вверх от нижнего края экрана, задержите палец в одной точке и отпустите." "Отлично!" @@ -92,14 +92,14 @@ "Нажмите кнопку главного экрана, чтобы открыть его." "Теперь вы можете использовать %1$s." "устройство" - "Системные настройки навигации" + "Настройки навигации в системе" "Поделиться" "Скриншот" "Разделить" "Сохранить приложения" "Для разделения экрана выберите другое приложение." "Чтобы использовать разделенный экран, выберите другое приложение." - "Отмена" + "Отмена" "Выйдите из режима разделения экрана." "Выберите другое приложение для разделения экрана." "Это действие заблокировано приложением или организацией." @@ -130,18 +130,37 @@ "Быстрые настройки" "Панель задач" "Панель задач показана" - "Панель задач скрыта" + "Слева панель задач, подсказки" + "Справа панель задач, подсказки" "Панель навигации" "Всегда показывать панель задач" "Изменить режим навигации" "Разделитель панели задач" + "Дополнительное меню панели задач" "Переместить вверх или влево" "Переместить вниз или вправо" - "{count,plural, =1{Показать ещё # приложение}one{Показать ещё # приложение}few{Показать ещё # приложения}many{Показать ещё # приложений}other{Показать ещё # приложения}}" - "{count,plural, =1{Показать # компьютерное приложение.}one{Показать # компьютерное приложение.}few{Показать # компьютерных приложения.}many{Показать # компьютерных приложений.}other{Показать # компьютерного приложения.}}" + "Открыть приложение во всплывающем окне" + "Недавние приложения" + "Список недавних приложений" + "{count,plural, =1{дополнительное приложение}one{дополнительное приложение}few{дополнительных приложения}many{дополнительных приложений}other{дополнительного приложения}}" + "Режим компьютера" "%1$s и %2$s" + "%1$s, элемент %2$d из %3$d" + "Прокрутить влево" + "Прокрутить вправо" "Всплывающая подсказка" "Дополнительное меню" "\"%1$s\" из приложения \"%2$s\"" "%1$s и ещё %2$d" + "Переместить влево" + "Переместить вправо" + "Закрыть все" + "Развернуто: %1$s" + "Свернуто: %1$s" + "Обвести и найти" + "Значок приложения" + "Название приложения" + "Кнопка \"Закрыть\"" + "Закрепить на панели" + "Открепить от панели" diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml index 9cbe837f940..2030252b292 100644 --- a/quickstep/res/values-si/strings.xml +++ b/quickstep/res/values-si/strings.xml @@ -22,11 +22,13 @@ "අමුණන්න" "Freeform" "ඩෙස්ක්ටොපය" + "බාහිර සංදර්ශකය වෙත ගෙන යන්න" + "වසන්න" + "ඩෙස්ක්ටොපය" "මෑත අයිතම නැත" "යෙදුම් භාවිත සැකසීම්" "සියල්ල හිස් කරන්න" "මෑත යෙදුම්" - "කාර්යය අවසන් කරන ලදි" "%1$s, %2$s" "< 1 විනාඩියක්" "අද %1$sක් ඉතුරුයි" @@ -45,6 +47,7 @@ "යෙදුම් යෝජනා සබලිතයි" "යෙදුම් යෝජනා අබල කර ඇත" "පුරෝකථනය කළ යෙදුම: %1$s" + "අභින සංචාලන නිබන්ධනය" "ඔබේ උපාංගය කරකවන්න" "අභින සංචාලන නිබන්ධනය සම්පූර්ණ කිරීම සඳහා ඔබේ උපාංගය කරකවන්න" "ඔබ ඈත දකුණු හෝ ඈත වම් දාරයේ සිට ස්වයිප් කරන බව සහතික කර ගන්න" @@ -56,7 +59,6 @@ "ආපසු ඉංගිතයෙහි සංවේදීතාව වෙනස් කිරීමට, සැකසීම් වෙත යන්න" "ආපසු යාමට ස්වයිප් කරන්න" "අවසාන තිරයට ආපසු යාමට, වම් හෝ දකුණු දාරයෙන් තිරයේ මැදට ස්වයිප් කරන්න." - "අවසාන තිරයට ආපසු යාමට, වම් හෝ දකුණු දාරයෙන් තිරයේ මැදට ඇඟිලි 2කින් ස්වයිප් කරන්න." "ආපසු යන්න" "වම් හෝ දකුණු කෙළවරේ සිට තිරයේ මැදට ස්වයිප් කරන්න" "ඔබ තිරයේ පහළ දාරයේ සිට ඉහළට ස්වයිප් කරන බව සහතික කර ගන්න" @@ -66,7 +68,6 @@ "ඔබ මුල් පිටුවට යාමේ ඉංගිතය සම්පූර්ණ කළා" "මුල් පිටුවට යාමට ස්වයිප් කරන්න" "ඔබගේ තිරයේ පහළින් උඩට ස්වයිප් කරන්න.මෙම ඉංගිතය සැම විටම ඔබව මුල් තිරයට ගෙන යයි." - "තිරයේ පහළම සිට ඇඟිලි 2කින් ඉහළට ස්වයිප් කරන්න. මෙම ඉංගිතය සැම විටම ඔබව මුල් තිරයට ගෙන යයි." "මුල් පිටුවට යන්න" "ඔබේ තිරයේ පහළ සිට උඩට ස්වයිප් කරන්න" "අනර්ඝ වැඩක්!" @@ -77,7 +78,6 @@ "ඔබ යෙදුම් මාරු කිරීමේ ඉංගිතය සම්පූර්ණ කළා" "යෙදුම් මාරු කිරීමට ස්වයිප් කරන්න" "යෙදුම් අතර මාරු වීමට, ඔබගේ තිරයේ පහළම සිට උඩට ස්වයිප් කර, අල්ලාගෙන සිට, අනතුරුව මුදා හරින්න." - "යෙදුම් අතර මාරු වීමට, ඔබගේ තිරයේ පහළම සිට උඩට ඇඟිලි 2කින් ස්වයිප් කර, අල්ලාගෙන සිට, අනතුරුව මුදා හරින්න." "යෙදුම් මාරු කරන්න" "ඔබේ තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න, රඳවා ගෙන සිට, පසුව මුදා හරින්න" "හොඳින් කළා!" @@ -99,7 +99,7 @@ "යෙදුම් යුගල සුරකින්න" "බෙදුම් තිරය භාවිතා කිරීමට තවත් යෙදුමක් තට්ටු කරන්න" "බෙදුම් තිරය භාවිත කිරීමට වෙනත් යෙදුමක් තෝරා ගන්න" - "අවලංගු කරන්න" + "අවලංගු කරන්න" "බෙදීම් තිර තේරීමෙන් පිටවන්න" "බෙදීම් තිරය භාවිතා කිරීමට වෙනත් යෙදුමක් තෝරා ගන්න" "මෙම ක්‍රියාව යෙදුම හෝ ඔබේ සංවිධානය මගින් ඉඩ නොදේ" @@ -130,18 +130,37 @@ "ඉක්මන් සැකසීම්" "කාර්ය තීරුව" "කාර්ය තීරුව පෙන්වා ඇත" - "කාර්ය තීරුව සඟවා ඇත" + "කාර්ය තීරුව සහ බුබුළු පෙන්වා ඇත" + "කාර්ය තීරුව සහ බුබුළු දකුණට පෙන්වා ඇත" "සංචලන තීරුව" "සෑම විටම කාර්ය තීරුව පෙන්වන්න" "සංචාලන ප්‍රකාරය වෙනස් කරන්න" "කාර්ය තීරු බෙදනය" + "කාර්ය තීරුව පිටාර යාම" "ඉහළ/වම වෙත ගෙන යන්න" "පහළ/දකුණ වෙත ගෙන යන්න" - "{count,plural, =1{තවත් # යෙදුමක් පෙන්වන්න.}one{තවත් යෙදුම් #ක් පෙන්වන්න.}other{තවත් යෙදුම් #ක් පෙන්වන්න.}}" - "{count,plural, =1{# ඩෙස්ක්ටොප් යෙදුමක් පෙන්වන්න.}one{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}other{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}}" + "යෙදුම බුබුලක් ලෙස විවෘත කරන්න" + "මෑත යෙදුම්" + "මෑත යෙදුම් ලැයිස්තුව" + "{count,plural, =1{තව යෙදුම}one{තවත් යෙදුම්}other{තවත් යෙදුම්}}" + "ඩෙස්ක්ටොපය" "%1$s සහ %2$s" + "%1$s, අයිතම %3$dන් %2$d" + "වමට අනුචලනය කරන්න" + "දකුණට අනුචලනය කරන්න" "බුබුළු" "පිටාර යාම" "%2$s සිට %1$s" "%1$s හා තව %2$dක්" + "වමට ගෙන යන්න" + "දකුණට ගෙන යන්න" + "සියල්ල ඉවතලන්න" + "%1$s දිග හරින්න" + "%1$s හකුළන්න" + "සෙවීමට කවයසෙවීමට කවය අදින්න" + "යෙදුම් නිරූපකය" + "යෙදුම් මාතෘකාව" + "වැසීමේ බොත්තම" + "කාර්ය තීරුවට අමුණන්න" + "කාර්ය තීරුවෙන් ඉවත් කරන්න" diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml index 3eca787fedd..7e071167a36 100644 --- a/quickstep/res/values-sk/strings.xml +++ b/quickstep/res/values-sk/strings.xml @@ -22,11 +22,13 @@ "Pripnúť" "Voľný režim" "Počítač" + "Presunúť na externú obrazovku" + "Zavrieť" + "Počítač" "Žiadne nedávne položky" "Nastavenia využívania aplikácie" "Vymazať všetko" "Nedávne aplikácie" - "Úloha bola zavretá" "%1$s, %2$s" "Menej ako 1 minúta" "Dnes ešte zostáva: %1$s" @@ -45,41 +47,39 @@ "Návrhy aplikácií zapnuté" "Návrhy aplikácií vypnuté" "Predpovedaná aplikácia: %1$s" + "Návod na navigáciu gestami" "Otočte zariadenie" "Otočte zariadenie a dokončite tak návod, ako navigovať gestami" - "Musíte potiahnuť úplne z pravého alebo ľavého okraja" + "Musíte potiahnuť úplne z pravého alebo ľavého okraja." "Musíte potiahnuť z pravého alebo ľavého okraja do stredu obrazovky a potom uvoľniť" "Naučili ste sa prejsť späť potiahnutím sprava. V ďalšom kroku sa naučíte prepínať aplikácie." "Dokončili ste gesto na prechod späť. V ďalšom kroku sa naučíte, ako prepínať aplikácie." - "Dokončili ste gesto na prechod späť" + "Použili ste gesto na prechod späť." "Nesmiete potiahnuť príliš blízko dolnej časti obrazovky" "Ak chcete zmeniť citlivosť gesta Späť, prejdite do Nastavení" "Prechod späť potiahnutím" "Na poslednú obrazovku prejdete potiahnutím z ľavého alebo pravého okraja do stredu obrazovky." - "Na poslednú obrazovku sa vrátite potiahnutím dvoma prstami z ľavého alebo pravého okraja do stredu obrazovky." "Prechod späť" - "Potiahnite z ľavého alebo pravého okraja do stredu obrazovky" - "Musíte potiahnuť nahor z dolného okraja obrazovky" + "Potiahnite z ľavého alebo pravého okraja do stredu obrazovky." + "Musíte potiahnuť nahor z dolného okraja obrazovky." "Pred uvoľnením nesmiete zastať" "Musíte potiahnuť priamo nahor" "Dokončili ste gesto prechodu na plochu. Teraz sa naučíte, ako sa vrátiť späť." "Dokončili ste gesto prechodu na plochu" "Prechod na plochu potiahnutím" "Potiahnite nahor zdola obrazovky. Týmto gestom sa vždy vrátite na plochu." - "Postiahnite dvoma prstami z dolnej časti obrazovky. Týmto gestom sa vždy vrátite na plochu." "Prechod na plochu" - "Potiahnite z dolnej časti obrazovky nahor" + "Potiahnite z dolnej časti obrazovky nahor." "Skvelé!" "Musíte potiahnuť nahor z dolného okraja obrazovky" "Skúste okno pred uvoľnením podržať dlhšie" - "Musite potiahnuť priamo nahor a potom zastať" - "Naučili ste sa používať gestá. Gestá môžete vypnúť v nastaveniach." - "Dokončili ste gesto na prepnutie aplikácií" + "Musite potiahnuť priamo nahor a potom zastať." + "Naučili ste sa používať gestá. Gestá môžete vypnúť v Nastaveniach." + "Použili ste gesto na prepnutie aplikácií." "Prepínanie aplikácií potiahnutím" "Aplikácie môžete prepínať potiahnutím obrazovky zdola nahor, pridržaním a následným uvoľnením." - "Aplikácie prepnete potiahnutím dvoma prstami z dolnej časti obrazovky, ich pridržaním a uvoľnením." "Prepnutie aplikácií" - "Potiahnite nahor z dolného okraja obrazovky, pridržte a uvoľnite" + "Potiahnite nahor z dolného okraja obrazovky, pridržte a uvoľnite." "Výborne" "Hotovo" "Hotovo" @@ -90,7 +90,7 @@ "Hotovo" "Potiahnutím nahor prejdete na plochu" "Na plochu prejdete klepnutím na tlačidlo plochy" - "%1$s môžete začať používať" + "Môžete %1$s začať používať" "zariadenie" "Nastavenia navigácie systémom" "Zdieľať" @@ -99,7 +99,7 @@ "Uložiť pár aplikácií" "Obrazovku rozdelíte klepnutím na inú aplikáciu" "Na použitie rozdelenej obrazovky vyberte ďalšiu aplikáciu" - "Zrušiť" + "Zrušiť" "Ukončite výber rozdelenej obrazovky" "Na použitie rozd. obrazovky vyberte inú aplikáciu" "Aplikácia alebo vaša organizácia túto akciu nepovoľuje" @@ -130,18 +130,37 @@ "Rýchle nastavenia" "Panel aplikácií" "Panel aplikácií je zobrazený" - "Panel aplikácií je skrytý" + "Panel aplik. a bubl. sú vľavo" + "Panel aplik. a bubl. sú vpravo" "Navigačný panel" "Zobrazovať panel aplikácií" "Zmeniť režim navigácie" "Rozdeľovač panela aplikácií" + "Rozšírená ponuka panela aplikácií" "Presunúť hore alebo doľava" "Presunúť dole alebo doprava" - "{count,plural, =1{Zobraziť # ďalšiu aplikáciu.}few{Zobraziť # ďalšie aplikácie.}many{Show # more apps.}other{Zobraziť # ďalších aplikácií.}}" - "{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č.}}" + "Otvoriť aplikáciu ako bublinu" + "Nedávne aplikácie" + "Zoznam nedávnych aplikácií" + "{count,plural, =1{ďalšia aplikácia}few{ďalšie aplikácie}many{ďalšie aplikácie}other{ďalšie aplikácie}}" + "Počítač" "%1$s%2$s" + "%1$s, %2$d. položka z %3$d" + "Posunúť doľava" + "Posunúť doprava" "Bublina" "Rozbaľovacia ponuka" "%1$s z aplikácie %2$s" "%1$s a ešte %2$d" + "Posunúť doľava" + "Posunúť doprava" + "Zavrieť všetko" + "rozbaliť %1$s" + "zbaliť %1$s" + "Vyhľadávanie krúžením" + "Ikona aplikácie" + "Názov aplikácie" + "Tlačidlo Zavrieť" + "Pripnúť na panel" + "Odopnúť z panela" diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml index 52faeb7a9e8..fd7dc69d445 100644 --- a/quickstep/res/values-sl/strings.xml +++ b/quickstep/res/values-sl/strings.xml @@ -22,11 +22,13 @@ "Pripni" "Prosta oblika" "Namizni računalnik" + "Premik v zunanji zaslon" + "Zapri" + "Namizni način" "Ni nedavnih elementov" "Nastavitve uporabe aplikacij" "Počisti vse" "Nedavne aplikacije" - "Opravilo je zaprto" "%1$s, %2$s" "< 1 min" "Danes je ostalo še %1$s" @@ -45,6 +47,7 @@ "Predlogi aplikacij so omogočeni." "Predlogi aplikacij so onemogočeni." "Predvidena aplikacija: %1$s" + "Vadnica za krmarjenje s potezami" "Zasukajte napravo" "Zasukajte napravo, če si želite ogledati vadnico za krmarjenje s potezami" "Pazite, da povlečete s skrajno desnega ali skrajno levega roba." @@ -56,7 +59,6 @@ "Občutljivost poteze za nazaj lahko spremenite v nastavitvah." "Povlecite za vrnitev" "Če se želite vrniti na prejšnji zaslon, povlecite z levega ali desnega roba do sredine zaslona." - "Če se želite vrniti na zadnji prikazani zaslon, z dvema prstoma povlecite z levega ali desnega roba do sredine zaslona." "Pomik nazaj" "Povlecite z levega ali desnega roba do sredine zaslona." "Pazite, da povlečete s spodnjega roba zaslona navzgor." @@ -66,7 +68,6 @@ "Izvedli ste potezo za pomik na začetni zaslon." "Povlecite za pomik na začetni zaslon" "Z dna zaslona s prstom povlecite navzgor. S to potezo lahko vedno odprete začetni zaslon." - "Z dvema prstoma povlecite navzgor z dna zaslona. S to potezo lahko vedno odprete začetni zaslon." "Pomik na začetni zaslon" "Z dna zaslona s prstom povlecite navzgor." "Odlično!" @@ -77,7 +78,6 @@ "Izvedli ste potezo za preklapljanje med aplikacijami." "Povlecite za preklapljanje med aplikacijami" "Za preklapljanje med aplikacijami povlecite navzgor z dna zaslona, pridržite in nato izpustite." - "Za preklop med aplikacijami z dvema prstoma povlecite navzgor z dna zaslona, pridržite in spustite." "Preklop aplikacij" "Povlecite navzgor z dna zaslona, pridržite, nato izpustite." "Odlično!" @@ -99,7 +99,7 @@ "Shrani par aplikacij" "Za razdeljeni zaslon se dotaknite še 1 aplikacije" "Izberite drugo aplikacijo za uporabo razdeljenega zaslona." - "Prekliči" + "Prekliči" "Zapri izbiro razdeljenega zaslona" "Izberite drugo aplikacijo za uporabo razdeljenega zaslona." "Aplikacija ali vaša organizacija ne dovoljuje tega dejanja" @@ -130,18 +130,37 @@ "Hitre nastavitve" "Opravilna vrstica" "Opravilna vrstica je prikazana" - "Opravilna vrstica je skrita" + "Prikazani so opravilna vrstica in oblački na levi" + "Prikazani so opravilna vrstica in oblački na desni" "Vrstica za krmarjenje" "Stalen prikaz oprav. vrstice" "Spreminjanje načina navigacije" "Razdelilnik opravilne vrstice" + "Oblaček opravilne vrstice z dodatnimi elementi" "Premakni na vrh/levo" "Premakni na dno/desno" - "{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.}}" - "{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.}}" + "Odpri aplikacijo kot oblaček" + "Nedavne aplikacije" + "Seznam nedavnih aplikacij" + "{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}two{dodatni aplikaciji}few{dodatne aplikacije}other{dodatnih aplikacij}}" + "Namizni računalnik" "%1$s in %2$s" + "%1$s, element %2$d od %3$d" + "Pomik levo" + "Pomik desno" "Oblaček" "Oblaček z dodatnimi elementi" "%1$s iz aplikacije %2$s" "%1$s in še %2$d" + "Premik v levo" + "Premik v desno" + "Opusti vse" + "razširitev oblačka %1$s" + "strnitev oblačka %1$s" + "Iskanje z obkroževanjem" + "Ikona aplikacije" + "Ime aplikacije" + "Gumb za zapiranje" + "Pripni v opravilno vrstico" + "Odpni iz opravilne vrstice" diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml index cdb9cf9a7ce..3ebff3a74db 100644 --- a/quickstep/res/values-sq/strings.xml +++ b/quickstep/res/values-sq/strings.xml @@ -22,11 +22,13 @@ "Gozhdo" "Formë e lirë" "Desktopi" + "Zhvendose tek ekrani i jashtëm" + "Mbyll" + "Desktopi" "Nuk ka asnjë artikull të fundit" "Cilësimet e përdorimit të aplikacionit" "Pastroji të gjitha" "Aplikacionet e fundit" - "Detyra u mbyll" "%1$s, %2$s" "< 1 minutë" "%1$s të mbetura sot" @@ -45,6 +47,7 @@ "Aplikacionet e sugjeruara janë aktivizuar" "Sugjerimet e aplikacioneve janë çaktivizuar" "Aplikacioni i parashikuar: %1$s" + "Udhëzuesi për navigimin me gjeste" "Rrotullo pajisjen" "Rrotullo pajisjen për të përfunduar udhëzuesin e navigimit me gjeste" "Sigurohu që të rrëshqasësh shpejt nga skaji më i djathtë ose më i majtë" @@ -56,7 +59,6 @@ "Për të ndryshuar ndjeshmërinë e gjestit të kthimit prapa, shko te \"Cilësimet\"" "Rrëshqit shpejt për t\'u kthyer prapa" "Për t\'u kthyer prapa tek ekrani i fundit, rrëshqit shpejt nga skaji i majtë ose i djathtë drejt mesit të ekranit" - "Për t\'u kthyer prapa tek ekrani i fundit, rrëshqit shpejt me 2 gishta nga skaji i majtë ose i djathtë drejt mesit të ekranit." "Kthehu prapa" "Rrëshqit shpejt nga skaji i majtë ose i djathtë drejt mesit të ekranit" "Sigurohu që të rrëshqasësh shpejt lart nga skaji i poshtëm i ekranit" @@ -66,7 +68,6 @@ "E ke përfunduar gjestin e kalimit tek ekrani bazë" "Rrëshqit shpejt për të kaluar tek ekrani bazë" "Rrëshqit shpejt lart nga fundi i ekranit tënd. Ky gjest të dërgon gjithmonë tek ekrani bazë." - "Rrëshqit shpejt lart me 2 gishta nga fundi i ekranit. Ky gjest të dërgon gjithmonë tek ekrani bazë." "Shko tek ekrani bazë" "Rrëshqit shpejt lart nga pjesa e poshtme e ekranit" "Punë e shkëlqyer!" @@ -77,7 +78,6 @@ "E ke përfunduar gjestin e ndërrimit të aplikacioneve" "Rrëshqit shpejt për të ndërruar aplikacionet" "Për të ndërruar mes aplikacioneve, rrëshqit shpejt lart nga fundi i ekranit tënd, mbaj dhe pastaj lësho." - "Për të ndërruar mes aplikacioneve, rrëshqit lart me 2 gishta nga fundi i ekranit, mbaje dhe lëshoje." "Ndërro aplikacionet" "Rrëshqit shpejt lart nga fundi i ekranit, mbaje të shtypur dhe më pas lëshoje" "Shumë mirë!" @@ -99,7 +99,7 @@ "Ruaj çiftin e aplikacioneve" "Trokit një apl. tjetër; përdor ekranin e ndarë" "Zgjidh një aplikacion tjetër për të përdorur ekranin e ndarë" - "Anulo" + "Anulo" "Dil nga zgjedhja e ekranit të ndarë" "Zgjidh një aplikacion tjetër për të përdorur ekranin e ndarë" "Ky veprim nuk lejohet nga aplikacioni ose organizata jote" @@ -130,18 +130,37 @@ "Cilësimet shpejt" "Shiriti i detyrave" "Shiriti i detyrave i shfaqur" - "Shiriti i detyrave i fshehur" + "Shiriti i detyrave dhe flluskat majtas janë shfaqur" + "Shiriti i detyrave dhe flluskat djathtas janë shfaqur" "Shiriti i navigimit" "Shfaq gjithmonë shiritin e detyrave" "Ndrysho modalitetin e navigimit" "Ndarësi i shiritit të detyrave" + "Tejkalimi i shiritit të detyrave" "Lëviz në krye/majtas" "Lëviz në fund/djathtas" - "{count,plural, =1{Shfaq # aplikacion tjetër.}other{Shfaq # aplikacione të tjera.}}" - "{count,plural, =1{Shfaq # aplikacion për desktop.}other{Shfaq # aplikacione për desktop.}}" + "Hap aplikacionin si një flluskë" + "Aplikacionet e fundit" + "Lista e aplikacioneve të fundit" + "{count,plural, =1{aplikacion tjetër}other{aplikacione të tjera}}" + "Desktop" "%1$s dhe %2$s" + "%1$s, artikulli %2$d nga %3$d" + "Lëviz majtas" + "Lëviz djathtas" "Flluska" "Tejkalimi" "\"%1$s\" nga %2$s" "\"%1$s\" dhe %2$d të tjera" + "Lëviz majtas" + "Lëviz djathtas" + "Hiqi të gjitha" + "zgjero %1$s" + "palos %1$s" + "Qarko për të kërkuar" + "Ikona e aplikacionit" + "Titulli i aplikacionit" + "Butoni i mbylljes" + "Gozhdo te shiriti" + "Zhgozhdo nga shiriti" diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml index 7456a36b7ef..6742da99671 100644 --- a/quickstep/res/values-sr/strings.xml +++ b/quickstep/res/values-sr/strings.xml @@ -22,11 +22,13 @@ "Закачи" "Слободни облик" "Рачунар" + "Преместите на спољни екран" + "Затвори" + "Рачунари" "Нема недавних ставки" "Подешавања коришћења апликације" "Обриши све" "Недавне апликације" - "Задатак је затворен" "%1$s, %2$s" "< 1 мин" "Још %1$s данас" @@ -45,18 +47,18 @@ "Предлози апликација су омогућени" "Предлози апликација су онемогућени" "Предвиђамо апликацију: %1$s" + "Водич за навигацију помоћу покрета" "Ротирајте уређај" "Ротирајте уређај да бисте довршили водич за навигацију помоћу покрета" "Обавезно превуците од саме десне или леве ивице" "Обавезно превуците од десне или леве ивице до средине екрана и отпустите" "Научили сте како да превлачите здесна да бисте се вратили уназад. Сада научите да замените апликације." "Довршили сте покрет за повратак. Сада сазнајте како да промените апликације." - "Довршили сте покрет за повратак" + "Довршили сте покрет за назад" "Никако не превлачите превише близу дна екрана" "Осетљивост пок. за назад можете да промените у Подешавањима" "Превуците да бисте се вратили уназад" "Да бисте се вратили на последњи екран, превуците од леве или десне ивице до средине екрана." - "Да бисте се вратили на последњи екран, превуците помоћу два прста од леве или десне ивице до средине екрана." "Назад" "Превуците од леве или десне ивице до средине екрана" "Обавезно превуците нагоре од доње ивице екрана" @@ -66,8 +68,7 @@ "Довршили сте покрет за повратак на почетну страницу." "Превуците да бисте отишли на почетну страницу" "Превуците нагоре од дна екрана. Овај покрет вас увек води на почетни екран." - "Превуците помоћу два прста нагоре од дна екрана. Овим покретом увек отварате почетни екран." - "Идите на почетни екран" + "На почетни екран" "Превуците нагоре са доњег дела екрана" "Одлично!" "Обавезно превуците нагоре од доње ивице екрана" @@ -77,8 +78,7 @@ "Довршили сте покрет за промену апликација" "Превуците да бисте заменили апликације" "За прелазак са једне апликације на другу превуците нагоре од дна екрана, задржите, па пустите." - "За прелазак између апликација превуците помоћу два прста нагоре од дна екрана, задржите, па пустите." - "Пређите на другу апликацију" + "На другу апликацију" "Превуците нагоре од дна екрана, задржите, па пустите" "Одлично!" "То је то" @@ -89,7 +89,7 @@ "Водич %1$d/%2$d" "Готово!" "Превуците нагоре да бисте отворили почетни екран" - "Додирните дугме Почетак да бисти ишли на почетни екран" + "Додирните дугме Почетак да бисте отишли на почетни екран" "Спремни сте да почнете да користите %1$s" "уређај" "Подешавања кретања кроз систем" @@ -99,7 +99,7 @@ "Сачувај пар апликација" "Додирните другу апликацију за подељени екран" "Одаберите другу апликацију да бисте користили подељени екран" - "Откажи" + "Откажи" "Излазак из бирања подељеног екрана" "Одаберите другу апликацију за подељени екран" "Апликација или организација не дозвољавају ову радњу" @@ -130,18 +130,37 @@ "Брза подешавања" "Трака задатака" "Трака задатака је приказана" - "Трака задатака је скривена" + "Приказ задатака/облачића лево" + "Приказ задатака/облачића десно" "Трака за навигацију" "Увек приказуј траку задатака" "Промени режим навигације" "Разделник траке задатака" + "Преклопна трака задатака" "Премести горе лево" "Премести доле десно" - "{count,plural, =1{Прикажи још # апликацију.}one{Прикажи још # апликацију.}few{Прикажи још # апликације.}other{Прикажи још # апликација.}}" - "{count,plural, =1{Прикажи # апликацију за рачунаре.}one{Прикажи # апликацију за рачунаре.}few{Прикажи # апликације за рачунаре.}other{Прикажи # апликација за рачунаре.}}" + "Отвори апликацију као облачић" + "Недавне апликације" + "Листа недавних апликација" + "{count,plural, =1{додатна апликација}one{додатна апликација}few{додатне апликације}other{додатних апликација}}" + "Рачунар" "%1$s и %2$s" + "%1$s, ставка %2$d од %3$d" + "Скролујте улево" + "Скролујте удесно" "Облачић" "Преклопни" "%1$s%2$s" "%1$s и још %2$d" + "Помери налево" + "Помери надесно" + "Одбаци све" + "проширите облачић %1$s" + "скупите облачић %1$s" + "Претрага заокруживањем" + "Икона апликације" + "Назив апликације" + "Дугме Затвори" + "Закачи за траку зад." + "Откачи са траке зад." diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml index f369daeb339..c995fd05215 100644 --- a/quickstep/res/values-sv/strings.xml +++ b/quickstep/res/values-sv/strings.xml @@ -21,12 +21,14 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fäst" "Fritt format" - "Dator" + "Skrivbordsläge" + "Flytta till extern skärm" + "Stäng" + "Skrivbordsläge" "Listan är tom" "Inställningar för appanvändning" "Rensa alla" "Senaste apparna" - "Uppgiften har stängts" "%1$s, %2$s" "< 1 min" "%1$s kvar i dag" @@ -45,6 +47,7 @@ "Appförslag har aktiverats" "Appförslag har inaktiverats" "Appförslag: %1$s" + "Guide för navigering med rörelser" "Rotera enheten" "Rotera enheten för att slutföra guiden för navigering med rörelser" "Se till att du sveper ända från högerkanten eller vänsterkanten" @@ -56,7 +59,6 @@ "Öppna inställningarna om du vill ändra rörelsens känslighet" "Svep för att återgå" "Återgå till den senaste skärmen genom att svepa från skärmens vänster- eller högerkant till mitten." - "Gå tillbaka till den senaste skärmen genom att med två fingrar svepa mot mitten av skärmen från vänster eller höger kant." "Tillbaka" "Svep från den högra eller vänstra kanten till mitten av skärmen" "Se till att du sveper uppåt från nederkanten av skärmen" @@ -66,7 +68,6 @@ "Du är klar med rörelsen för att öppna startskärmen" "Svep för att öppna startskärmen" "Svep uppåt från skärmens nederkant. Du kan alltid återgå till startskärmen med den här rörelsen." - "Svep uppåt med två fingrar från skärmens nederkant. Så kommer du alltid tillbaka till startskärmen." "Öppna startskärmen" "Svep uppåt från skärmens nederkant" "Bra jobbat!" @@ -77,7 +78,6 @@ "Du är klar med rörelsen för att byta mellan appar" "Svep för att byta mellan appar" "Byt mellan appar genom att svepa uppåt från skärmens nederkant. Håll fingret nedtryckt och släpp." - "Byta mellan appar: Svep uppåt med två fingrar från skärmens nederkant, håll kvar och släpp sedan." "Byt app" "Svep uppåt från skärmens nederkant. Håll fingret nedtryckt och släpp sedan" "Bra gjort!" @@ -99,7 +99,7 @@ "Spara app-par" "Tryck på en annan app för att använda delad skärm" "Välj en annan app för att använda delad skärm" - "Avbryt" + "Avbryt" "Avsluta val av delad skärm" "Välj en annan app för att använda delad skärm" "Appen eller organisationen tillåter inte den här åtgärden" @@ -130,18 +130,37 @@ "Snabbinställn." "Aktivitetsfält" "Aktivitetsfältet visas" - "Aktivitetsfältet är dolt" + "Vänster fält och bubblor visas" + "Höger fält och bubblor visas" "Navigeringsfält" "Visa alltid aktivitetsfältet" "Ändra navigeringsläge" "Avdelare för aktivitetsfältet" + "Fler alternativ för aktivitetsfältet" "Flytta högst upp/till vänster" "Flytta längst ned/till höger" - "{count,plural, =1{Visa # app till.}other{Visa # appar till.}}" - "{count,plural, =1{Visa # datorapp.}other{Visa # datorappar.}}" + "Öppna appen som en bubbla" + "Senaste apparna" + "Lista över senaste appar" + "{count,plural, =1{app till}other{appar till}}" + "Skrivbordsläge" "%1$s och %2$s" + "%1$s, objekt %2$d av %3$d" + "Scrolla åt vänster" + "Scrolla åt höger" "Bubbla" "Fler alternativ" "%1$s från %2$s" "%1$s och %2$d till" + "Flytta åt vänster" + "Flytta åt höger" + "Stäng alla" + "utöka %1$s" + "komprimera %1$s" + "Circle to Search" + "Appikon" + "Apptitel" + "Knappen Stäng" + "Fäst i aktivitetsfält" + "Lossa från aktivitetsfält" diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml index 3d8277b4489..046e37a01f8 100644 --- a/quickstep/res/values-sw/strings.xml +++ b/quickstep/res/values-sw/strings.xml @@ -22,11 +22,13 @@ "Bandika" "Muundo huru" "Kompyuta ya mezani" + "Hamishia programu kwenye skrini ya nje" + "Funga" + "Kompyuta ya Mezani" "Hakuna vipengee vya hivi karibuni" "Mipangilio ya matumizi ya programu" "Ondoa zote" "Programu za hivi karibuni" - "Jukumu Limefungwa" "%1$s, %2$s" "< dak 1" "Umebakisha %1$s leo" @@ -45,28 +47,27 @@ "Mapendekezo ya programu yamewashwa" "Umezima mapendekezo ya programu" "Programu iliyotabiriwa: %1$s" + "Mafunzo ya Usogezaji kwa Kutumia Miguso" "Zungusha kifaa chako" - "Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia ishara" + "Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia miguso" "Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto kabisa" "Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto hadi katikati ya skrini na uachilie" "Umejifunza jinsi ya kutelezesha kidole kuanzia kulia ili kurudi nyuma. Sasa jifunze jinsi ya kubadilisha programu." - "Umekamilisha ishara ya kurudi nyuma. Hatua inayofuata, jifunze jinsi ya kubadilisha programu." - "Umeweka ishara ya kurudi nyuma" + "Umekamilisha mafunzo ya miguso ya kurudi nyuma. Hatua inayofuata, fahamu jinsi ya kubadilisha programu." + "Umekamilisha mafunzo ya miguso ya kurudi nyuma" "Hakikisha hutelezeshi kidole karibu sana na sehemu ya chini ya skrini" "Kubadilisha hisi ya ishara ya nyuma, nenda kwenye Mipangilio" "Telezesha kidole ili urudi nyuma" "Ili urudi kwenye skrini iliyotangulia, telezesha kidole kuanzia ukingo wa kushoto au wa kulia kuelekea katikati ya skrini." - "Ili urudi kwenye skrini iliyopita, telezesha vidole viwili kuanzia ukingo wa kushoto au wa kulia kuelekea katikati ya skrini." "Rudi nyuma" "Telezesha kidole kutoka ukingo wa kushoto au kulia hadi katikati ya skrini" "Hakikisha unatelezesha kidole juu kuanzia ukingo wa chini wa skrini" "Hakikisha husitishi kabla ya kuachilia" "Hakikisha unatelezesha kidole juu" - "Umeweka ishara ya kwenda kwenye Skrini ya kwanza. Inayofuata, jifunze jinsi ya kurudi nyuma." - "Umeweka ishara ya kwenda kwenye skrini ya kwanza" + "Umekamilisha mguso wa kwenda kwenye skrini ya kwanza. Inayofuata, fahamu jinsi ya kurudi nyuma." + "Umekamilisha mguso wa kwenda kwenye skrini ya kwanza" "Telezesha kidole ili uende kwenye skrini ya kwanza" "Telezesha kidole juu kuanzia chini ya skrini yako. Ishara hii kila wakati hukupeleka kwenye Skrini ya kwanza." - "Telezesha vidole viwili kuelekea juu kuanzia sehemu ya chini ya skrini. Ishara hii kila wakati hukupeleka kwenye Skrini ya kwanza." "Nenda kwenye ukurasa wa mwanzo" "Telezesha kidole juu kutoka sehemu ya chini ya skrini yako" "Kazi nzuri!" @@ -74,10 +75,9 @@ "Jaribu kushikilia dirisha kwa muda mrefu kabla ya kuachilia" "Hakikisha unatelezesha kidole juu, kisha usitishe" "Umejifunza jinsi ya kutumia ishara. Ili uzime ishara, nenda kwenye Mipangilio." - "Umeweka ishara ya kubadilisha programu" + "Umekamilisha mguso wa kubadilisha programu" "Telezesha kidole ili ubadilishe programu" "Ili ubadili kati ya programu, telezesha kidole juu kuanzia sehemu ya chini ya skrini yako, ushikilie, kisha uachilie." - "Ili ubadilishe kati ya programu, telezesha vidole viwili kuelekea juu kuanzia sehemu ya chini ya skrini yako, ushikilie, kisha uachilie." "Badilisha programu" "Telezesha kidole juu kutoka sehemu ya chini ya skrini yako, shikilia kisha uachilie" "Hongera!" @@ -99,7 +99,7 @@ "Hifadhi jozi ya programu" "Gusa programu nyingine ili utumie kipengele cha kugawa skrini" "Chagua programu nyingine ili utumie hali ya kugawa skrini" - "Ghairi" + "Acha" "Ondoka kwenye hali ya skrini iliyogawanywa" "Chagua programu nyingine ili utumie hali ya kugawa skrini" "Kitendo hiki hakiruhusiwi na programu au shirika lako" @@ -130,18 +130,37 @@ "Mipangilio ya Haraka" "Upauzana" "Upauzana umeonyeshwa" - "Upauzana umefichwa" + "Upauzana na viputo vinaonyeshwa kushoto" + "Upauzana na viputo vinaonyeshwa kulia" "Sehemu ya viungo muhimu" "Onyesha Zana kila wakati" "Badilisha hali ya usogezaji" "Kitenganishi cha Upauzana" + "Upauzana wa Vipengele vya Ziada" "Sogeza juu/kushoto" "Sogeza chini/kulia" - "{count,plural, =1{Onyesha programu # zaidi.}other{Onyesha programu # zaidi.}}" - "{count,plural, =1{Onyesha programu # ya kompyuta ya mezani.}other{Onyesha programu # za kompyuta ya mezani.}}" + "Fungua programu kama kiputo" + "Programu ulizofungua hivi majuzi" + "Orodha ya programu ulizofungua hivi majuzi" + "{count,plural, =1{programu nyingine}other{programu zingine}}" + "Kompyuta ya Mezani" "%1$s na %2$s" + "%1$s, kipengee cha %2$d kati ya %3$d" + "Sogeza kushoto" + "Sogeza kulia" "Kiputo" "Kiputo cha vipengee vya ziada" "%1$s kutoka %2$s" "%1$s na vingine %2$d" + "Sogeza kushoto" + "Sogeza kulia" + "Ondoa vyote" + "panua %1$s" + "kunja %1$s" + "Chora Mviringo ili Kutafuta" + "Aikoni ya programu" + "Kichwa cha programu" + "Kitufe cha kufunga" + "Bandika kwa upauzana" + "Bandua kwa upauzana" diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml index 5e9a177b8bc..cf7ba0094b2 100644 --- a/quickstep/res/values-sw600dp-land/dimens.xml +++ b/quickstep/res/values-sw600dp-land/dimens.xml @@ -16,7 +16,7 @@ --> - 48dp + 48dp 48dp @@ -27,10 +27,6 @@ 40dp 49dp - - - 24dp - - 40dp + 24dp diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml index e24d8fea79e..3e726519aca 100644 --- a/quickstep/res/values-sw600dp/dimens.xml +++ b/quickstep/res/values-sw600dp/dimens.xml @@ -33,20 +33,11 @@ 36dp 64dp - - 80dp - - 80dp 24dp - 120dp + 120dp 38sp 15sp - - - - 300dp - diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml index 47d8055e16b..9074f6c9c3a 100644 --- a/quickstep/res/values-ta/strings.xml +++ b/quickstep/res/values-ta/strings.xml @@ -22,11 +22,13 @@ "பின் செய்தல்" "குறிப்பிட்ட வடிவமில்லாத பயன்முறை" "டெஸ்க்டாப்" + "வெளிப்புற டிஸ்ப்ளேவிற்கு நகர்த்துதல்" + "மூடு" + "டெஸ்க்டாப்" "சமீபத்தியவை எதுவுமில்லை" "ஆப்ஸ் உபயோக அமைப்புகள்" "எல்லாம் அழி" "சமீபத்திய ஆப்ஸ்" - "பணி முடிந்தது" "%1$s, %2$s" "< 1 நி" "இன்று %1$s மீதமுள்ளது" @@ -45,6 +47,7 @@ "ஆப்ஸ் பரிந்துரைகள் இயக்கப்பட்டுள்ளன" "ஆப்ஸ் பரிந்துரைகள் முடக்கப்பட்டுள்ளன" "கணித்த ஆப்ஸ்: %1$s" + "சைகை வழிசெலுத்தலுக்கான பயிற்சி" "உங்கள் சாதனத்தைச் சுழற்றுங்கள்" "சைகை வழிசெலுத்தல் பயிற்சியை நிறைவுசெய்ய உங்கள் சாதனத்தைச் சுழற்றுங்கள்" "வலது அல்லது இடது ஓரத்தின் விளிம்பிலிருந்து ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்" @@ -56,7 +59,6 @@ "பின்செல் சைகையின் உணர்திறனை மாற்ற அமைப்புகளுக்குச் செல்க" "பின்செல்ல ஸ்வைப் செய்யுங்கள்" "முந்தைய திரைக்கு மீண்டும் செல்ல, இடது/வலது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்க." - "முந்தைய திரைக்கு மீண்டும் செல்ல, 2 விரல்களால் இடது அல்லது வலது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்யுங்கள்." "பின்செல்லுதல்" "வலது அல்லது இடது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்யுங்கள்" "திரையின் கீழ் ஓரத்திலிருந்து மேல்நோக்கி ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்" @@ -66,18 +68,16 @@ "முகப்புக்குச் செல் சைகைப் பயிற்சியை நிறைவுசெய்துவிட்டீர்கள்" "முகப்புக்குச் செல்ல ஸ்வைப் செய்யுங்கள்" "திரையின் கீழிருந்து மேலாக ஸ்வைப் செய்க. இந்தச் சைகை எப்போதும் முகப்புத் திரைக்கு அழைத்துச் செல்லும்." - "2 விரலால் திரையின் கீழிருந்து மேலாக ஸ்வைப் செய்க. இந்தச் சைகை முகப்புத் திரைக்கு அழைத்துச் செல்லும்." "முகப்புக்குச் செல்லுதல்" "திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யுங்கள்" "அருமை!" "திரையின் கீழ் ஓரத்திலிருந்து மேல்நோக்கி ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்" "விடுவிப்பதற்கு முன்பாக நீண்டநேரம் சாளரத்தை அழுத்திப் பிடித்திருங்கள்" - "மேல்நோக்கி நேராக ஸ்வைப் செய்தபிறகு இடைநிறுத்துவதை உறுதிசெய்துகொள்ளுங்கள்" + "மேல்நோக்கி நேராக ஸ்வைப் செய்தபிறகு சற்றுநேரம் அழுத்திபடியே வைத்திருங்கள்" "சைகைகளை எப்படி உபயோகிப்பது என்று கற்றுக்கொண்டீர்கள். சைகைகளை முடக்க அமைப்புகளுக்குச் செல்லுங்கள்." "ஆப்ஸுக்கிடையே மாறும் சைகைப் பயிற்சியை நிறைவுசெய்துவிட்டீர்கள்" "ஆப்ஸுக்கிடையே மாற ஸ்வைப் செய்யுங்கள்" "ஆப்ஸுக்கு இடையே மாற, திரையின் கீழிலிருந்து மேலாக ஸ்வைப் செய்து, பிடித்திருந்து, பிறகு விடுவிக்கவும்." - "ஆப்ஸுக்கிடையே மாற, திரையின் கீழிருந்து மேலாக 2 விரலால் ஸ்வைப் செய்து, பிடித்து, பிறகு விடுவிக்கவும்." "ஆப்ஸுக்கிடையே மாறுதல்" "உங்கள் திரையின் கீழ்ப்பகுதியில் இருந்து மேலே ஸ்வைப் செய்து, பிடித்து, பிறகு விடுவியுங்கள்" "அருமை!" @@ -90,7 +90,7 @@ "அனைத்தையும் அமைத்துவிட்டீர்கள்!" "முகப்புக்குச் செல்ல மேல்நோக்கி ஸ்வைப் செய்யுங்கள்" "முகப்புத் திரைக்குச் செல்வதற்கு முகப்பு பட்டனைத் தட்டவும்" - "உங்கள் %1$s சாதனத்தைப் பயன்படுத்தத் தயாராகிவிட்டீர்கள்" + "உங்கள் %1$s உங்களுக்காகத் தயாராக இருக்கிறது" "சாதனம்" "சிஸ்டம் வழிசெலுத்தல் அமைப்புகள்" "பகிர்" @@ -99,7 +99,7 @@ "ஆப்ஸ் ஜோடியைச் சேமி" "திரைப் பிரிப்பைப் பயன்படுத்த வேறு ஆப்ஸைத் தட்டவும்" "திரைப் பிரிப்பைப் பயன்படுத்த வேறு ஆப்ஸைத் தேர்வுசெய்யுங்கள்" - "ரத்துசெய்" + "ரத்துசெய்" "திரைப் பிரிப்பு தேர்வில் இருந்து வெளியேறும்" "திரைப் பிரிப்பை பயன்படுத்த வேறு ஆப்ஸை தேர்வுசெய்க" "ஆப்ஸோ உங்கள் நிறுவனமோ இந்த செயலை அனுமதிப்பதில்லை" @@ -130,18 +130,37 @@ "விரைவு அமைப்புகள்" "செயல் பட்டி" "செயல் பட்டி காட்டப்படுகிறது" - "செயல் பட்டி மறைக்கப்பட்டுள்ளது" + "செயல் பட்டி & குமிழை இடதுபுறம் காட்டும்" + "செயல் பட்டி & குமிழை வலதுபுறம் காட்டும்" "வழிசெலுத்தல் பட்டி" "செயல் பட்டியை எப்போதும் காட்டு" "வழிசெலுத்தல் பயன்முறையை மாற்று" "செயல் பட்டிப் பிரிப்பான்" + "செயல் பட்டிக்கான கூடுதல் விருப்பங்கள்" "மேலே/இடதுபுறம் நகர்த்தும்" "கீழே/வலதுபுறம் நகர்த்தும்" - "{count,plural, =1{மேலும் # ஆப்ஸைக் காட்டு.}other{மேலும் # ஆப்ஸைக் காட்டு.}}" - "{count,plural, =1{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}other{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}}" + "ஆப்ஸைக் குமிழாகத் திற" + "சமீபத்திய ஆப்ஸ்" + "சமீபத்திய ஆப்ஸ் பட்டியல்" + "{count,plural, =1{கூடுதல் ஆப்ஸ்}other{கூடுதல் ஆப்ஸ்}}" + "டெஸ்க்டாப்" "%1$s மற்றும் %2$s" + "%1$s, %3$d இல் %2$d கட்டம்" + "இடதுபுறம் நகர்த்தும்" + "வலதுபுறம் நகர்த்தும்" "குமிழ்" "கூடுதல் விருப்பங்களைக் காட்டும்" "%2$s வழங்கும் %1$s" "%1$s மற்றும் %2$d" + "இடதுபுறம் நகர்த்தும்" + "வலதுபுறம் நகர்த்தும்" + "அனைத்தையும் மூடும்" + "%1$s ஐ விரிவாக்கும்" + "%1$s ஐச் சுருக்கும்" + "வட்டமிட்டுத் தேடல்" + "ஆப்ஸ் ஐகான்" + "ஆப்ஸ் தலைப்பு" + "மூடுவதற்கான பட்டன்" + "செயல்பட்டியில் பின் செய்" + "செயல்பட்டியிலிருந்து அகற்று" diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml index a4e1cbf09c8..28852236c9c 100644 --- a/quickstep/res/values-te/strings.xml +++ b/quickstep/res/values-te/strings.xml @@ -22,11 +22,13 @@ "పిన్ చేయండి" "సంప్రదాయేతర" "డెస్క్‌టాప్" + "ఎక్స్‌టర్నల్ డిస్‌ప్లేకు తరలించండి" + "మూసివేయండి" + "డెస్క్‌టాప్" "ఇటీవలి ఐటెమ్‌లు ఏవీ లేవు" "యాప్ వినియోగ సెట్టింగ్‌లు" "అన్నీ తీసివేయండి" "ఇటీవలి యాప్‌లు" - "టాస్క్ మూసివేయబడింది" "%1$s, %2$s" "< 1 నిమిషం" "నేటికి %1$s మిగిలి ఉంది" @@ -45,6 +47,7 @@ "యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి" "యాప్ సూచ‌న‌లు డిజేబుల్‌ చేయబడ్డాయి" "సూచించబడిన యాప్: %1$s" + "సంజ్ఞ నావిగేషన్ ట్యుటోరియల్" "మీ పరికరాన్ని రొటేట్ చేయండి" "సంజ్ఞ నావిగేషన్ ట్యుటోరియల్‌ను పూర్తి చేయడానికి దయచేసి మీ పరికరాన్ని రొటేట్ చేయండి" "కుడి వైపు చిట్ట చివరి లేదా ఎడమ వైపు చిట్ట చివరి అంచు నుండి స్వైప్ చేస్తున్నారని నిర్ధారించుకోండి" @@ -56,7 +59,6 @@ "వెనుక సంజ్ఞ సున్నితత్వం మార్చడానికి, సెట్టింగ్‌లకు వెళ్లండి" "వెనుకకు వెళ్ళడం కోసం స్వైప్ చేయండి" "మునుపటి స్క్రీన్‌కు తిరిగి వెళ్లడానికి, ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి స్వయిప్ చేయండి." - "గత స్క్రీన్‌కు తిరిగి వెళ్లడానికి, ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి 2 వేళ్లతో స్వైప్ చేయండి." "వెనుకకు వెళ్లండి" "ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యకు స్వైప్ చేయండి" "మీరు స్క్రీన్ దిగువ అంచు నుండి పైకి స్వైప్ చేశారని నిర్ధారించుకోండి" @@ -66,7 +68,6 @@ "మీరు మొదటి స్క్రీన్‌కు వెళ్లే సంజ్ఞను పూర్తి చేశారు" "మొదటి స్క్రీన్‌కు వెళ్లడానికి స్వైప్ చేయండి" "స్క్రీన్ కింది నుండి పైకి స్వైప్ చేయండి. ఈ సంజ్ఞ ఎప్పుడూ మిమ్మల్ని మొదటి స్క్రీన్‌కు తీసుకెళ్తుంది." - "స్క్రీన్ కింది నుండి 2 వేళ్లతో పైకి స్వైప్ చేయండి. సంజ్ఞ ఎల్లప్పుడూ మొదటి స్క్రీన్‌కు తీసుకెళ్తుంది." "మొదటి ట్యాబ్‌కు వెళ్లండి" "స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి" "బాగా చేశారు!" @@ -74,10 +75,9 @@ "వేలిని రిలీజ్ చేయడానికి ముందు విండోను ఎక్కువసేపు నొక్కి, పట్టుకోవడానికి ట్రై చేయండి" "స్క్రీన్‌పై నేరుగా పైకి స్వైప్ చేసి, ఆపై పాజ్ చేయండి" "మీరు సంజ్ఞలను ఎలా ఉపయోగించాలో నేర్చుకున్నారు. సంజ్ఞలను ఆఫ్ చేయడానికి, సెట్టింగ్‌లకు వెళ్లండి." - "మీరు \'యాప్‌ల మధ్య మార్పు\' సంజ్ఞను పూర్తి చేశారు" + "మీరు \'యాప్‌ల మధ్య మారేందుకు సంజ్ఞ\' ట్యుటోరియల్‌ను పూర్తి చేశారు" "యాప్‌ల మధ్య మార్చడం కోసం స్వైప్ చేయండి" "యాప్‌ల మధ్య మారడానికి, మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, పట్టుకుని, తర్వాత వదలండి." - "యాప్‌ల మధ్య మారడానికి, మీ స్క్రీన్ కింది నుండి 2 వేళ్లతో పైకి స్వైప్ చేసి, నొక్కి పట్టి, వదలండి." "యాప్‌ల మధ్య స్విచ్ అవ్వండి" "మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, పట్టుకుని, తర్వాత వదలండి" "చాలా బాగా చేశారు!" @@ -99,7 +99,7 @@ "యాప్ పెయిర్‌ను సేవ్ చేయండి" "స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్‌ను ట్యాప్ చేయండి" "స్ప్లిట్ స్క్రీన్‌ను ఉపయోగించడానికి మరొక యాప్ ఎంచుకోండి" - "రద్దు చేయండి" + "రద్దు చేయండి" "స్ప్లిట్ స్క్రీన్ ఎంపిక నుండి ఎగ్జిట్ అవ్వండి" "స్ప్లిట్ స్క్రీన్ ఉపయోగానికి మరొక యాప్ ఎంచుకోండి" "ఈ చర్యను యాప్ గానీ, మీ సంస్థ గానీ అనుమతించవు" @@ -130,18 +130,37 @@ "క్విక్ సెట్టింగ్‌లు" "టాస్క్‌బార్" "టాస్క్‌బార్ చూపబడింది" - "టాస్క్‌బార్ దాచబడింది" + "టాస్క్‌బార్, బబుల్స్ ఎడమవైపున చూపబడ్డాయి" + "టాస్క్‌బార్, బబుల్స్ కుడివైపున చూపబడ్డాయి" "నావిగేషన్ బార్" "టాస్క్‌బార్‌ను నిరంతరం చూపండి" "నావిగేషన్ మోడ్‌ను మార్చండి" "టాస్క్‌బార్ డివైడర్" + "టాస్క్‌బార్ ఓవర్‌ఫ్లో" "ఎగువ/ఎడమ వైపునకు తరలించండి" "దిగువ/కుడి వైపునకు తరలించండి" - "{count,plural, =1{మరో # యాప్‌ను చూడండి.}other{మరో # యాప్‌లను చూడండి.}}" - "{count,plural, =1{# డెస్క్‌టాప్ యాప్‌ను చూపండి.}other{# డెస్క్‌టాప్ యాప్‌లను చూపండి.}}" + "యాప్‌ను బబుల్‌లాగా తెరవండి" + "ఇటీవలి యాప్‌లు" + "ఇటీవలి యాప్ లిస్ట్" + "{count,plural, =1{మరో యాప్‌}other{మరిన్ని యాప్‌లు}}" + "డెస్క్‌టాప్" "%1$s, %2$s" + "%1$s, %3$d‌లో %2$d‌వ ఐటెమ్" + "ఎడమవైపునకు స్క్రోల్ చేయండి" + "కుడివైపునకు స్క్రోల్ చేయండి" "బబుల్" "ఓవర్‌ఫ్లో" "%2$s నుండి %1$s" "%1$s, మరో %2$d" + "ఎడమ వైపుగా జరపండి" + "కుడి వైపుగా జరపండి" + "అన్నింటినీ విస్మరించండి" + "%1$sను విస్తరించండి" + "%1$sను కుదించండి" + "సెర్చ్ చేయడానికి సర్కిల్ గీయండి" + "యాప్ చిహ్నం" + "యాప్ టైటిల్" + "\'మూసివేయండి\' బటన్" + "టాస్క్‌బార్‌కు పిన్" + "టాస్క్‌బార్ అన్‌పిన్" diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml index 1bbb137a1b9..09d0df14a83 100644 --- a/quickstep/res/values-th/strings.xml +++ b/quickstep/res/values-th/strings.xml @@ -22,11 +22,13 @@ "ปักหมุด" "รูปแบบอิสระ" "เดสก์ท็อป" + "ย้ายไปยังจอแสดงผลภายนอก" + "ปิด" + "เดสก์ท็อป" "ไม่มีรายการล่าสุด" "การตั้งค่าการใช้แอป" "ล้างทั้งหมด" "แอปล่าสุด" - "ปิดงานแล้ว" "%1$s %2$s" "<1 นาที" "วันนี้เหลืออีก %1$s" @@ -45,39 +47,37 @@ "เปิดใช้แอปแนะนำแล้ว" "ปิดใช้คำแนะนำเกี่ยวกับแอปอยู่" "แอปที่คาดว่าจะใช้: %1$s" + "บทแนะนำการไปยังส่วนต่างๆ ด้วยท่าทางสัมผัส" "หมุนอุปกรณ์ของคุณ" - "โปรดหมุนอุปกรณ์เพื่อทำตามบทแนะนำการนำทางด้วยท่าทางสัมผัสให้เสร็จสมบูรณ์" - "ตรวจสอบว่าปัดจากขอบด้านขวาสุดหรือซ้ายสุด" + "โปรดหมุนอุปกรณ์เพื่อทำตามบทแนะนำการไปยังส่วนต่างๆ ด้วยท่าทางสัมผัสให้เสร็จสมบูรณ์" + "ปัดจากขอบด้านขวาสุดหรือซ้ายสุด" "ตรวจสอบว่าปัดจากขอบด้านขวาหรือซ้ายไปตรงกลางหน้าจอ แล้วยกนิ้วขึ้น" "คุณรู้วิธีปัดจากด้านขวาเพื่อย้อนกลับแล้ว ต่อไปดูวิธีสลับแอป" - "คุณทำท่าทางสัมผัสเพื่อย้อนกลับเสร็จแล้ว ต่อไปดูวิธีสลับแอป" - "คุณทำท่าทางสัมผัสเพื่อย้อนกลับเสร็จแล้ว" + "คุณทำท่าทางสัมผัสเพื่อย้อนกลับสำเร็จแล้ว ต่อไปดูวิธีสลับแอป" + "คุณทำท่าทางสัมผัสเพื่อย้อนกลับสำเร็จแล้ว" "ไม่ปัดใกล้กับด้านล่างของหน้าจอมากเกินไป" "เปลี่ยนความไวของท่าทางสัมผัสเพื่อย้อนกลับได้ที่การตั้งค่า" "ปัดเพื่อย้อนกลับ" "หากต้องการย้อนกลับไปที่หน้าจอล่าสุด ให้ปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ" - "หากต้องการย้อนกลับไปที่หน้าจอล่าสุด ให้ใช้ 2 นิ้วปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ" "ย้อนกลับ" "ปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ" "ปัดขึ้นจากขอบด้านล่างของหน้าจอ" "ไม่ต้องหยุดชั่วคราวก่อนยกนิ้วขึ้น" "ปัดขึ้นในแนวตรง" - "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าแรกเสร็จแล้ว ต่อไปดูวิธีย้อนกลับ" - "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าแรกเสร็จแล้ว" + "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าจอหลักสำเร็จแล้ว ต่อไปดูวิธีย้อนกลับ" + "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าจอหลักสำเร็จแล้ว" "ปัดเพื่อไปที่หน้าแรก" "ปัดขึ้นจากด้านล่างของหน้าจอ ท่าทางสัมผัสนี้จะนำคุณไปที่หน้าจอหลักเสมอ" - "ใช้ 2 นิ้วปัดขึ้นจากด้านล่างของหน้าจอ ท่าทางสัมผัสนี้จะนำคุณไปที่หน้าจอหลักเสมอ" "ไปที่หน้าจอหลัก" "ปัดขึ้นจากด้านล่างของหน้าจอ" "เก่งมาก" "ปัดขึ้นจากขอบด้านล่างของหน้าจอ" "ลองแตะหน้าต่างค้างไว้นานขึ้นก่อนปล่อยนิ้ว" - "ตรวจสอบว่าปัดขึ้นในแนวตรง แล้วหยุดชั่วคราว" + "ปัดขึ้นในแนวตรง แล้วหยุดชั่วคราว" "คุณรู้วิธีใช้ท่าทางสัมผัสแล้ว หากต้องการปิดท่าทางสัมผัส ให้ไปที่การตั้งค่า" - "คุณทำท่าทางสัมผัสเพื่อสลับแอปเสร็จแล้ว" + "คุณทำท่าทางสัมผัสเพื่อเปลี่ยนแอปสำเร็จแล้ว" "ปัดเพื่อสลับแอป" "หากต้องการสลับระหว่างแอปต่างๆ ให้ปัดขึ้นจากด้านล่างของหน้าจอ ค้างไว้ แล้วปล่อย" - "หากต้องการสลับระหว่างแอป ให้ใช้ 2 นิ้วปัดขึ้นจากด้านล่างของหน้าจอค้างไว้แล้วปล่อย" "เปลี่ยนแอป" "ปัดขึ้นจากด้านล่างของหน้าจอ ค้างไว้ แล้วปล่อย" "เยี่ยมมาก" @@ -99,7 +99,7 @@ "บันทึกคู่แอป" "แตะแอปอื่นเพื่อใช้การแยกหน้าจอ" "เลือกแอปอื่นเพื่อใช้การแยกหน้าจอ" - "ยกเลิก" + "ยกเลิก" "ออกจากการเลือกโหมดแยกหน้าจอ" "เลือกแอปอื่นเพื่อใช้การแยกหน้าจอ" "แอปหรือองค์กรของคุณไม่อนุญาตการดำเนินการนี้" @@ -130,18 +130,37 @@ "การตั้งค่าด่วน" "แถบงาน" "แถบงานแสดงอยู่" - "แถบงานซ่อนอยู่" + "แถบงานและบับเบิลแสดงไว้ทางซ้าย" + "แถบงานและบับเบิลแสดงไว้ทางขวา" "แถบนำทาง" "แสดงแถบงานเสมอ" "เปลี่ยนโหมดการนําทาง" "ตัวแบ่งแถบงาน" + "การดำเนินการเพิ่มเติมของแถบงาน" "ย้ายไปที่ด้านบนหรือด้านซ้าย" "ย้ายไปที่ด้านล่างหรือด้านขวา" - "{count,plural, =1{แสดงเพิ่มเติมอีก # แอป}other{แสดงเพิ่มเติมอีก # แอป}}" - "{count,plural, =1{แสดงแอปบนเดสก์ท็อป # รายการ}other{แสดงแอปบนเดสก์ท็อป # รายการ}}" + "เปิดแอปเป็นบับเบิล" + "แอปล่าสุด" + "รายการแอปล่าสุด" + "{count,plural, =1{แอปเพิ่มเติม}other{แอปเพิ่มเติม}}" + "เดสก์ท็อป" "%1$s และ %2$s" + "%1$s, รายการที่ %2$d จาก %3$d" + "เลื่อนไปทางซ้าย" + "เลื่อนไปทางขวา" "บับเบิล" "การดำเนินการเพิ่มเติม" "%1$s จาก %2$s" "%1$s และอีก %2$d รายการ" + "ย้ายไปทางซ้าย" + "ย้ายไปทางขวา" + "ปิดทั้งหมด" + "ขยาย %1$s" + "ยุบ %1$s" + "วงเพื่อค้นหา" + "ไอคอนแอป" + "ชื่อแอป" + "ปุ่มปิด" + "ปักหมุดไปยังแถบงาน" + "เลิกปักหมุดจากแถบงาน" diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml index 978a5a37f87..35515789cbc 100644 --- a/quickstep/res/values-tl/strings.xml +++ b/quickstep/res/values-tl/strings.xml @@ -22,11 +22,13 @@ "I-pin" "Freeform" "Desktop" + "Ilipat sa external na display" + "Isara" + "Desktop" "Walang kamakailang item" "Mga setting ng paggamit ng app" "I-clear lahat" "Mga kamakailang app" - "Isinara ang Gawain" "%1$s, %2$s" "< 1 min" "%1$s na lang ngayon" @@ -45,6 +47,7 @@ "Naka-enable ang mga iminumungkahing app" "Naka-disable ang mga iminumungkahing app" "Hinulaang app: %1$s" + "Tutorial sa Navigation gamit ang Galaw" "I-rotate ang iyong device" "Paki-rotate ang iyong device para tapusin ang tutorial sa navigation gamit ang galaw" "Tiyaking magsa-swipe ka mula sa dulong kanan o dulong kaliwang gilid" @@ -56,7 +59,6 @@ "Pumunta sa Settings para baguhin ang sensitivity ng pagbalik" "Mag-swipe para bumalik" "Para bumalik sa nakaraang screen, mag-swipe mula sa kaliwa o kanang gilid patungo sa gitna ng screen." - "Para bumalik sa huling screen, mag-swipe gamit ang 2 daliri mula sa kaliwa o kanang gilid hanggang sa gitna ng screen." "Bumalik" "Mag-swipe mula sa kaliwa o kanang gilid papunta sa gitna ng screen" "Tiyaking magsa-swipe ka pataas mula sa pinakaibaba ng screen" @@ -66,7 +68,6 @@ "Nakumpleto mo na ang galaw para pumunta sa home" "Mag-swipe para pumunta sa home" "Mag-swipe pataas mula sa ibaba ng iyong screen. Dadalhin ka palagi ng galaw na ito sa Home screen." - "Mag-swipe pataas gamit ang 2 daliri mula sa ibaba ng screen. Dadalhin ka palagi nito sa Home screen." "Pumunta sa home" "Mag-swipe pataas mula sa ibabang bahagi ng iyong screen" "Magaling!" @@ -77,7 +78,6 @@ "Nakumpleto mo na ang galaw para magpalipat-lipat sa mga app" "Mag-swipe para lumipat ng app" "Para lumipat ng app, mag-swipe pataas mula sa ibaba ng iyong screen, mag-hold, at iangat ang daliri." - "Para lumipat ng app, mag-swipe pataas gamit ang 2 daliri mula sa ibaba, mag-hold, at bumitaw." "Lumipat ng app" "Mag-swipe pataas mula sa ibaba ng iyong screen, i-hold ito saka bitawan" "Magaling!" @@ -99,7 +99,7 @@ "I-save ang app pair" "Mag-tap ng ibang app para gamitin ang split screen" "Pumili ng ibang app para gamitin ang split screen" - "Kanselahin" + "Kanselahin" "Lumabas sa pagpili ng split screen" "Pumili ng ibang app para gamitin ang split screen" "Hindi pinapayagan ng app o ng iyong organisasyon ang pagkilos na ito" @@ -130,18 +130,37 @@ "Quick Settings" "Taskbar" "Ipinapakita ang taskbar" - "Nakatago ang taskbar" + "Taskbar at bubble sa kaliwa" + "Taskbar at bubble sa kanan" "Navigation bar" "Ipakita lagi ang Taskbar" "Magpalit ng navigation mode" "Divider ng Taskbar" + "Taskbar Overflow" "Ilipat sa itaas/kaliwa" "Ilipat sa ibaba/kanan" - "{count,plural, =1{Magpakita ng # pang app.}one{Magpakita ng # pang app.}other{Magpakita ng # pang app.}}" - "{count,plural, =1{Ipakita ang # desktop app.}one{Ipakita ang # desktop app.}other{Ipakita ang # na desktop app.}}" + "Buksan ang app bilang bubble" + "Mga kamakailang app" + "Kamakailang listahan ng app" + "{count,plural, =1{pang app}one{pang app}other{pang app}}" + "Desktop" "%1$s at %2$s" + "%1$s, item %2$d ng %3$d" + "Mag-scroll pakaliwa" + "Mag-scroll pakanan" "Bubble" "Overflow" "%1$s mula sa %2$s" "%1$s at %2$d pa" + "Ilipat pakaliwa" + "Ilipat pakanan" + "I-dismiss lahat" + "i-expand ang %1$s" + "i-collapse ang %1$s" + "Circle to Search" + "Icon ng app" + "Pamagat ng app" + "Button na isara" + "I-pin sa taskbar" + "I-unpin sa taskbar" diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml index 0cc5d7f8561..5c5fba656df 100644 --- a/quickstep/res/values-tr/strings.xml +++ b/quickstep/res/values-tr/strings.xml @@ -22,11 +22,13 @@ "Sabitle" "Serbest çalışma" "Masaüstü" + "Harici ekrana taşı" + "Kapat" + "Masaüstü" "Yeni öğe yok" "Uygulama kullanım ayarları" "Tümünü temizle" "Son uygulamalar" - "Görev Kapatıldı" "%1$s, %2$s" "< 1 dk." "Bugün %1$s kaldı" @@ -45,6 +47,7 @@ "Uygulama önerileri etkinleştirildi" "Uygulama önerileri devre dışı bırakıldı" "Tahmin edilen uygulama: %1$s" + "Hareketle Gezinme Eğitimi" "Cihazınızı döndürün" "Hareketle gezinme eğitimini tamamlamak için lütfen cihazınızı döndürün" "En sağ veya en sol kenardan kaydırdığınızdan emin olun" @@ -56,7 +59,6 @@ "Geri hareketinin hassasiyetini değiştirmek için Ayarlar\'a gidin" "Geri dönmek için kaydırma" "Son ekrana geri gitmek için sol veya sağ kenardan ekranın ortasına doğru kaydırın." - "Son ekrana geri gitmek için sol veya sağ kenardan ekranın ortasına doğru 2 parmağınızla kaydırın." "Geri dönme" "Sol veya sağ kenardan ekranın ortasına doğru kaydırın" "Ekranın alt kenarından yukarı kaydırdığınızdan emin olun" @@ -66,7 +68,6 @@ "Ana ekrana git hareketini tamamladınız" "Ana ekrana gitmek için kaydırma" "Ekranın alt kısmından yukarıya doğru kaydırın. Bu hareket sizi her zaman Ana ekrana götürür." - "Ekranın alt kısmından 2 parmağınızla yukarı kaydırın. Bu hareket sizi her zaman Ana ekrana götürür." "Ana sayfaya gitme" "Parmağınızı ekranın alt kısmından yukarıya doğru kaydırın" "Tebrikler!" @@ -77,7 +78,6 @@ "Uygulamalar arasında geçiş yapma hareketini tamamladınız" "Uygulamalar arasında geçiş yapmak için kaydırma" "Uygulamalar arasında geçiş yapmak için ekranınızın altından yukarı kaydırıp basılı tutun ve sonra bırakın." - "Uygulamalara geçiş yapmak için ekranın altından 2 parmakla yukarı kaydırıp basılı tutun ve bırakın." "Uygulamalar arasında geçiş yapma" "Ekranınızın alt tarafından yukarı doğru kaydırın, tutun ve sonra bırakın" "Tebrikler!" @@ -87,10 +87,10 @@ "Tekrar deneyin" "Güzel!" "Eğitim %1$d/%2$d" - "İşlem tamam!" + "Kurulum tamamlandı" "Ana ekrana gitmek için yukarı kaydırın" "Ana ekranınıza gitmek için ana sayfa düğmesine dokunun" - "%1$s cihazınızı kullanmaya hazırsınız" + "Artık %1$s kullanılmak için hazır" "cihaz" "Sistem gezinme ayarları" "Paylaş" @@ -99,7 +99,7 @@ "Uygulama çiftini kaydet" "Bölünmüş ekran için başka bir uygulamaya dokunun" "Bölünmüş ekran kullanmak için başka bir uygulama seçin" - "İptal" + "İptal" "Bölünmüş ekran seçiminden çıkın" "Bölünmüş ekran kullanmak için başka bir uygulama seçin" "Uygulamanız veya kuruluşunuz bu işleme izin vermiyor" @@ -130,18 +130,37 @@ "Hızlı Ayarlar" "Görev çubuğu." "Görev çubuğu gösteriliyor" - "Görev çubuğu gizlendi" + "Görev çubuğu ve baloncuklar solda gösteriliyor" + "Görev çubuğu ve baloncuklar sağda gösteriliyor" "Gezinme çubuğu" "Görev çubuğunu daima göster" "Gezinme modunu değiştir" "Görev Çubuğu Ayırıcısı" + "Görev Çubuğu Taşması" "Sol üste taşı" "Sağ alta taşı" - "{count,plural, =1{# uygulama daha göster.}other{# uygulama daha göster}}" - "{count,plural, =1{# masaüstü uygulamasını göster.}other{# masaüstü uygulamasını göster.}}" + "Uygulamayı balon olarak aç" + "Son uygulamalar" + "Son uygulama listesi" + "{count,plural, =1{uygulama daha}other{uygulama daha}}" + "Masaüstü" "%1$s ve %2$s" + "%1$s, %2$d/%3$d öğe" + "Sola kaydır" + "Sağa kaydır" "Balon" "Taşma" "%2$s uygulamasından %1$s" "%1$s ve %2$d tane daha" + "Sola taşı" + "Sağa taşı" + "Tümünü kapat" + "genişlet: %1$s" + "daralt: %1$s" + "Seçerek Arat" + "Uygulama simgesi" + "Uygulama başlığı" + "Kapat düğmesi" + "Çubuğa sabitle" + "Çubuktan kaldır" diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml index 9c706a84033..1d1f040acce 100644 --- a/quickstep/res/values-uk/strings.xml +++ b/quickstep/res/values-uk/strings.xml @@ -21,12 +21,14 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Закріпити" "Довільна форма" - "Комп’ютер" + "Робочий стіл" + "Перемістити на зовнішній екран" + "Закрити" + "Комп’ютер" "Немає нещодавніх додатків" "Налаштування використання додатка" "Очистити все" "Нещодавні додатки" - "Завдання закрито" "%1$s, %2$s" "< 1 хв" "Сьогодні залишилося %1$s" @@ -45,6 +47,7 @@ "Рекомендовані додатки ввімкнено" "Рекомендовані додатки вимкнено" "Передбачений додаток: %1$s" + "Посібник із навігації за допомогою жестів" "Оберніть пристрій" "Обертайте пристрій, щоб ознайомитися з посібником із навігації за допомогою жестів" "Проведіть пальцем від самого краю екрана (правого або лівого)" @@ -56,7 +59,6 @@ "Щоб змінити чутливість жесту \"Назад\", відкрийте налаштування" "Щоб повернутися, проведіть пальцем по екрану" "Щоб перейти на попередній екран, проведіть пальцем від лівого чи правого краю до середини екрана." - "Щоб перейти на попередній екран, проведіть двома пальцями від лівого чи правого краю до середини екрана." "Повернення на попередній екран" "Проведіть пальцем від лівого чи правого краю до середини екрана" "Проведіть пальцем угору від нижнього краю екрана" @@ -66,7 +68,6 @@ "Ви виконали жест переходу на головний екран" "Проведіть пальцем, щоб перейти на головний екран" "Проведіть пальцем по екрану знизу вгору. Цей жест завжди повертатиме вас на головний екран." - "Проведіть двома пальцями вгору від низу екрана. Цей жест завжди спрямовує вас на головний екран." "Перехід на головний екран" "Проведіть пальцем угору від низу екрана" "Чудово!" @@ -77,7 +78,6 @@ "Ви виконали жест переходу в інший додаток" "Проведіть пальцем, щоб перейти в інший додаток" "Щоб переключатися між додатками, проведіть знизу вгору по екрану, утримуйте палець, а потім відпустіть." - "Щоб перейти в інший додаток, проведіть 2 пальцями від низу екрана, потримайте й відпустіть палець." "Перемикання між додатками" "Проведіть пальцем знизу вгору, утримуйте палець на екрані, а потім відпустіть" "Чудово!" @@ -92,14 +92,14 @@ "Натисніть кнопку головного екрана, щоб відкрити його" "Тепер ви можете використовувати %1$s" "пристрій" - "Системні налаштування навігації" + "Налаштування навігації в системі" "Поділитися" "Знімок екрана" "Розділити" "Зберегти пару" "Щоб розділити екран, виберіть ще один додаток." "Щоб розділити екран, виберіть ще один додаток." - "Скасувати" + "Скасувати" "Вийти з режиму розділення екрана" "Щоб розділити екран, виберіть ще один додаток." "Ця дія заборонена додатком або адміністратором організації" @@ -130,18 +130,37 @@ "Швидкі налаштув." "Панель завдань" "Панель завдань показано" - "Панель завдань приховано" + "Панель завдань і чати – зліва" + "Панель завдань і чати – справа" "Панель навігації" "Завжди показув. панель завдань" "Змінити режим навігації" "Розділювач панелі завдань" + "Додаткове меню панелі завдань" "Перемістити вгору або вліво" "Перемістити вниз або вправо" - "{count,plural, =1{Показати ще # додаток.}one{Показати ще # додаток.}few{Показати ще # додатки.}many{Показати ще # додатків.}other{Показати ще # додатка.}}" - "{count,plural, =1{Показати # комп’ютерну програму.}one{Показати # комп’ютерну програму.}few{Показати # комп’ютерні програми.}many{Показати # комп’ютерних програм.}other{Показати # комп’ютерної програми.}}" + "Відкрити додаток у спливаючому вікні" + "Нещодавні додатки" + "Список нещодавніх додатків" + "{count,plural, =1{інший додаток}one{інший додаток}few{інші додатки}many{інших додатків}other{іншого додатка}}" + "Комп’ютер" "%1$s та %2$s" + "%1$s, об’єкт %2$d з %3$d" + "Прокрутити вліво" + "Прокрутити вправо" "Повідомлення" "Додаткове повідомлення" "%1$s з додатка %2$s" "%1$s і ще %2$d" + "Перемістити вліво" + "Перемістити вправо" + "Закрити все" + "розгорнути \"%1$s\"" + "згорнути \"%1$s\"" + "Обвести й знайти" + "Значок додатка" + "Назва додатка" + "Кнопка \"Закрити\"" + "На панель завдань" + "З панелі завдань" diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml index e12524868f5..8c44ef71a43 100644 --- a/quickstep/res/values-ur/strings.xml +++ b/quickstep/res/values-ur/strings.xml @@ -22,11 +22,13 @@ "پن کریں" "فری فارم" "ڈیسک ٹاپ" + "بیرونی ڈسپلے پر متقل کریں" + "بند کریں" + "ڈیسک ٹاپ" "کوئی حالیہ آئٹم نہیں" "ایپ کے استعمال کی ترتیبات" "سبھی کو صاف کریں" "حالیہ ایپس" - "ٹاسک بند ہے" "%1$s،%2$s" "‏< 1 منٹ" "آج %1$s بچا ہے" @@ -45,6 +47,7 @@ "ایپ کی تجاویز فعال ہیں" "ایپ کی تجاویز غیر فعال ہیں" "پیشن گوئی کردہ ایپ: %1$s" + "اشاروں والی نیویگیشن ٹیوٹوریل" "اپنا آلہ گھمائیں" "براہ کرم اشاروں والی نیویگیشن کا ٹیوٹوریل مکمل کرنے کے لیے اپنا آلہ گھمائیں" "یقینی بنائیں کہ آپ دائیں یا بائیں کنارے سے دور سے سوائپ کریں" @@ -56,7 +59,6 @@ "پچھلے اشارے کی حساسیت تبدیل کرنے کے لیے ترتیبات پر جائیں" "واپس جانے کے لیے سوائپ کریں" "پچھلی اسکرین پر واپس جانے کے لیے بائیں یا دائیں کنارے سے اسکرین کے وسط تک سوائپ کریں۔" - "آخری اسکرین پر واپس جانے کے لیے، 2 انگلیوں سے بائیں یا دائیں کنارے سے اسکرین کے وسط تک سوائپ کریں۔" "واپس جائیں" "دائیں یا بائیں کنارے سے اسکرین کے وسط تک سوائپ کریں" "اس بات کو یقینی بنائیں کہ آپ اسکرین کے نچلے کنارے سے اوپر کی طرف سوائپ کریں" @@ -66,7 +68,6 @@ "آپ نے ہوم پر جانے کا اشارہ مکمل کر لیا" "ہوم پر جانے کے لیے سوائپ کریں" "اپنی اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں۔ یہ اشارہ آپ کو ہمیشہ ہوم اسکرین پر لے جاتا ہے۔" - "اسکرین کے نیچے سے 2 انگلیوں سے اوپر سوائپ کریں۔ یہ اشارہ آپ کو ہمیشہ ہوم اسکرین پر لے جاتا ہے۔" "ہوم پر جائیں" "اپنی اسکرین کے نچلے حصے سے اوپر کی طرف سوائپ کریں" "بہترین!" @@ -77,7 +78,6 @@ "آپ نے ایپس کو سوئچ کرنے کا اشارہ مکمل کر لیا" "ایپس سوئچ کرنے کے لیے سوائپ کریں" "ایپس کے مابین سوئچ کرنے کے لیے، اپنی اسکرین کے نچلے حصے سے اوپر کی جانب سوائپ کریں، پکڑے رکھیں، پھر چھوڑ دیں۔" - "ایپس کے مابین سوئچ کرنے کیلئے، اپنی اسکرین کے نیچے سے 2 انگلیوں سے اوپر سوائپ کریں، دبائے رکھیں پھر چھوڑ دیں۔" "ایپس سوئچ کریں" "اپنی اسکرین کے نچلے حصے سے اوپر کی جانب سوائپ کریں، دبائے رکھیں، پھر چھوڑ دیں" "بہت خوب!" @@ -99,7 +99,7 @@ "ایپس کے جوڑے کو محفوظ کریں" "اسپلٹ اسکرین کا استعمال کرنے کیلئے دوسری ایپ پر تھپتھپائیں" "اسپلٹ اسکرین کے استعمال کیلئے دوسری ایپ منتخب کریں" - "منسوخ کریں" + "منسوخ کریں" "اسپلٹ اسکرین کے انتخاب سے باہر نکلیں" "اسپلٹ اسکرین کے استعمال کیلئے دوسری ایپ منتخب کریں" "ایپ یا آپ کی تنظیم کی جانب سے اس کارروائی کی اجازت نہیں ہے" @@ -130,18 +130,37 @@ "فوری ترتیبات" "ٹاسک بار" "ٹاشک بار دکھایا گیا" - "ٹاسک بار چھپایا گیا" + "ٹاسک بار و بلبلے بائیں طرف ہیں" + "ٹاسک بار و بلبلے دائیں طرف ہیں" "نیویگیشن بار" "ہمیشہ ٹاسک بار دکھائیں" "نیویگیشن موڈ تبدیل کریں" "ٹاسک بار ڈیوائیڈر" + "ٹاسک بار اوورفلو" "اوپر/بائیں طرف منتقل کریں" "نیچے/دائیں طرف منتقل کریں" - "{count,plural, =1{# مزید ایپ دکھائیں۔}other{# مزید ایپس دکھائیں۔}}" - "{count,plural, =1{# ڈیسک ٹاپ ایپ دکھائیں۔}other{# ڈیسک ٹاپ ایپس دکھائیں۔}}" + "ایپ کو بطور ببل کھولیں" + "حالیہ ایپس" + "حالیہ ایپ کی فہرست" + "{count,plural, =1{مزید ایپ}other{مزید ایپس}}" + "ڈیسک ٹاپ" "%1$s اور %2$s" + "‫%1$s، آئٹم %2$d از %3$d" + "بائیں طرف اسکرول کریں" + "دائیں طرف اسکرول کریں" "ببل" "اوورفلو" "%2$s سے %1$s" "%1$s اور %2$d مزید" + "بائیں منتقل کریں" + "دائیں منتقل کریں" + "سبھی کو برخاست کریں" + "%1$s کو پھیلائیں" + "%1$s کو سکیڑیں" + "تلاش کرنے کیلئے دائرہ بنائیں" + "ایپ آئیکن" + "ایپ کا عنوان" + "\'بند کریں\' بٹن" + "ٹاسک بار میں پن کریں" + "ٹاسک بار سے پن ہٹائیں" diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml index 3f4f9810959..86fdd5614ce 100644 --- a/quickstep/res/values-uz/strings.xml +++ b/quickstep/res/values-uz/strings.xml @@ -22,11 +22,13 @@ "Qadash" "Erkin shakl" "Desktop" + "Tashqi displeyga olish" + "Yopish" + "Desktop" "Yaqinda ishlatilgan ilovalar yo‘q" "Ilovadan foydalanish sozlamalari" "Hammasini tozalash" "Oxirgi ilovalar" - "Vazifalar yopildi" "%1$s, %2$s" "< 1 daqiqa" "Bugun %1$s qoldi" @@ -45,6 +47,7 @@ "Ilova tavsiyalari yoqildi" "Endi ilova takliflari chiqmaydi" "Taklif etilgan ilova: %1$s" + "Ishorali navigatsiya darsligi" "Qurilmangizni buring" "Ishorali navigatsiya darsligini tugatish uchun qurilmani buring" "Ekran chetidan boshlab oʻngdan yoki chapdan suring" @@ -56,7 +59,6 @@ "Orqaga ishorasi sezuvchanligi Sozlamalardan oʻzgartiriladi" "Orqaga qaytish" "Ortga qaytish uchun barmoqni ekranning yon chekkalaridan oʻrtasigacha suring." - "Oxirgi ekranga qaytish uchun 2 barmoq bilan ekranning chap yoki oʻng chekkasidan oʻrtasigacha suring." "Orqaga" "Chap yoki oʻng chetidan ekranning oʻrtasiga suring" "Barmoqni ekranning pastki chetidan yuqoriga suring." @@ -66,7 +68,6 @@ "Bosh ekranni ochish ishorasi darsini tamomladingiz" "Svayp bilan bosh ekranni ochish" "Ekranning pastidan tepaga qarab suring. Bu ishora doim Bosh ekranni ochadi." - "2 barmoq bilan ekranning quyidan tepasiga suring. Bu ishora har doim Bosh ekranni ochadi." "Boshiga" "Ekranning quyi qismidan tepaga torting" "Barakalla!" @@ -77,7 +78,6 @@ "Ilovalarni almashtirish darsini tamomladingiz" "Ilovalar orasida almashish" "Ilovalarni ochish uchun ekranning pastidan tepaga qarab suring, biroz ushlab turing va qoʻyib yuboring" - "Ilovalarni almashtirish uchun 2 barmoq bilan ekranning quyidan tepasiga surib turib, qoʻyib yuboring" "Ilovalarni almashtirish" "Ekranning pastidan tepaga qarab suring, biroz ushlab turing va qoʻyib yuboring" "Juda soz!" @@ -90,7 +90,7 @@ "Hammasi tayyor!" "Boshiga qaytish uchun tepaga suring" "Bosh ekranga oʻtish uchun bosh ekran tugmasini bosing" - "%1$s xizmatga tayyor" + "Sizning %1$s xizmatga tayyor" "qurilma" "Tizim navigatsiya sozlamalari" "Ulashish" @@ -99,7 +99,7 @@ "Ilova juftini saqlash" "Ekranni ikkiga ajratish uchun boshqa ilovani bosing" "Ekranni ikkiga ajratish uchun boshqa ilovani tanlang" - "Bekor qilish" + "Bekor qilish" "Ekranni ikkiga ajratish tanlovidan chiqish" "Ekranni ikkiga ajratish uchun boshqa ilovani tanlang" "Bu amal ilova yoki tashkilotingiz tomonidan taqiqlangan" @@ -130,18 +130,37 @@ "Tezkor sozlamalar" "Vazifalar paneli" "Vazifalar paneli ochiq" - "Vazifalar paneli yopiq" + "Panel va bulutchalar chapda" + "Panel va bulutchalar oʻngda" "Navigatsiya paneli" "Vazifalar paneli doim chiqarilsin" "Navigatsiya rejimini oʻzgartirish" "Vazifalar panelini ajratkich" + "Vazifalar panelini kengaytirish" "Yuqoriga yoki chapga oʻtkazish" "Pastga yoki oʻngga oʻtkazish" - "{count,plural, =1{Yana # ta ilovani chiqarish}other{Yana # ta ilovani chiqarish}}" - "{count,plural, =1{# ta desktop ilovani chiqarish.}other{# ta desktop ilovani chiqarish.}}" + "Ilovani qalqib chiquvchi oynada ochish" + "Oxirgi ilovalar" + "Oxirgi ilovalar roʻyxati" + "{count,plural, =1{boshqa ilova}other{boshqa ilovalar}}" + "Kompyuter" "%1$s va %2$s" + "%1$s, %2$d-element, jami: %3$d ta" + "Chapga varaqlash" + "Oʻngga varaqlash" "Pufak" "Kengaytirish" "%1$s (%2$s)" "%1$s va yana %2$d kishi" + "Chapga siljitish" + "Oʻngga siljitish" + "Hammasini yopish" + "%1$sni yoyish" + "%1$sni yigʻish" + "Chizib qidirish" + "Ilova belgisi" + "Ilova nomi" + "Yopish tugmasi" + "Panelga qadash" + "Vazifalar panelidan yechib olish" diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml index 9bc526faace..26b37702ea2 100644 --- a/quickstep/res/values-vi/strings.xml +++ b/quickstep/res/values-vi/strings.xml @@ -22,11 +22,13 @@ "Ghim" "Dạng tự do" "Máy tính" + "Chuyển sang màn hình ngoài" + "Đóng" + "Máy tính" "Không có mục gần đây nào" "Cài đặt mức sử dụng ứng dụng" "Xóa tất cả" "Ứng dụng gần đây" - "Đã đóng tác vụ" "%1$s, %2$s" "< 1 phút" "Hôm nay còn %1$s" @@ -45,20 +47,20 @@ "Đã bật tính năng Ứng dụng đề xuất" "Tính năng Ứng dụng đề xuất bị tắt" "Ứng dụng dự đoán: %1$s" + "Hướng dẫn thực hiện thao tác bằng cử chỉ" "Xoay thiết bị của bạn" "Vui lòng xoay thiết bị của bạn để hoàn tất hướng dẫn thao tác bằng cử chỉ" "Hãy vuốt từ mép ngoài cùng bên phải hoặc ngoài cùng bên trái" "Hãy vuốt từ mép phải hoặc mép trái tới giữa màn hình rồi nhấc ngón tay ra" "Bạn đã học được cách vuốt từ mép phải để quay lại. Tiếp theo, hãy tìm hiểu cách chuyển đổi ứng dụng." "Bạn đã thực hiện xong cử chỉ quay lại. Tiếp theo, hãy tìm hiểu cách chuyển đổi ứng dụng." - "Bạn đã thực hiện xong cử chỉ quay lại" + "Bạn đã hoàn tất cử chỉ quay lại" "Hãy nhớ không được vuốt quá gần phần dưới cùng của màn hình" "Để thay đổi độ nhạy của cử chỉ quay lại, hãy vào mục Cài đặt" "Vuốt để quay lại" "Để quay lại màn hình gần đây nhất, hãy vuốt từ mép trái hoặc mép phải tới chính giữa màn hình." - "Để quay lại màn hình trước đó, hãy vuốt 2 ngón tay từ cạnh trái hoặc phải vào giữa màn hình." "Quay lại" - "Hãy vuốt từ mép trái hoặc mép phải tới giữa màn hình" + "Vuốt từ mép trái hoặc mép phải tới giữa màn hình" "Hãy vuốt lên từ mép dưới cùng của màn hình" "Hãy nhớ không được dừng trước khi nhấc ngón tay" "Hãy vuốt thẳng lên" @@ -66,7 +68,6 @@ "Bạn đã thực hiện xong cử chỉ chuyển đến Màn hình chính" "Vuốt để chuyển đến Màn hình chính" "Vuốt lên từ cuối màn hình. Cử chỉ này luôn đưa bạn đến Màn hình chính." - "Vuốt 2 ngón tay lên từ cuối màn hình. Cử chỉ này luôn đưa bạn về Màn hình chính." "Chuyển đến màn hình chính" "Vuốt lên từ mép dưới cùng của màn hình" "Tuyệt vời!" @@ -77,7 +78,6 @@ "Bạn đã thực hiện xong cử chỉ chuyển đổi ứng dụng" "Vuốt để chuyển đổi ứng dụng" "Để chuyển đổi ứng dụng, hãy vuốt lên từ cuối màn hình, giữ rồi thả ra." - "Để chuyển đổi giữa các ứng dụng, hãy vuốt 2 ngón tay lên từ cuối màn hình, giữ rồi thả ra." "Chuyển đổi ứng dụng" "Vuốt lên từ mép dưới cùng của màn hình, giữ rồi nhấc ngón tay ra" "Rất tốt!" @@ -99,7 +99,7 @@ "Lưu cặp ứng dụng" "Nhấn vào ứng dụng khác để chia đôi màn hình" "Chọn một ứng dụng khác để dùng chế độ chia đôi màn hình" - "Huỷ" + "Huỷ" "Thoát khỏi lựa chọn chia đôi màn hình" "Chọn một ứng dụng khác để dùng chế độ chia đôi màn hình" "Ứng dụng hoặc tổ chức của bạn không cho phép thực hiện hành động này" @@ -130,18 +130,37 @@ "Cài đặt nhanh" "Thanh tác vụ" "Đã hiện thanh thao tác" - "Đã ẩn thanh thao tác" + "Hiện thanh tác vụ, b.bóng trái" + "Hiện thanh tác vụ, b.bóng phải" "Thanh điều hướng" "Luôn hiện Thanh tác vụ" "Thay đổi chế độ điều hướng" "Đường phân chia Taskbar" + "Trình đơn mục bổ sung trên thanh tác vụ" "Chuyển lên trên cùng/sang bên trái" "Chuyển xuống dưới cùng/sang bên phải" - "{count,plural, =1{Hiện thêm # ứng dụng.}other{Hiện thêm # ứng dụng.}}" - "{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.}}" + "Mở ứng dụng dưới dạng bong bóng" + "Các ứng dụng gần đây" + "Danh sách ứng dụng gần đây" + "{count,plural, =1{ứng dụng khác}other{ứng dụng khác}}" + "Máy tính" "%1$s%2$s" + "%1$s, mục %2$d trong số %3$d" + "Cuộn sang trái" + "Cuộn sang phải" "Bong bóng" "Bong bóng bổ sung" "%1$s từ %2$s" "%1$s%2$d bong bóng khác" + "Di chuyển sang trái" + "Di chuyển sang phải" + "Đóng tất cả" + "mở rộng %1$s" + "thu gọn %1$s" + "Khoanh tròn để tìm kiếm" + "Biểu tượng ứng dụng" + "Tên ứng dụng" + "Nút đóng" + "Ghim vào thanh tác vụ" + "Bỏ ghim khỏi thanh tác vụ" diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml index f6e446e803e..21c212f88ce 100644 --- a/quickstep/res/values-zh-rCN/strings.xml +++ b/quickstep/res/values-zh-rCN/strings.xml @@ -22,11 +22,13 @@ "固定" "自由窗口" "桌面" + "移至外接显示屏" + "关闭" + "桌面设备" "近期没有任何内容" "应用使用设置" "全部清除" "最近用过的应用" - "任务已关闭" "%1$s%2$s)" "不到 1 分钟" "今天还可使用 %1$s" @@ -45,28 +47,27 @@ "已启用应用建议" "已停用应用建议" "预测的应用:%1$s" + "手势导航教程" "请旋转设备" "请旋转设备,完成手势导航教程" "确保从最右侧或最左侧边缘开始滑动" "确保从右侧或左侧边缘滑动到屏幕中间位置后再松开手指" "您已了解如何使用“从右侧向左滑动”手势返回。接下来学习切换应用吧!" "您完成了“返回”手势教程。接下来了解如何切换应用。" - "您完成了“返回”手势" + "您已完成“返回”手势教程" "确保滑动时手的位置不要太靠近屏幕底部" "如要调节“返回”手势的灵敏度,请转到“设置”" "滑动即可返回" "如要返回上一个屏幕,请从屏幕左侧或右侧边缘往屏幕中间滑动。" - "若要返回上一个屏幕,请用两根手指从屏幕左侧或右侧边缘向中间滑动。" "返回" "从屏幕左侧或右侧边缘滑动到中间" "确保从屏幕底部边缘向上滑动" "松开手指前,请勿中途停顿" "确保笔直向上滑动" - "您完成了“转到主屏幕”手势。接下来了解如何返回。" - "您完成了“转到主屏幕”手势" + "您已完成“前往主屏幕”手势教程。接下来学习如何返回。" + "您已完成“前往主屏幕”手势教程" "上滑可转到主屏幕" "从屏幕底部向上滑动,即可随时回到主屏幕。" - "用两根手指从屏幕底部向上滑动,这个手势会一律使您回到主屏幕。" "前往主屏幕" "从屏幕底部向上滑动" "太棒了!" @@ -74,10 +75,9 @@ "尝试按住窗口较长时间,然后再松开手指" "确保笔直向上滑动,然后停住" "您已了解如何使用手势了。如要关闭手势,请前往“设置”。" - "您完成了应用切换手势" + "您已完成“切换应用”手势教程" "滑动即可切换应用" "如需在应用之间切换,请从屏幕底部向上滑动,按住,然后松开。" - "如需在应用之间切换,请从屏幕底部向上滑动,按住,然后松开。" "切换应用" "从屏幕底部向上滑动后按住,然后松开" "恭喜!" @@ -99,7 +99,7 @@ "保存应用组合" "点按另一个应用即可使用分屏" "另外选择一个应用才可使用分屏模式" - "取消" + "取消" "退出分屏选择模式" "另外选择一个应用才可使用分屏模式" "该应用或您所在的单位不允许执行此操作" @@ -130,18 +130,37 @@ "快捷设置" "任务栏" "任务栏已显示" - "任务栏已隐藏" + "已显示任务栏和左侧消息气泡" + "已显示任务栏和右侧消息气泡" "导航栏" "始终显示任务栏" "更改导航模式" "任务栏分隔线" + "任务栏溢出图标" "移到顶部/左侧" "移到底部/右侧" - "{count,plural, =1{显示另外 # 个应用。}other{显示另外 # 个应用。}}" - "{count,plural, =1{显示 # 款桌面应用。}other{显示 # 款桌面应用。}}" + "以气泡的形式打开应用" + "最近打开过的应用" + "“最近打开过的应用”列表" + "{count,plural, =1{多个应用}other{多个应用}}" + "桌面模式" "%1$s%2$s" + "%1$s,第 %2$d 项(共 %3$d 项)" + "向左滚动" + "向右滚动" "气泡框" "溢出式气泡框" "来自“%2$s”的%1$s" "%1$s以及另外 %2$d 个" + "左移" + "右移" + "全部关闭" + "展开“%1$s”" + "收起“%1$s”" + "圈定即搜" + "应用图标" + "应用名称" + "“关闭”按钮" + "固定到任务栏" + "从任务栏取消固定" diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml index b9d8eb765ab..34f8aacb49c 100644 --- a/quickstep/res/values-zh-rHK/strings.xml +++ b/quickstep/res/values-zh-rHK/strings.xml @@ -22,11 +22,13 @@ "固定" "自由形式" "桌面" + "移至外部顯示屏" + "關閉" + "桌面" "最近沒有任何項目" "應用程式使用情況設定" "全部清除" "最近使用的應用程式" - "閂咗工作" "%1$s%2$s" "少於 1 分鐘" "今天剩餘時間:%1$s" @@ -45,6 +47,7 @@ "已啟用應用程式建議" "已停用應用程式建議" "預測應用程式:%1$s" + "手勢導覽教學課程" "旋轉裝置方向" "請旋轉裝置方向以完成手勢導覽教學課程" "請確保從螢幕最右側或最左側邊緣滑動" @@ -56,7 +59,6 @@ "如要變更「返回」手勢的敏感度,請前往「設定」" "滑動即可返回" "如要返回上一個畫面,請從螢幕左側或右側邊緣往中央滑動。" - "如要返回上一個畫面,請用兩指從螢幕左側或右側邊緣往中央滑動。" "返回" "從螢幕左側或右側邊緣往中央滑動" "請確保從螢幕底部邊緣向上滑動" @@ -66,7 +68,6 @@ "你已完成「返回主畫面」手勢的教學課程" "向上滑動即可返回主畫面" "從螢幕底部向上滑動。這個手勢在所有畫面下都可讓你返回主畫面。" - "請用兩指從螢幕底部向上滑動。這個手勢在所有畫面下都可讓你返回主畫面。" "返回主畫面" "從螢幕底部向上滑動" "太好了!" @@ -77,7 +78,6 @@ "你已完成「切換應用程式」手勢的教學課程" "滑動即可切換應用程式" "如要切換應用程式,請從螢幕底部向上滑動並按住,然後放開。" - "如要切換應用程式,請用兩指從螢幕底部向上滑動並按住,然後放開手指。" "切換應用程式" "從螢幕底部向上滑動並按住,然後放開" "做得好!" @@ -99,7 +99,7 @@ "儲存應用程式組合" "輕按其他應用程式以使用分割螢幕" "選擇其他應用程式才能使用分割螢幕" - "取消" + "取消" "退出分割螢幕選取頁面" "選擇其他應用程式才能使用分割螢幕" "應用程式或你的機構不允許此操作" @@ -130,18 +130,37 @@ "快速設定" "工作列" "顯示咗工作列" - "隱藏咗工作列" + "工作列和對話氣泡在左邊顯示" + "工作列和對話氣泡在右邊顯示" "導覽列" "一律顯示工作列" "變更導覽模式" "工作列分隔線" + "工作列溢位" "移至上方/左側" "移至底部/右側" - "{count,plural, =1{顯示另外 # 個應用程式。}other{顯示另外 # 個應用程式。}}" - "{count,plural, =1{顯示 # 個桌面應用程式。}other{顯示 # 個桌面應用程式。}}" + "在小視窗開啟應用程式" + "最近開啟的應用程式" + "「最近開啟的應用程式」清單" + "{count,plural, =1{個其他應用程式}other{個其他應用程式}}" + "桌面" "「%1$s」和「%2$s」" + "%1$s,第 %2$d 個項目,總共有 %3$d 項" + "向左捲動" + "向右捲動" "對話氣泡" "展開式" "%2$s 的「%1$s」通知" "%1$s和其他 %2$d 則通知" + "向左移" + "向右移" + "全部關閉" + "打開%1$s" + "收埋%1$s" + "一圈即搜" + "應用程式圖示" + "應用程式名稱" + "關閉按鈕" + "固定至工作列" + "取消固定至工作列" diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml index 90140cb5879..5244b05d485 100644 --- a/quickstep/res/values-zh-rTW/strings.xml +++ b/quickstep/res/values-zh-rTW/strings.xml @@ -21,12 +21,14 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "固定" "自由形式" - "電腦" + "電腦模式" + "移至外接螢幕" + "關閉" + "電腦模式" "最近沒有任何項目" "應用程式使用情況設定" "全部清除" "最近使用的應用程式" - "工作已關閉" "%1$s (%2$s)" "< 1 分鐘" "今天還能使用 %1$s" @@ -45,6 +47,7 @@ "應用程式建議功能已啟用" "應用程式建議功能已停用" "預測的應用程式:%1$s" + "手勢操作教學課程" "旋轉裝置螢幕方向" "如要完成手勢操作教學課程,請旋轉裝置螢幕方向" "請務必從螢幕最右側或最左側滑動" @@ -56,7 +59,6 @@ "如要變更「返回」手勢的敏感度,請前往「設定」" "滑動即可返回" "如要返回上一個畫面,請從螢幕左側或右側邊緣往中央滑動。" - "如要返回上一個畫面,請用 2 指從螢幕左側或右側邊緣往中央滑動。" "返回" "從螢幕右側或左側往中央滑動" "請務必從螢幕底部向上滑動" @@ -66,7 +68,6 @@ "你已完成「返回主畫面」手勢的教學課程" "使用滑動手勢返回主畫面" "從螢幕底部向上滑動,即可返回主畫面。" - "用 2 指從螢幕底部向上滑動,即可回到主畫面。" "返回主畫面" "從螢幕底部向上滑動" "太棒了!" @@ -77,7 +78,6 @@ "你已完成「切換應用程式」手勢的教學課程" "使用滑動手勢切換應用程式" "如要切換不同的應用程式,請從螢幕底部向上滑動並按住,然後放開手指。" - "如要切換應用程式,請用 2 指從螢幕底部向上滑動並按住,然後放開手指。" "切換應用程式" "從螢幕底部向上滑動並按住,然後放開手指" "非常好!" @@ -99,7 +99,7 @@ "儲存應用程式配對" "輕觸另一個應用程式即可使用分割畫面" "選擇要在分割畫面中使用的另一個應用程式" - "取消" + "取消" "退出分割畫面選擇器" "必須選擇另一個應用程式才能使用分割畫面" "這個應用程式或貴機構不允許執行這個動作" @@ -130,18 +130,37 @@ "快速設定" "工作列" "已顯示工作列" - "已隱藏工作列" + "工作列和對話框顯示在左側" + "工作列和對話框顯示在右側" "導覽列" "一律顯示工作列" "變更操作模式" "工作列分隔線" + "工作列溢位" "移到上方/左側" "移到底部/右側" - "{count,plural, =1{再多顯示 # 個應用程式。}other{再多顯示 # 個應用程式。}}" - "{count,plural, =1{顯示 # 個電腦版應用程式。}other{顯示 # 個電腦版應用程式。}}" + "以泡泡形式開啟應用程式" + "最近開啟的應用程式" + "「最近開啟的應用程式」清單" + "{count,plural, =1{個其他應用程式}other{個其他應用程式}}" + "電腦模式" "「%1$s」和「%2$s」" + "%1$s,第 %2$d 個項目,共 %3$d 項" + "向左捲動" + "向右捲動" "泡泡" "溢位" "「%2$s」的「%1$s」通知" "%1$s和另外 %2$d 則通知" + "向左移" + "向右移" + "全部關閉" + "展開「%1$s」" + "收合「%1$s」" + "畫圈搜尋" + "應用程式圖示" + "應用程式標題" + "關閉按鈕" + "固定到工作列" + "從工作列中取消固定" diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml index 73be445a550..a81fe25676c 100644 --- a/quickstep/res/values-zu/strings.xml +++ b/quickstep/res/values-zu/strings.xml @@ -22,11 +22,13 @@ "Phina" "I-Freeform" "Ideskithophu" + "Hambisa esibonisini sangaphandle" + "Vala" + "Ideskithophu" "Azikho izinto zakamuva" "Izilungiselelo zokusetshenziswa kohlelo lokusebenza" "Sula konke" "Izinhlelo zokusebenza zakamuva" - "Umsebenzi Uvaliwe" "%1$s, %2$s" "< 1 iminithi" "%1$s esele namhlanje" @@ -45,6 +47,7 @@ "Iziphakamiso zohlelo lokusebenza zinikwe amandla" "Iziphakamiso zohlelo lokusebenza zikhutshaziwe" "Uhlelo lokusebenza olubikezelwe: %1$s" + "Okokufundisa Kokuzulazula Kokuthinta" "Zungezisa idivayisi yakho" "Sicela uzungezise idivayisi yakho ukuze uqedele okokufundisa kokufuna ngokuthinta" "Qinisekisa ukuthi uswayipha ukusuka onqenqemeni olukude ngakwesokudla noma olukude ngakwesokunxele" @@ -56,7 +59,6 @@ "Ukuze ushintshe ukuzwela kokuthinta emuva, iya Kumasethingi" "Swayipha ukuze uye emuva" "Ukuze ubuyele emuva esikrinini sokugcina, swapha kusuka emngceleni wesobunxele noma wesokudla kuya phakathi kwesikrini." - "Ukuze ubuyele esikrinini sokugcina, swayipha ngeminwe emi-2 ukusuka kwesokunxele noma kwesokudla emphethweni uye phakathi kwesikrini." "Iya emuva" "Swayipha ukusuka kwesokunxele noma kwesokudla ukuya phakathi kwesikrini" "Qiniseka ukuthi uswayiphela phezulu ukusuka emngceleni ophansi wesikrini" @@ -66,7 +68,6 @@ "Ukuqedile ukuthinta kokuya ekhaya" "Swayipha ukuze uye ekhaya" "Swayiphela phezulu kusuka phansi kwesikrini sakho.Lokhu kuthinta kuhlala kukusa esikrinini sasekhaya." - "Swayiphela phezulu ngeminwe emi-2 kusukela phansi esikrinini. Lesi senzo sihlala sikuyisa esikrinini Sasekhaya." "Iya ekhasini lokuqala" "Swayiphela phezulu ukusuka phansi esikrinini sakho" "Umsebenzi omuhle!" @@ -77,7 +78,6 @@ "Ukuqedile ukuthinta kokushintsha ama-app" "Swayipha ukuze ushintshe ama-app" "Ukuze ushintshe phakathi kwama-app, swayiphela phezulu kusuka ngezansi kwesikrini sakho, bese uyadedela." - "Ukuze ushintshe phakathi kwama-app, swayiphela phezulu ngeminwe emi-2 kusukela phansi esikrinini sakho, ubambe, bese uyakhulula." "Shintsha ama-app" "Swayiphela phezulu ukusuka phansi esikrinini sakho, ubambe, bese uyadedela" "Wenze kahle!" @@ -99,7 +99,7 @@ "Londoloza ukubhangqa i-app" "Thepha enye i-app ukuze usebenzise isikrini sokuhlukanisa" "Khetha enye i-app ukuze usebenzise ukuhlukanisa isikrini" - "Khansela" + "Khansela" "Phuma ekukhetheni ukuhlukaniswa kwesikrini" "Khetha enye i-app ukuze usebenzise ukuhlukanisa isikrini" "Lesi senzo asivunyelwanga uhlelo lokusebenza noma inhlangano yakho" @@ -130,18 +130,37 @@ "Amasethingi Asheshayo" "I-Taskbar" "Ibha yomsebenzi ibonisiwe" - "Ibha yomsebenzi ifihliwe" + "ITaskbar namabhamuza aboniswe kwesokunxele" + "ITaskbar namabhamuza aboniswe kwesokudla" "Ibha yokufuna" "Bonisa i-Taskbar njalo." "Shintsha imodi yokufuna" "Isihlukanisi se-Taskbar" + "Ukuphuphuma Kwetaskbar" "Hamba phezulu/kwesokunxele" "Hamba phansi/kwesokudla" - "{count,plural, =1{Bonisa i-app e-# ngaphezulu.}one{Bonisa ama-app angu-# ngaphezulu.}other{Bonisa ama-app angu-# ngaphezulu.}}" - "{count,plural, =1{Bonisa i-app engu-# yedeskithophu.}one{Bonisa ama-app angu-# wedeskithophu.}other{Bonisa ama-app angu-# wedeskithophu.}}" + "Vula i-app njengebhamuza" + "Ama-app wakamuva" + "Uhlu lwe-app lwakamuva" + "{count,plural, =1{i-app eyengeziwe}one{ama-app engeziwe}other{ama-app engeziwe}}" + "Ideskithophu" "I-%1$s ne-%2$s" + "I-%1$s, into engu-%2$d kwezingu-%3$d" + "Skrolela ngakwesokunxele" + "Skrolela ngakwesokudla" "Ibhamuza" "Ukugcwala kakhulu" "%1$s kusuka ku-%2$s" "%1$s nokunye okungu-%2$d" + "Iya kwesokunxele" + "Iya kwesokudla" + "Chitha konke" + "nweba %1$s" + "goqa %1$s" + "Khethela Ukusesha" + "Isithonjana se-app" + "Isihloko se-app" + "Inkinobho yokuvala" + "Phina kutaskbar" + "Susa ukuphina i-taskbar" diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml index ccc7f180dde..28c0d5c9e82 100644 --- a/quickstep/res/values/attrs.xml +++ b/quickstep/res/values/attrs.xml @@ -28,6 +28,7 @@ + @@ -35,6 +36,11 @@ + + + + + - + #fff #39000000 @@ -31,7 +31,6 @@ #99000000 #EBffffff #99000000 - #646464 #ffffff @@ -76,7 +75,7 @@ #80868b #bdc1c6 - #FFFFFFFF + @android:color/system_neutral1_50 #333333 @@ -94,7 +93,5 @@ #f9ab00 - ?attr/colorAccentPrimary - ?attr/materialColorPrimaryFixedDim - @color/material_color_on_primary_fixed - + @color/materialColorPrimary + \ No newline at end of file diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index fd122103e13..49cee0fa86f 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -23,23 +23,15 @@ com.android.quickstep.logging.StatsLogCompatManager com.android.quickstep.QuickstepTestInformationHandler - com.android.quickstep.util.SystemWindowManagerProxy - com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory com.android.quickstep.InstantAppResolverImpl com.android.launcher3.appprediction.PredictionAppTracker com.android.quickstep.QuickstepProcessInitializer - com.android.launcher3.model.QuickstepModelDelegate com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl com.android.launcher3.taskbar.TaskbarModelCallbacksFactory com.android.launcher3.taskbar.TaskbarViewCallbacksFactory com.android.quickstep.LauncherRestoreEventLoggerImpl - com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl com.android.launcher3.taskbar.TaskbarEduTooltipController - - - - com.android.launcher3.uioverrides.SystemApiWrapper @@ -54,10 +46,17 @@ 23 + 34dp + + + 6 + diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 5c82c991c75..86d44c978ab 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -34,13 +34,12 @@ 0.7 - - 0.46 48dp 44dp + + 52dp 4dp @@ -82,6 +81,15 @@ 44dp 4dp + + 30dp + 18dp + 9dp + 18dp + 16dp + + + 25dp 72dp 1.1 @@ -101,6 +109,10 @@ 24dp 2dp + + 56dp + 2dp + 600dp @@ -109,12 +121,14 @@ 0.0285dp 0.15dp 0.285dp + 0.7dp 1.4dp 36dp 0.5dp 115dp + 36dp 100dp @@ -263,7 +277,7 @@ 24dp - 40dp + 40dp 36sp 14sp @@ -348,19 +362,16 @@ 48dp 48dp 32dp - 6dp 6dp + 5.5dp 64dp 64dp 48dp 1dp 72dp - 4dp - 14dp - 2dp - 2dp - 12dp - 2dp + 2dp + 12dp + 4dp 12dp @@ -371,9 +382,11 @@ 10dp 32dp 8dp + 8dp 400dp 8dp 16dp + 4dp 16dp @@ -422,6 +435,10 @@ 300dp 16dp + 16dp + + + 8dp 60dp @@ -434,6 +451,7 @@ 55dp @dimen/transient_taskbar_stashed_height @dimen/taskbar_stashed_handle_height + @dimen/transient_taskbar_stash_spring_velocity_dp_per_s 9dp @@ -443,23 +461,28 @@ 80dp 1dp - 2dp + + 3dp 90dp + 20dp 32dp 36dp + 28dp 24dp 12dp 16dp 6dp 8dp + @dimen/bubblebar_icon_spacing 12dp 1dp + 12dp - 96dp + @dimen/floating_dismiss_background_size 60dp - 24dp + @dimen/floating_dismiss_icon_size 50dp 548dp 192dp @@ -473,31 +496,49 @@ 24dp 16dp + + 16dp + 4dp + 14dp + 238dp + 276dp + 12dp + 10dp + 1dp + 2dp + 20dp - 4dp + 5dp + 3dp 104dp - 134dp + 136dp 52dp 20dp + 32dp 56dp + 24dp 16dp 16dp - 2dp + 4dp 28dp 16dp 24dp 8dp + 104dp + 360dp + 16dp + 20dp + 36dp + 56dp + 6dp + 16dp + 18dp 48dp - - - - 220dp - diff --git a/quickstep/res/drawable/ic_bubble_dismiss_white.xml b/quickstep/res/values/ids.xml similarity index 51% rename from quickstep/res/drawable/ic_bubble_dismiss_white.xml rename to quickstep/res/values/ids.xml index b15111b821d..c71bb762db2 100644 --- a/quickstep/res/drawable/ic_bubble_dismiss_white.xml +++ b/quickstep/res/values/ids.xml @@ -1,6 +1,6 @@ - - - + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index f3066389d5c..77a6bf9b898 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -23,8 +23,15 @@ Pin Freeform - + Desktop + + Move to external display + + Close + + + Desktop No recent items @@ -41,9 +48,6 @@ Recent apps - - Task Closed - %1$s, %2$s @@ -93,6 +97,9 @@ Predicted app: %1$s + + Gesture Navigation Tutorial + Rotate your device @@ -117,8 +124,6 @@ Swipe to go back To go back to the last screen, swipe from the left or right edge to the middle of the screen. - - To go back to the last screen, swipe with 2 fingers from the left or right edge to the middle of the screen. Go back @@ -137,8 +142,6 @@ Swipe to go home Swipe up from the bottom of your screen. This gesture always takes you to the Home screen. - - Swipe up with 2 fingers from the bottom of the screen. This gesture always takes you to the Home screen. Go home @@ -160,8 +163,6 @@ Swipe to switch apps To switch between apps, swipe up from the bottom of your screen, hold, then release. - - To switch between apps, swipe up with 2 fingers from the bottom of your screen, hold, then release. Switch apps @@ -234,7 +235,7 @@ Tap another app to use split screen Choose another app to use split screen - Cancel + Cancel Exit split screen selection Choose another app to use split screen @@ -296,10 +297,12 @@ Quick Settings Taskbar - + Taskbar shown - - Taskbar hidden + + Taskbar & bubbles left shown + + Taskbar & bubbles right shown Navigation bar @@ -308,27 +311,41 @@ Change navigation mode Taskbar Divider + + Taskbar Overflow Move to top/left Move to bottom/right + + Open app as a bubble + + + Recent apps - + + Recent app list + + {count, plural, - =1{Show # more app.} - other{Show # more apps.} + =1{more app} + other{more apps} } - - {count, plural, - =1{Show # desktop app.} - other{Show # desktop apps.} - } + + Desktop %1$s and %2$s + + %1$s, item %2$d of %3$d + + + Scroll left + + Scroll right @@ -339,4 +356,31 @@ %1$s from %2$s %1$s and %2$d more + + Move left + + Move right + + Dismiss all + + expand %1$s + + collapse %1$s + + + Circle to Search + + + + App icon + + App title + + Close button + + + Pin to taskbar + + Unpin from taskbar diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml index 1da11666313..f8ca8d9adfb 100644 --- a/quickstep/res/values/styles.xml +++ b/quickstep/res/values/styles.xml @@ -124,7 +124,7 @@ - Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW) + * |--> (1) Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW) * | | - * | --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW) + * | --> (1a) App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW) * |--> Divider - * |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW) + * |--> (2) Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW) * | - * --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW) + * --> (2a) App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW) * * We want to animate the Root (grandparent) so that it affects both apps and the divider. To do * this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones, @@ -682,7 +707,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC launchingIconView: AppPairIcon, transitionInfo: TransitionInfo, t: Transaction, - finishCallback: Runnable + finishCallback: Runnable, + windowRadius: Float, ) { // If launching an app pair from Taskbar inside of an app context (no access to Launcher), // use the scale-up animation @@ -691,7 +717,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo, t, finishCallback, - WINDOWING_MODE_MULTI_WINDOW + WINDOWING_MODE_MULTI_WINDOW, ) return } @@ -702,48 +728,28 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // Create an AnimatorSet that will run both shell and launcher transitions together val launchAnimation = AnimatorSet() - var rootCandidate: Change? = null - for (change in transitionInfo.changes) { - val taskInfo: RunningTaskInfo = change.taskInfo ?: continue + val splitRoots: Pair>? = + SplitScreenUtils.extractTopParentAndChildren(transitionInfo) + check(splitRoots != null) { "Could not find split roots" } - // TODO (b/316490565): Replace this logic when SplitBounds is available to - // startAnimation() and we can know the precise taskIds of launching tasks. - // Find a change that has WINDOWING_MODE_MULTI_WINDOW. - if ( - taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW && - (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) - ) { - // Check if it is a left/top app. - val isLeftTopApp = - (dp.isLeftRightSplit && change.endAbsBounds.left == 0) || - (!dp.isLeftRightSplit && change.endAbsBounds.top == 0) - if (isLeftTopApp) { - // Found one! - rootCandidate = change - break - } - } - } - - // If we could not find a proper root candidate, something went wrong. - check(rootCandidate != null) { "Could not find a split root candidate" } + // Will point to change (0) in diagram above + val mainRootCandidate = splitRoots.first + // Will contain changes (1) and (2) in diagram above + val leafRoots: List = splitRoots.second + // Don't rely on DP.isLeftRightSplit because if launcher is portrait apps could still + // launch in landscape if system auto-rotate is enabled and phone is held horizontally + val isLeftRightSplit = leafRoots.all { it.endAbsBounds.top == 0 } // Find the place where our left/top app window meets the divider (used for the // launcher side animation) + val leftTopApp = + leafRoots.single { change -> + (isLeftRightSplit && change.endAbsBounds.left <= 0) || + (!isLeftRightSplit && change.endAbsBounds.top <= 0) + } val dividerPos = - if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right - else rootCandidate.endAbsBounds.bottom - - // Recurse up the tree until parent is null, then we've found our root. - var parentToken: WindowContainerToken? = rootCandidate.parent - while (parentToken != null) { - rootCandidate = transitionInfo.getChange(parentToken) ?: break - parentToken = rootCandidate.parent - } - - // Make sure nothing weird happened, like getChange() returning null. - check(rootCandidate != null) { "Failed to find a root leash" } + if (isLeftRightSplit) leftTopApp.endAbsBounds.right else leftTopApp.endAbsBounds.bottom // Create a new floating view in Launcher, positioned above the launching icon val drawableArea = launchingIconView.iconDrawableArea @@ -758,13 +764,30 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC drawableArea, appIcon1, appIcon2, - dividerPos + dividerPos, ) floatingView.bringToFront() - launchAnimation.play( - getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate) + val iconLaunchValueAnimator = + getIconLaunchValueAnimator( + t, + dp, + finishCallback, + launcher, + floatingView, + mainRootCandidate, + ) + iconLaunchValueAnimator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator, isReverse: Boolean) { + for (c in leafRoots) { + t.setCornerRadius(c.leash, windowRadius) + t.apply() + } + } + } ) + launchAnimation.play(iconLaunchValueAnimator) launchAnimation.start() } @@ -778,7 +801,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo: TransitionInfo, t: Transaction, finishCallback: Runnable, - launchFullscreenIndex: Int + launchFullscreenIndex: Int, ) { // If launching an app pair from Taskbar inside of an app context (no access to Launcher), // use the scale-up animation @@ -787,7 +810,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo, t, finishCallback, - WINDOWING_MODE_FULLSCREEN + WINDOWING_MODE_FULLSCREEN, ) return } @@ -808,8 +831,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC val baseIntent = taskInfo.baseIntent.component?.packageName if ( TransitionUtil.isOpeningType(change.mode) && - taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN && - baseIntent == intentToLaunch + taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN && + baseIntent == intentToLaunch ) { rootCandidate = change } @@ -839,7 +862,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC drawableArea, appIcon, null /*appIcon2*/, - 0 /*dividerPos*/ + 0, /*dividerPos*/ ) floatingView.bringToFront() launchAnimation.play( @@ -854,7 +877,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC finishCallback: Runnable, launcher: QuickstepLauncher, floatingView: FloatingAppPairView, - rootCandidate: Change + rootCandidate: Change, ): ValueAnimator { val progressUpdater = ValueAnimator.ofFloat(0f, 1f) val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet) @@ -868,7 +891,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.LINEAR, valueAnimator.animatedFraction, timings.appRevealStartOffset, - timings.appRevealEndOffset + timings.appRevealEndOffset, ) // Set the alpha of the shell layer (2 apps + divider) @@ -885,8 +908,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( timings.getStagedRectXInterpolator(), timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset - ) + timings.stagedRectSlideEndOffset, + ), ) var mDy = FloatProp( @@ -895,8 +918,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset - ) + timings.stagedRectSlideEndOffset, + ), ) var mScaleX = FloatProp( @@ -905,8 +928,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset - ) + timings.stagedRectSlideEndOffset, + ), ) var mScaleY = FloatProp( @@ -915,8 +938,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset - ) + timings.stagedRectSlideEndOffset, + ), ) override fun onUpdate(percent: Float, initOnly: Boolean) { @@ -951,7 +974,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo: TransitionInfo, t: Transaction, finishCallback: Runnable, - windowingMode: Int + windowingMode: Int, ) { val launchAnimation = AnimatorSet() val progressUpdater = ValueAnimator.ofFloat(0f, 1f) @@ -967,7 +990,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // startAnimation() and we can know the precise taskIds of launching tasks. if ( taskInfo.windowingMode == windowingMode && - (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) + (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) ) { // Found one! rootCandidate = change @@ -994,10 +1017,10 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC val startingScale = 0.34f val startX = screenBounds.left + - ((screenBounds.right - screenBounds.left) * ((1 - startingScale) / 2f)) + ((screenBounds.right - screenBounds.left) * ((1 - startingScale) / 2f)) val startY = screenBounds.top + - ((screenBounds.bottom - screenBounds.top) * ((1 - startingScale) / 2f)) + ((screenBounds.bottom - screenBounds.top) * ((1 - startingScale) / 2f)) val endX = screenBounds.left val endY = screenBounds.top @@ -1037,7 +1060,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC secondTaskId: Int, transitionInfo: TransitionInfo, t: Transaction, - finishCallback: Runnable + finishCallback: Runnable, + cornerRadius: Float, ) { var splitRoot1: Change? = null var splitRoot2: Change? = null @@ -1102,7 +1126,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.LINEAR, valueAnimator.animatedFraction, 0.8f, - 1f + 1f, ) for (leash in openingTargets) { animTransaction.setAlpha(leash, progress) @@ -1115,6 +1139,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC override fun onAnimationStart(animation: Animator) { for (leash in openingTargets) { animTransaction.show(leash).setAlpha(leash, 0.0f) + animTransaction.setCornerRadius(leash, cornerRadius) } animTransaction.apply() } @@ -1134,4 +1159,4 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC container.dragLayer.removeView(view) } } -} \ No newline at end of file +} diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java index b618546576f..3a6d9b027b8 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java @@ -17,6 +17,7 @@ package com.android.quickstep.util; import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.app.animation.Interpolators.STANDARD; import android.view.animation.Interpolator; @@ -38,6 +39,8 @@ public interface SplitAnimationTimings { int TABLET_APP_PAIR_LAUNCH_DURATION = 998; /** Total duration (ms) for launching an app pair from its icon on phones. */ int PHONE_APP_PAIR_LAUNCH_DURATION = 915; + /** Total duration (ms) for fading out desktop tasks in split mode. */ + int DESKTOP_FADE_OUT_DURATION = 200; // Initialize timing classes so they can be accessed statically SplitAnimationTimings TABLET_OVERVIEW_TO_SPLIT = new TabletOverviewToSplitTimings(); @@ -83,6 +86,10 @@ default float getStagedRectSlideEndOffset() { return (float) getStagedRectSlideEnd() / getDuration(); } + default float getDesktopFadeSplitAnimationEndOffset() { + return (float) DESKTOP_FADE_OUT_DURATION / getDuration(); + } + // DEFAULT VALUES: We define default values here so that SplitAnimationTimings can be used // flexibly in animation-running functions, e.g. a single function that handles 2 types of split // animations. The values are not intended to be used, and can safely be removed if refactoring @@ -105,6 +112,10 @@ default float getStagedRectSlideEndOffset() { default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; } default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; } + default Interpolator getDesktopTaskFadeInterpolator() { + return LINEAR; + } + // Defaults for HomeToSplit default float getScrimFadeInStartOffset() { return 0; } default float getScrimFadeInEndOffset() { return 0; } @@ -120,5 +131,9 @@ default float getStagedRectSlideEndOffset() { default float getAppRevealEndOffset() { return 0; } default Interpolator getCellSplitInterpolator() { return LINEAR; } default Interpolator getIconFadeInterpolator() { return LINEAR; } + + default Interpolator getDesktopTaskScaleInterpolator() { + return STANDARD; + } } diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt index 38bbe601b65..4005c5a8130 100644 --- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt +++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt @@ -16,43 +16,78 @@ package com.android.quickstep.util +import android.util.Log +import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_TO_FRONT +import android.window.TransitionInfo +import android.window.TransitionInfo.Change +import android.window.TransitionInfo.FLAG_FIRST_CUSTOM import com.android.launcher3.util.SplitConfigurationOptions -import com.android.wm.shell.util.SplitBounds +import com.android.wm.shell.shared.split.SplitBounds +import java.lang.IllegalStateException class SplitScreenUtils { companion object { + private const val TAG = "SplitScreenUtils" + // TODO(b/254378592): Remove these methods when the two classes are reunited /** Converts the shell version of SplitBounds to the launcher version */ @JvmStatic - fun convertShellSplitBoundsToLauncher( - shellSplitBounds: SplitBounds? - ): SplitConfigurationOptions.SplitBounds? { - return if (shellSplitBounds == null) { - null - } else { - SplitConfigurationOptions.SplitBounds( - shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds, - shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId, - shellSplitBounds.snapPosition - ) + fun convertShellSplitBoundsToLauncher(shellSplitBounds: SplitBounds) = + SplitConfigurationOptions.SplitBounds( + shellSplitBounds.leftTopBounds, + shellSplitBounds.rightBottomBounds, + shellSplitBounds.leftTopTaskId, + shellSplitBounds.rightBottomTaskId, + shellSplitBounds.snapPosition, + ) + + /** + * Given a TransitionInfo, generates the tree structure for those changes and extracts out + * the top most root and it's two immediate children. Changes can be provided in any order. + * + * @return a [Pair] where first -> top most split root, second -> [List] of 2, + * leftTop/bottomRight stage roots + */ + fun extractTopParentAndChildren( + transitionInfo: TransitionInfo + ): Pair>? { + val parentToChildren = mutableMapOf>() + val hasParent = mutableSetOf() + // filter out anything that isn't opening and the divider + val taskChanges: List = + transitionInfo.changes + .filter { change -> + (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) && + change.flags < FLAG_FIRST_CUSTOM + } + .toList() + + // 1. Build Parent-Child Relationships + for (change in taskChanges) { + // TODO (b/316490565): Replace this logic when SplitBounds is available to + // startAnimation() and we can know the precise taskIds of launching tasks. + change.parent?.let { parent -> + parentToChildren + .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() } + .add(change) + hasParent.add(change) + } } - } - /** Converts the launcher version of SplitBounds to the shell version */ - @JvmStatic - fun convertLauncherSplitBoundsToShell( - launcherSplitBounds: SplitConfigurationOptions.SplitBounds? - ): SplitBounds? { - return if (launcherSplitBounds == null) { - null + // 2. Find Top Parent + val topParent = taskChanges.firstOrNull { it !in hasParent } + + // 3. Extract Immediate Children + return if (topParent != null) { + val immediateChildren = parentToChildren.getOrDefault(topParent, emptyList()) + if (immediateChildren.size != 2) { + throw IllegalStateException("incorrect split stage root size") + } + Pair(topParent, immediateChildren) } else { - SplitBounds( - launcherSplitBounds.leftTopBounds, - launcherSplitBounds.rightBottomBounds, - launcherSplitBounds.leftTopTaskId, - launcherSplitBounds.rightBottomTaskId, - launcherSplitBounds.snapPosition - ) + Log.w(TAG, "No top parent found") + null } } } diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index bcfcd8eb260..08f25521574 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -16,7 +16,6 @@ package com.android.quickstep.util; -import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTED_SECOND_APP; @@ -35,8 +34,8 @@ 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.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; +import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -53,16 +52,13 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.util.Pair; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.View; import android.window.IRemoteTransitionFinishedCallback; import android.window.RemoteTransition; import android.window.RemoteTransitionStub; @@ -75,12 +71,12 @@ import com.android.launcher3.R; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.apppairs.AppPairIcon; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconProvider; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager; +import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.uioverrides.QuickstepLauncher; @@ -94,23 +90,22 @@ import com.android.quickstep.RecentsModel; import com.android.quickstep.SplitSelectionListener; import com.android.quickstep.SystemUiProxy; -import com.android.quickstep.TaskAnimationManager; 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.systemui.shared.system.QuickStepContract; +import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.splitscreen.ISplitSelectListener; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; /** @@ -121,7 +116,6 @@ public class SplitSelectStateController { private static final String TAG = "SplitSelectStateCtor"; private RecentsViewContainer mContainer; - private final Handler mHandler; private final RecentsModel mRecentTasksModel; @Nullable private Runnable mActivityBackCallback; @@ -154,9 +148,10 @@ public class SplitSelectStateController { /** * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be - * set for all launches. + * set for all launches. Used in conjunction with {@link #mLaunchingViewCuj} below. */ private int mLaunchCuj = -1; + private View mLaunchingViewCuj; private FloatingTaskView mFirstFloatingTaskView; private SplitInstructionsView mSplitInstructionsView; @@ -168,10 +163,12 @@ public class SplitSelectStateController { */ private Pair mSessionInstanceIds; + private boolean mIsDestroyed = false; + private final BackPressHandler mSplitBackHandler = new BackPressHandler() { @Override public boolean canHandleBack() { - return FeatureFlags.enableSplitContextually() && isSplitSelectActive(); + return isSplitSelectActive(); } @Override @@ -186,12 +183,11 @@ public void onBackInvoked() { } }; - public SplitSelectStateController(RecentsViewContainer container, Handler handler, - StateManager stateManager, DepthController depthController, - StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel, - Runnable activityBackCallback) { + public SplitSelectStateController(RecentsViewContainer container, + StateManager stateManager, DepthController depthController, + StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel, + Runnable activityBackCallback) { mContainer = container; - mHandler = handler; mStatsLogManager = statsLogManager; mSystemUiProxy = systemUiProxy; mStateManager = stateManager; @@ -199,12 +195,13 @@ public SplitSelectStateController(RecentsViewContainer container, Handler handle mRecentTasksModel = recentsModel; mActivityBackCallback = activityBackCallback; mSplitAnimationController = new SplitAnimationController(this); - mAppPairsController = new AppPairsController(mContainer.asContext(), this, statsLogManager); + mAppPairsController = new AppPairsController(mContainer, this, statsLogManager); mSplitSelectDataHolder = new SplitSelectDataHolder(mContainer.asContext()); } public void onDestroy() { mContainer = null; + mIsDestroyed = true; mActivityBackCallback = null; mAppPairsController.onDestroy(); mSplitSelectDataHolder.onDestroy(); @@ -219,8 +216,8 @@ public void onDestroy() { * @param intent will be ignored if @param alreadyRunningTask is set */ public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition, - @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, - int alreadyRunningTask) { + @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, + int alreadyRunningTask) { mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent, alreadyRunningTask); createAndLogInstanceIdsForSession(); @@ -231,8 +228,8 @@ public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int sta * running app. */ public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info, - @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, - StatsLogManager.EventEnum splitEvent) { + @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, + StatsLogManager.EventEnum splitEvent) { mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent); createAndLogInstanceIdsForSession(); } @@ -247,7 +244,7 @@ public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info, * tasks (i.e. searching for a running pair of tasks.) */ public void findLastActiveTasksAndRunCallback(@Nullable List componentKeys, - boolean findExactPairMatch, Consumer callback) { + boolean findExactPairMatch, Consumer callback) { mRecentTasksModel.getTasks(taskGroups -> { if (componentKeys == null || componentKeys.isEmpty()) { callback.accept(new Task[]{}); @@ -262,7 +259,7 @@ public void findLastActiveTasksAndRunCallback(@Nullable List compo GroupTask groupTask = taskGroups.get(i); if (isInstanceOfAppPair( groupTask, componentKeys.get(0), componentKeys.get(1))) { - lastActiveTasks[0] = groupTask.task1; + lastActiveTasks[0] = ((SplitTask) groupTask).getTopLeftTask(); break; } } @@ -275,17 +272,15 @@ public void findLastActiveTasksAndRunCallback(@Nullable List compo // Loop through tasks in reverse, since they are ordered with recent tasks last for (int j = taskGroups.size() - 1; j >= 0; j--) { GroupTask groupTask = taskGroups.get(j); - Task task1 = groupTask.task1; - // Don't add duplicate Tasks - if (isInstanceOfComponent(task1, key) - && !Arrays.asList(lastActiveTasks).contains(task1)) { - lastActiveTask = task1; - break; + // Account for desktop cases where there can be N tasks in the group + for (Task task : groupTask.getTasks()) { + if (isInstanceOfComponent(task, key) + && !Arrays.asList(lastActiveTasks).contains(task)) { + lastActiveTask = task; + break; + } } - Task task2 = groupTask.task2; - if (isInstanceOfComponent(task2, key) - && !Arrays.asList(lastActiveTasks).contains(task2)) { - lastActiveTask = task2; + if (lastActiveTask != null) { break; } } @@ -317,12 +312,16 @@ public boolean isInstanceOfComponent(@Nullable Task task, @NonNull ComponentKey * both permutations because task order is not guaranteed in GroupTasks. */ public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1, - @NonNull ComponentKey componentKey2) { - return ((isInstanceOfComponent(groupTask.task1, componentKey1) - && isInstanceOfComponent(groupTask.task2, componentKey2)) - || - (isInstanceOfComponent(groupTask.task1, componentKey2) - && isInstanceOfComponent(groupTask.task2, componentKey1))); + @NonNull ComponentKey componentKey2) { + if (groupTask instanceof SplitTask splitTask) { + return ((isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey1) + && isInstanceOfComponent(splitTask.getBottomRightTask(), componentKey2)) + || + (isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey2) + && isInstanceOfComponent(splitTask.getBottomRightTask(), + componentKey1))); + } + return false; } /** @@ -351,7 +350,7 @@ private void dispatchOnSplitSelectionExit() { * animations are complete. */ public void launchSplitTasks(@PersistentSnapPosition int snapPosition, - @Nullable Consumer callback) { + @Nullable Consumer callback) { launchTasks(callback, false /* freezeTaskList */, snapPosition, mSessionInstanceIds.first); mStatsLogManager.logger() @@ -372,7 +371,7 @@ public void launchSplitTasks(@PersistentSnapPosition int snapPosition) { * A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio. */ public void launchSplitTasks(@Nullable Consumer callback) { - launchSplitTasks(SNAP_TO_50_50, callback); + launchSplitTasks(SNAP_TO_2_50_50, callback); } /** @@ -380,7 +379,7 @@ public void launchSplitTasks(@Nullable Consumer callback) { * ratio and no callback. */ public void launchSplitTasks() { - launchSplitTasks(SNAP_TO_50_50, null); + launchSplitTasks(SNAP_TO_2_50_50, null); } /** @@ -436,7 +435,7 @@ public void setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent) { * foreground (quickswitch, launching previous pairs from overview) */ public void launchTasks(@Nullable Consumer callback, boolean freezeTaskList, - @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) { + @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "launchSplitTasks"); final ActivityOptions options1 = ActivityOptions.makeBasic(); @@ -459,77 +458,41 @@ public void launchTasks(@Nullable Consumer callback, boolean freezeTask Bundle optionsBundle = options1.toBundle(); Bundle extrasBundle = new Bundle(1); extrasBundle.putParcelable(KEY_EXTRA_WIDGET_INTENT, widgetIntent); - if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId, - secondTaskId, callback, "LaunchSplitPair"); - switch (launchData.getSplitLaunchType()) { - case SPLIT_TASK_TASK -> - mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, - null /* options2 */, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); - - case SPLIT_TASK_PENDINGINTENT -> - mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle, - firstTaskId, extrasBundle, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); - - case SPLIT_TASK_SHORTCUT -> - mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle, - firstTaskId, null /*options2*/, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); - - case SPLIT_PENDINGINTENT_TASK -> - mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle, - secondTaskId, null /*options2*/, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); - - case SPLIT_PENDINGINTENT_PENDINGINTENT -> - mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut, - optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle, - initialStagePosition, snapPosition, remoteTransition, - shellInstanceId); - - case SPLIT_SHORTCUT_TASK -> - mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle, - secondTaskId, null /*options2*/, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); - } - } else { - final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId, - callback); - switch (launchData.getSplitLaunchType()) { - case SPLIT_TASK_TASK -> - mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, - secondTaskId, null /* options2 */, initialStagePosition, - snapPosition, adapter, shellInstanceId); - - case SPLIT_TASK_PENDINGINTENT -> - mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI, - secondUserId, optionsBundle, firstTaskId, null /*options2*/, - initialStagePosition, snapPosition, adapter, shellInstanceId); - - case SPLIT_TASK_SHORTCUT -> - mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut, - optionsBundle, firstTaskId, null /*options2*/, initialStagePosition, - snapPosition, adapter, shellInstanceId); - - case SPLIT_PENDINGINTENT_TASK -> - mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId, - optionsBundle, secondTaskId, null /*options2*/, - initialStagePosition, snapPosition, adapter, shellInstanceId); - - case SPLIT_PENDINGINTENT_PENDINGINTENT -> - mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId, - firstShortcut, optionsBundle, secondPI, secondUserId, - secondShortcut, null /*options2*/, initialStagePosition, - snapPosition, adapter, shellInstanceId); - - case SPLIT_SHORTCUT_TASK -> - mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut, - optionsBundle, secondTaskId, null /*options2*/, - initialStagePosition, snapPosition, adapter, shellInstanceId); - } + final RemoteTransition remoteTransition = getRemoteTransition(firstTaskId, + secondTaskId, callback, "LaunchSplitPair"); + switch (launchData.getSplitLaunchType()) { + case SPLIT_TASK_TASK -> + mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, + null /* options2 */, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); + + case SPLIT_TASK_PENDINGINTENT -> + mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle, + firstTaskId, extrasBundle, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); + + case SPLIT_TASK_SHORTCUT -> + mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle, + firstTaskId, null /*options2*/, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); + + case SPLIT_PENDINGINTENT_TASK -> + mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle, + secondTaskId, null /*options2*/, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); + + case SPLIT_PENDINGINTENT_PENDINGINTENT -> + mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut, + optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle, + initialStagePosition, snapPosition, remoteTransition, + shellInstanceId); + + case SPLIT_SHORTCUT_TASK -> + mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle, + secondTaskId, null /*options2*/, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); } + } /** @@ -540,9 +503,9 @@ public void launchTasks(@Nullable Consumer callback, boolean freezeTask * GroupedTaskView, int, int, int, Consumer, boolean, int, RemoteTransition)} */ public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, - int firstTaskId, int secondTaskId, @StagePosition int stagePosition, - Consumer callback, boolean freezeTaskList, - @PersistentSnapPosition int snapPosition) { + int firstTaskId, int secondTaskId, @StagePosition int stagePosition, + Consumer callback, boolean freezeTaskList, + @PersistentSnapPosition int snapPosition) { launchExistingSplitPair( groupedTaskView, firstTaskId, @@ -565,9 +528,9 @@ public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, * NOTE: This is not to be used to launch AppPairs. */ public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, - int firstTaskId, int secondTaskId, @StagePosition int stagePosition, - Consumer callback, boolean freezeTaskList, - @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) { + int firstTaskId, int secondTaskId, @StagePosition int stagePosition, + Consumer callback, boolean freezeTaskList, + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) { mLaunchingTaskView = groupedTaskView; final ActivityOptions options1 = ActivityOptions.makeBasic(); if (freezeTaskList) { @@ -575,20 +538,13 @@ public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, } Bundle optionsBundle = options1.toBundle(); - if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - final RemoteTransition transition = remoteTransition == null - ? getShellRemoteTransition( - firstTaskId, secondTaskId, callback, "LaunchExistingPair") - : remoteTransition; - mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, - stagePosition, snapPosition, transition, null /*shellInstanceId*/); - } else { - final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, - secondTaskId, callback); - mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, secondTaskId, - null /* options2 */, stagePosition, snapPosition, adapter, - null /*shellInstanceId*/); - } + final RemoteTransition transition = remoteTransition == null + ? getRemoteTransition( + firstTaskId, secondTaskId, callback, "LaunchExistingPair") + : remoteTransition; + mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, + stagePosition, snapPosition, transition, null /*shellInstanceId*/); + } /** @@ -614,44 +570,24 @@ public void launchInitialAppFullscreen(Consumer callback) { ActivityThread.currentActivityThread().getApplicationThread(), "LaunchAppFullscreen"); InstanceId instanceId = mSessionInstanceIds.first; - if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - switch (launchData.getSplitLaunchType()) { - case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId, - optionsBundle, secondTaskId, null /* options2 */, initialStagePosition, - SNAP_TO_50_50, remoteTransition, instanceId); - case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI, - firstUserId, optionsBundle, secondTaskId, null /*options2*/, - initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId); - case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask( - initialShortcut, optionsBundle, firstTaskId, null /* options2 */, - initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId); - } - } else { - final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, - secondTaskId, callback); - switch (launchData.getSplitLaunchType()) { - case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasksWithLegacyTransition( - firstTaskId, optionsBundle, secondTaskId, null /* options2 */, - initialStagePosition, SNAP_TO_50_50, adapter, instanceId); - case SPLIT_SINGLE_INTENT_FULLSCREEN -> - mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId, - optionsBundle, secondTaskId, null /*options2*/, - initialStagePosition, SNAP_TO_50_50, adapter, instanceId); - case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> - mSystemUiProxy.startShortcutAndTaskWithLegacyTransition( - initialShortcut, optionsBundle, firstTaskId, null /* options2 */, - initialStagePosition, SNAP_TO_50_50, adapter, instanceId); - } + switch (launchData.getSplitLaunchType()) { + case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId, + optionsBundle, secondTaskId, null /* options2 */, initialStagePosition, + SNAP_TO_2_50_50, remoteTransition, instanceId); + case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI, + firstUserId, optionsBundle, secondTaskId, null /*options2*/, + initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId); + case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask( + initialShortcut, optionsBundle, firstTaskId, null /* options2 */, + initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId); } } /** * Init {@code SplitFromDesktopController} */ - public void initSplitFromDesktopController(QuickstepLauncher launcher, - OverviewComponentObserver overviewComponentObserver) { - initSplitFromDesktopController( - new SplitFromDesktopController(launcher, overviewComponentObserver)); + public void initSplitFromDesktopController(QuickstepLauncher launcher) { + initSplitFromDesktopController(new SplitFromDesktopController(launcher)); } @VisibleForTesting @@ -659,22 +595,14 @@ void initSplitFromDesktopController(SplitFromDesktopController controller) { mSplitFromDesktopController = controller; } - private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId, - @Nullable Consumer callback, String transitionName) { + private RemoteTransition getRemoteTransition(int firstTaskId, int secondTaskId, + @Nullable Consumer callback, String transitionName) { final RemoteSplitLaunchTransitionRunner animationRunner = new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); return new RemoteTransition(animationRunner, ActivityThread.currentActivityThread().getApplicationThread(), transitionName); } - private RemoteAnimationAdapter getLegacyRemoteAdapter(int firstTaskId, int secondTaskId, - @Nullable Consumer callback) { - final RemoteSplitLaunchAnimationRunner animationRunner = - new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback); - return new RemoteAnimationAdapter(animationRunner, 300, 150, - ActivityThread.currentActivityThread().getApplicationThread()); - } - /** * Will initialize {@link #mSessionInstanceIds} if null and log the first split event from * {@link #mSplitSelectDataHolder} @@ -727,7 +655,12 @@ public SplitAnimationController getSplitAnimationController() { return mSplitAnimationController; } - public void setLaunchingCuj(int launchCuj) { + /** + * Set params to invoke a trace session for the given view and CUJ when we begin animating the + * split launch AFTER we get a response from Shell. + */ + public void setLaunchingCuj(View launchingView, int launchCuj) { + mLaunchingViewCuj = launchingView; mLaunchCuj = launchCuj; } @@ -741,16 +674,16 @@ private class RemoteSplitLaunchTransitionRunner extends RemoteTransitionStub { private Consumer mFinishCallback; RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, - @Nullable Consumer callback) { + @Nullable Consumer callback) { mInitialTaskId = initialTaskId; mSecondTaskId = secondTaskId; mFinishCallback = callback; } @Override - public void startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishedCallback) { + public void startAnimation(IBinder transition, TransitionInfo transitionInfo, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishedCallback) { final Runnable finishAdapter = () -> { try { finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); @@ -765,6 +698,9 @@ public void startAnimation(IBinder transition, TransitionInfo info, && mLaunchingTaskView.getRecentsView() != null && mLaunchingTaskView.getRecentsView().isTaskViewVisible( mLaunchingTaskView); + if (mLaunchingViewCuj != null && mLaunchCuj != -1) { + InteractionJankMonitorWrapper.begin(mLaunchingViewCuj, mLaunchCuj); + } mSplitAnimationController.playSplitLaunchAnimation( shouldLaunchFromTaskView ? mLaunchingTaskView : null, mLaunchingIconView, @@ -775,10 +711,11 @@ public void startAnimation(IBinder transition, TransitionInfo info, null /* nonApps */, mStateManager, mDepthController, - info, t, () -> { + transitionInfo, t, () -> { finishAdapter.run(); cleanup(true /*success*/); - }); + }, + QuickStepContract.getWindowCornerRadius(mContainer.asContext())); }); } @@ -805,60 +742,15 @@ private void cleanup(boolean success) { } /** - * LEGACY - * Remote animation runner for animation to launch an app. - */ - private class RemoteSplitLaunchAnimationRunner extends RemoteAnimationRunnerCompat { - - private final int mInitialTaskId; - private final int mSecondTaskId; - private final Consumer mSuccessCallback; - - RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, - @Nullable Consumer successCallback) { - mInitialTaskId = initialTaskId; - mSecondTaskId = secondTaskId; - mSuccessCallback = successCallback; - } - - @Override - public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - Runnable finishedCallback) { - postAsyncCallback(mHandler, - () -> mSplitAnimationController.playSplitLaunchAnimation(mLaunchingTaskView, - mLaunchingIconView, mInitialTaskId, mSecondTaskId, apps, wallpapers, - nonApps, mStateManager, mDepthController, null /* info */, null /* t */, - () -> { - finishedCallback.run(); - if (mSuccessCallback != null) { - mSuccessCallback.accept(true); - } - resetState(); - })); - } - - @Override - public void onAnimationCancelled() { - postAsyncCallback(mHandler, () -> { - if (mSuccessCallback != null) { - // Launching legacy tasks while recents animation is running will always cause - // onAnimationCancelled to be called (should be fixed w/ shell transitions?) - mSuccessCallback.accept(mRecentsAnimationRunning); - } - resetState(); - }); - } - } - - /** - * To be called whenever we exit split selection state. If - * {@link FeatureFlags#enableSplitContextually()} is set, this should be the + * To be called whenever we exit split selection state. This should be the * central way split is getting reset, which should then go through the callbacks to reset * other state. */ public void resetState() { mSplitSelectDataHolder.resetState(); + if (!mIsDestroyed) { + mContainer.getOverviewPanel().resetDesktopTaskFromSplitSelectState(); + } dispatchOnSplitSelectionExit(); mRecentsAnimationRunning = false; mLaunchingTaskView = null; @@ -873,6 +765,7 @@ public void resetState() { InteractionJankMonitorWrapper.end(mLaunchCuj); } mLaunchCuj = -1; + mLaunchingViewCuj = null; if (mSessionInstanceIds != null) { mStatsLogManager.logger() @@ -957,32 +850,24 @@ public class SplitFromDesktopController { private final int mSplitPlaceholderSize; private final int mSplitPlaceholderInset; private ActivityManager.RunningTaskInfo mTaskInfo; - private ISplitSelectListener mSplitSelectListener; + private DesktopSplitSelectListenerImpl mSplitSelectListener; private Drawable mAppIcon; - public SplitFromDesktopController(QuickstepLauncher launcher, - OverviewComponentObserver overviewComponentObserver) { + public SplitFromDesktopController(QuickstepLauncher launcher) { mLauncher = launcher; - mOverviewComponentObserver = overviewComponentObserver; + mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(launcher); mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( R.dimen.split_placeholder_size); mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize( R.dimen.split_placeholder_inset); - mSplitSelectListener = new ISplitSelectListener.Stub() { - @Override - public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo, - int splitPosition, Rect taskBounds) { - MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition, - taskBounds)); - return true; - } - }; + mSplitSelectListener = new DesktopSplitSelectListenerImpl(this); SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener); } void onDestroy() { SystemUiProxy.INSTANCE.get(mLauncher).unregisterSplitSelectListener( mSplitSelectListener); + mSplitSelectListener.release(); mSplitSelectListener = null; } @@ -993,32 +878,34 @@ void onDestroy() { * @param taskBounds the bounds of the task, used for {@link FloatingTaskView} animation */ public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, - int splitPosition, Rect taskBounds) { + int splitPosition, Rect taskBounds) { mTaskInfo = taskInfo; String packageName = mTaskInfo.realActivity.getPackageName(); PackageManager pm = mLauncher.getApplicationContext().getPackageManager(); IconProvider provider = new IconProvider(mLauncher.getApplicationContext()); try { mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity, - PackageManager.ComponentInfoFlags.of(0))); + PackageManager.ComponentInfoFlags.of(0))); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Package not found: " + packageName, e); } RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( - SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()), - false /* allowMinimizeSplitScreen */); + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())); DesktopSplitRecentsAnimationListener listener = new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds); - MAIN_EXECUTOR.execute(() -> { - callbacks.addListener(listener); - UI_HELPER_EXECUTOR.execute( - // Transition from app to enter stage split in launcher with - // recents animation. - () -> ActivityManagerWrapper.getInstance().startRecentsActivity( - mOverviewComponentObserver.getOverviewIntent(), - SystemClock.uptimeMillis(), callbacks, null, null)); + callbacks.addListener(listener); + UI_HELPER_EXECUTOR.execute(() -> { + // Transition from app to enter stage split in launcher with recents animation + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); + options.setTransientLaunch(); + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) + .startRecentsActivity( + mOverviewComponentObserver.getOverviewIntent(), options, + callbacks, false /* useSyntheticRecentsTransition */); }); } @@ -1035,11 +922,11 @@ private class DesktopSplitRecentsAnimationListener implements @Override public void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targets) { + RecentsAnimationTargets targets, TransitionInfo transitionInfo) { StatsLogManager.LauncherEvent launcherDesktopSplitEvent = mSplitPosition == STAGE_POSITION_BOTTOM_OR_RIGHT ? - LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM : - LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; + LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM : + LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; setInitialTaskSelect(mTaskInfo, mSplitPosition, null, launcherDesktopSplitEvent); @@ -1054,6 +941,10 @@ public void onRecentsAnimationStart(RecentsAnimationController controller, mLauncher, mLauncher.getDragLayer(), null /* thumbnail */, mAppIcon, new RectF()); + floatingTaskView.setOnClickListener(view -> + getSplitAnimationController() + .playAnimPlaceholderToFullscreen(mContainer, view, + Optional.of(() -> resetState()))); floatingTaskView.setAlpha(1); floatingTaskView.addStagingAnimation(anim, mTaskBounds, mTempRect, false /* fadeWithThumbnail */, true /* isStagedTask */); @@ -1062,7 +953,16 @@ public void onRecentsAnimationStart(RecentsAnimationController controller, anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - controller.finish(true /* toRecents */, null /* onFinishComplete */, + controller.finish( + true /* toRecents */, + () -> { + LauncherTaskbarUIController controller = + mLauncher.getTaskbarUIController(); + if (controller != null) { + controller.updateTaskbarLauncherStateGoingHome(); + } + + }, false /* sendUserLeaveHint */); } @Override @@ -1070,9 +970,49 @@ public void onAnimationEnd(Animator animation) { SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) .onDesktopSplitSelectAnimComplete(mTaskInfo); } + @Override + public void onAnimationCancel(Animator animation) { + mLauncher.getDragLayer().removeView(floatingTaskView); + getSplitAnimationController() + .removeSplitInstructionsView(mLauncher); + resetState(); + } }); + anim.add(getSplitAnimationController() + .getShowSplitInstructionsAnim(mLauncher).buildAnim()); anim.buildAnim().start(); } } } -} \ No newline at end of file + + /** + * Wrapper for the ISplitSelectListener stub to prevent lingering references to the launcher + * activity via the controller. + */ + private static class DesktopSplitSelectListenerImpl extends ISplitSelectListener.Stub { + + private SplitFromDesktopController mController; + + DesktopSplitSelectListenerImpl(@NonNull SplitFromDesktopController controller) { + mController = controller; + } + + /** + * Clears any references to the controller. + */ + void release() { + mController = null; + } + + @Override + public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo, + int splitPosition, Rect taskBounds) { + MAIN_EXECUTOR.execute(() -> { + if (mController != null) { + mController.enterSplitSelect(taskInfo, splitPosition, taskBounds); + } + }); + return true; + } + } +} diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java index 4962367bd2f..d3390b4ed9d 100644 --- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java +++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java @@ -16,6 +16,7 @@ package com.android.quickstep.util; +import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.Animator; @@ -48,8 +49,11 @@ import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.quickstep.views.FloatingTaskView; import com.android.quickstep.views.RecentsView; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; +import java.util.Collections; + /** Handles when the stage split lands on the home screen. */ public class SplitToWorkspaceController { @@ -86,7 +90,7 @@ public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pend MODEL_EXECUTOR.execute(() -> { PackageItemInfo infoInOut = new PackageItemInfo(pendingIntent.getCreatorPackage(), pendingIntent.getCreatorUserHandle()); - mIconCache.getTitleAndIconForApp(infoInOut, false); + mIconCache.getTitleAndIconForApp(infoInOut, DEFAULT_LOOKUP_FLAG); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); view.post(() -> { @@ -133,10 +137,20 @@ public boolean handleSecondAppSelectionForSplit(View view) { // Use Launcher's default click handler return false; } - - mController.setSecondTask(intent, user, (ItemInfo) tag); - - startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher)); + // Check for background task matching this tag; if we find one, set second task + // via task instead of intent so the bounds and windowing mode will be corrected. + mController.findLastActiveTasksAndRunCallback( + Collections.singletonList(((ItemInfo) tag).getComponentKey()), + false /* findExactPairMatch */, + foundTasks -> { + Task foundTask = foundTasks[0]; + if (foundTask != null) { + mController.setSecondTask(foundTask, (ItemInfo) tag); + } else { + mController.setSecondTask(intent, user, (ItemInfo) tag); + } + startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher)); + }); return true; } @@ -205,6 +219,6 @@ private void cleanUp() { } private boolean shouldIgnoreSecondSplitLaunch() { - return !FeatureFlags.enableSplitContextually() || !mController.isSplitSelectActive(); + return !mController.isSplitSelectActive(); } } diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java index 5e42b9001bd..0e2713981d4 100644 --- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java +++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java @@ -16,7 +16,6 @@ package com.android.quickstep.util; -import static com.android.launcher3.config.FeatureFlags.enableSplitContextually; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; @@ -27,9 +26,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.graphics.Rect; import android.graphics.RectF; -import android.os.SystemClock; +import android.window.TransitionInfo; import androidx.annotation.BinderThread; @@ -40,7 +40,6 @@ 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.SystemUiProxy; @@ -55,20 +54,16 @@ public class SplitWithKeyboardShortcutController { private final QuickstepLauncher mLauncher; private final SplitSelectStateController mController; - private final RecentsAnimationDeviceState mDeviceState; private final OverviewComponentObserver mOverviewComponentObserver; private final int mSplitPlaceholderSize; private final int mSplitPlaceholderInset; - public SplitWithKeyboardShortcutController(QuickstepLauncher launcher, - SplitSelectStateController controller, - OverviewComponentObserver overviewComponentObserver, - RecentsAnimationDeviceState deviceState) { + public SplitWithKeyboardShortcutController( + QuickstepLauncher launcher, SplitSelectStateController controller) { mLauncher = launcher; mController = controller; - mDeviceState = deviceState; - mOverviewComponentObserver = overviewComponentObserver; + mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(launcher); mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( R.dimen.split_placeholder_size); @@ -78,49 +73,48 @@ public SplitWithKeyboardShortcutController(QuickstepLauncher launcher, @BinderThread public void enterStageSplit(boolean leftOrTop) { - if (!enableSplitContextually() || - // Do not enter stage split from keyboard shortcuts if the user is already in split - TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) { + if (TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) { + // Do not enter stage split from keyboard shortcuts if the user is already in split return; } RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( - SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()), - false /* allowMinimizeSplitScreen */); + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())); SplitWithKeyboardShortcutRecentsAnimationListener listener = new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop); MAIN_EXECUTOR.execute(() -> { callbacks.addListener(listener); - UI_HELPER_EXECUTOR.execute( - // Transition from fullscreen app to enter stage split in launcher with - // recents animation. - () -> ActivityManagerWrapper.getInstance().startRecentsActivity( - mOverviewComponentObserver.getOverviewIntent(), - SystemClock.uptimeMillis(), callbacks, null, null)); + UI_HELPER_EXECUTOR.execute(() -> { + // Transition from fullscreen app to enter stage split in launcher with + // recents animation + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); + options.setTransientLaunch(); + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) + .startRecentsActivity(mOverviewComponentObserver.getOverviewIntent(), + ActivityOptions.makeBasic(), callbacks, + false /* useSyntheticRecentsTransition */); + }); }); } - public void onDestroy() { - mOverviewComponentObserver.onDestroy(); - mDeviceState.destroy(); - } - private class SplitWithKeyboardShortcutRecentsAnimationListener implements RecentsAnimationCallbacks.RecentsAnimationListener { private final boolean mLeftOrTop; private final Rect mTempRect = new Rect(); + private final ActivityManager.RunningTaskInfo mRunningTaskInfo; private SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop) { mLeftOrTop = leftOrTop; + mRunningTaskInfo = ActivityManagerWrapper.getInstance().getRunningTask(); } @Override public void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targets) { - ActivityManager.RunningTaskInfo runningTaskInfo = - ActivityManagerWrapper.getInstance().getRunningTask(); - mController.setInitialTaskSelect(runningTaskInfo, + RecentsAnimationTargets targets, TransitionInfo transitionInfo) { + mController.setInitialTaskSelect(mRunningTaskInfo, mLeftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT, null /* itemInfo */, mLeftOrTop ? LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP @@ -136,14 +130,15 @@ public void onRecentsAnimationStart(RecentsAnimationController controller, RectF startingTaskRect = new RectF(); final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView( mLauncher, mLauncher.getDragLayer(), - controller.screenshotTask(runningTaskInfo.taskId).getThumbnail(), + controller.screenshotTask(mRunningTaskInfo.taskId).getThumbnail(), null /* icon */, startingTaskRect); + Task task = Task.from(new Task.TaskKey(mRunningTaskInfo), mRunningTaskInfo, + false /* isLocked */); RecentsModel.INSTANCE.get(mLauncher.getApplicationContext()) .getIconCache() - .updateIconInBackground( - Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo, - false /* isLocked */), - (task) -> floatingTaskView.setIcon(task.icon)); + .getIconInBackground( + task, + (icon, contentDescription, title) -> floatingTaskView.setIcon(icon)); floatingTaskView.setAlpha(1); floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, false /* fadeWithThumbnail */, true /* isStagedTask */); diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 997a842dc2e..a2856a6012a 100644 --- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -49,6 +49,7 @@ import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DynamicResource; import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.ResourceProvider; @@ -63,8 +64,7 @@ public class StaggeredWorkspaceAnim { private static final int APP_CLOSE_ROW_START_DELAY_MS = 10; // Should be used for animations running alongside this StaggeredWorkspaceAnim. public static final int DURATION_MS = 250; - public static final int DURATION_TASKBAR_MS = - QuickstepTransitionManager.getTaskbarToHomeDuration(); + private final int mTaskbarDurationInMs; private static final float MAX_VELOCITY_PX_PER_S = 22f; @@ -81,6 +81,10 @@ public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) { + boolean isPinnedTaskbarAndNotInDesktopMode = DisplayController.isPinnedTaskbar(launcher) + && !DisplayController.isInDesktopMode(launcher); + mTaskbarDurationInMs = QuickstepTransitionManager.getTaskbarToHomeDuration( + isPinnedTaskbarAndNotInDesktopMode); prepareToAnimate(launcher, animateOverviewScrim); mIgnoredView = ignoredView; @@ -93,7 +97,7 @@ public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y); DeviceProfile grid = launcher.getDeviceProfile(); - long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS; + long duration = grid.isTaskbarPresent ? mTaskbarDurationInMs : DURATION_MS; if (staggerWorkspace) { Workspace workspace = launcher.getWorkspace(); Hotseat hotseat = launcher.getHotseat(); diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java index c3a7bfeac58..49c69e248c8 100644 --- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java +++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java @@ -18,6 +18,7 @@ import android.animation.Animator; import android.animation.RectEvaluator; +import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -25,6 +26,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; +import android.util.Rational; import android.view.Surface; import android.view.SurfaceControl; import android.view.View; @@ -39,7 +41,7 @@ import com.android.quickstep.TaskAnimationManager; import com.android.systemui.shared.pip.PipSurfaceTransactionHelper; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; -import com.android.wm.shell.pip.PipContentOverlay; +import com.android.wm.shell.shared.pip.PipContentOverlay; /** * Subclass of {@link RectFSpringAnim} that animates an Activity to PiP (picture-in-picture) window @@ -50,8 +52,6 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { 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; @@ -137,8 +137,13 @@ private SwipePipToHomeAnimator(@NonNull Context context, mDestinationBoundsTransformed.set(destinationBoundsTransformed); mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius); - final float aspectRatio = destinationBounds.width() / (float) destinationBounds.height(); + final Rational aspectRatio = new Rational( + destinationBounds.width(), destinationBounds.height()); String reasonForCreateOverlay = null; // For debugging purpose. + + // Slightly larger app bounds to allow for off by 1 pixel source-rect-hint errors. + Rect overflowAppBounds = new Rect(appBounds.left - 1, appBounds.top - 1, + appBounds.right + 1, appBounds.bottom + 1); if (sourceRectHint.isEmpty()) { reasonForCreateOverlay = "Source rect hint is empty"; } else if (sourceRectHint.width() < destinationBounds.width() @@ -149,40 +154,27 @@ private SwipePipToHomeAnimator(@NonNull Context context, // animation in this case. reasonForCreateOverlay = "Source rect hint is too small " + sourceRectHint; sourceRectHint.setEmpty(); - } else if (!appBounds.contains(sourceRectHint)) { + } else if (!overflowAppBounds.contains(sourceRectHint)) { // This is a situation in which the source hint rect is outside the app bounds, so it is // not a valid rectangle to use for cropping app surface reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint; 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(); + } else { + final Rational srcAspectRatio = new Rational( + sourceRectHint.width(), sourceRectHint.height()); + if (!PictureInPictureParams.isSameAspectRatio(destinationBounds, srcAspectRatio)) { + // The aspect ratio of destination bounds does not match source rect hint. + // We use the aspect ratio of source rect hint to check against destination bounds + // here to avoid upscaling error. + reasonForCreateOverlay = "Source rect hint:" + sourceRectHint + + " does not match destination bounds:" + destinationBounds; + sourceRectHint.setEmpty(); + } } 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 - // the cleanup. + mSourceRectHint.set( + getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio.floatValue())); mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(), mAppBounds, mDestinationBounds, new IconProvider(context).getIcon(mActivityInfo), appIconSizePx); @@ -225,6 +217,26 @@ public void onAnimationEnd(Animator animation) { addOnUpdateListener(this::onAnimationUpdate); } + /** + * Crop a Rect matches the aspect ratio and pivots at the center point. + */ + private Rect getEnterPipWithOverlaySrcRectHint(Rect appBounds, float aspectRatio) { + final float appBoundsAspectRatio = appBounds.width() / (float) appBounds.height(); + final int width, height; + int left = appBounds.left; + int top = appBounds.top; + if (appBoundsAspectRatio < aspectRatio) { + width = appBounds.width(); + height = (int) (width / aspectRatio); + top = appBounds.top + (appBounds.height() - height) / 2; + } else { + height = appBounds.height(); + width = (int) (height * aspectRatio); + left = appBounds.left + (appBounds.width() - width) / 2; + } + return new Rect(left, top, left + width, top + height); + } + private void onAnimationUpdate(RectF currentRect, float progress) { if (mHasAnimationEnded) return; final SurfaceControl.Transaction tx = @@ -441,13 +453,22 @@ public Builder setFromRotation(TaskViewSimulator taskViewSimulator, return this; } + public Builder setDisplayCutoutInsets(@NonNull Rect displayCutoutInsets) { + mDisplayCutoutInsets = new Rect(displayCutoutInsets); + return this; + } + public SwipePipToHomeAnimator build() { if (mDestinationBoundsTransformed.isEmpty()) { mDestinationBoundsTransformed.set(mDestinationBounds); } // adjust the mSourceRectHint / mAppBounds by display cutout if applicable. if (mSourceRectHint != null && mDisplayCutoutInsets != null) { - if (mFromRotation == Surface.ROTATION_90) { + if (mFromRotation == Surface.ROTATION_0) { + // TODO: this is to special case the issues on Foldable device + // with display cutout. + mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top); + } else if (mFromRotation == Surface.ROTATION_90) { mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top); } else if (mFromRotation == Surface.ROTATION_270) { mAppBounds.inset(mDisplayCutoutInsets); @@ -461,15 +482,6 @@ public SwipePipToHomeAnimator build() { } } - private static class RotatedPosition { - private final float degree; - private final float positionX; - private final float positionY; - - private RotatedPosition(float degree, float positionX, float positionY) { - this.degree = degree; - this.positionX = positionX; - this.positionY = positionY; - } + private record RotatedPosition(float degree, float positionX, float positionY) { } } diff --git a/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt index 5f4388c918c..1ff05dae567 100644 --- a/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt +++ b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt @@ -47,6 +47,22 @@ object SystemUiFlagUtils { !hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) } + /** + * 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 when + * the device is asleep, the second condition extends ensures that the transition from and to + * the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar hide/reveal + * animation timings. The Taskbar can show when dreaming if the glanceable hub is showing on + * top. + */ + @JvmStatic + fun isTaskbarHidden(@SystemUiStateFlags flags: Long): Boolean { + return ((hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_DEVICE_DREAMING) && + !hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING)) || + (flags and QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK) != + QuickStepContract.WAKEFULNESS_AWAKE) + } + 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 5653d932cd2..b3ab4df1b63 100644 --- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java +++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java @@ -15,6 +15,7 @@ */ package com.android.quickstep.util; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.Display.DEFAULT_DISPLAY; import android.content.Context; @@ -26,24 +27,36 @@ import android.view.WindowMetrics; import com.android.internal.policy.SystemBarUtils; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logging.FileLog; 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 com.android.quickstep.SystemUiProxy; +import com.android.window.flags.Flags; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; +import java.util.Collections; import java.util.List; import java.util.Set; +import javax.inject.Inject; + /** - * Extension of {@link WindowManagerProxy} with some assumption for the default - * system Launcher + * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher */ +@LauncherAppSingleton public class SystemWindowManagerProxy extends WindowManagerProxy { + // LC-Note: This is pretty much unused by Launcher3, see [LawnchairWindowManagerProxy] + + private final DesktopVisibilityController mDesktopVisibilityController; - public SystemWindowManagerProxy(Context context) { + + @Inject + public SystemWindowManagerProxy(DesktopVisibilityController desktopVisibilityController) { super(true); + mDesktopVisibilityController = desktopVisibilityController; } @Override @@ -53,10 +66,55 @@ public Rect getCurrentBounds(Context displayInfoContext) { } @Override - public boolean isInDesktopMode() { - DesktopVisibilityController desktopController = LauncherActivityInterface.INSTANCE - .getDesktopVisibilityController(); - return desktopController != null && desktopController.areDesktopTasksVisible(); + public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { + mDesktopVisibilityController.registerDesktopVisibilityListener(listener); + } + + @Override + public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { + mDesktopVisibilityController.unregisterDesktopVisibilityListener(listener); + } + + @Override + public boolean isInDesktopMode(int displayId) { + return mDesktopVisibilityController.isInDesktopMode(displayId); + } + + @Override + public boolean showLockedTaskbarOnHome(Context displayInfoContext) { + if (!DesktopModeStatus.canEnterDesktopMode(displayInfoContext)) { + return false; + } + if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(displayInfoContext)) { + return false; + } + final boolean isFreeformDisplay = displayInfoContext.getResources().getConfiguration() + .windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; + return isFreeformDisplay; + } + + @Override + public boolean showDesktopTaskbarForFreeformDisplay(Context displayInfoContext) { + if (!DesktopModeStatus.canEnterDesktopMode(displayInfoContext)) { + return false; + } + + if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(displayInfoContext)) { + return false; + } + + if (!Flags.enableDesktopTaskbarOnFreeformDisplays()) { + return false; + } + + final boolean isFreeformDisplay = displayInfoContext.getResources().getConfiguration() + .windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; + return isFreeformDisplay; + } + + @Override + public boolean isHomeVisible(Context context) { + return SystemUiProxy.INSTANCE.get(context).getHomeVisibilityState().isHomeVisible(); } @Override @@ -67,10 +125,8 @@ public int getRotation(Context displayInfoContext) { @Override protected int getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset) { - // See b/264656380, calculate the status bar height manually as the inset in the - // system - // server might not be updated by this point yet causing extra DeviceProfile - // updates + // See b/264656380, calculate the status bar height manually as the inset in the system + // server might not be updated by this point yet causing extra DeviceProfile updates return SystemBarUtils.getStatusBarHeight(context); } @@ -79,9 +135,14 @@ public ArrayMap> estimateInternalDisplayBo Context displayInfoContext) { ArrayMap> result = new ArrayMap<>(); WindowManager windowManager = displayInfoContext.getSystemService(WindowManager.class); - Set possibleMaximumWindowMetrics = windowManager - .getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); - FileLog.d("b/283944974", "possibleMaximumWindowMetrics: " + possibleMaximumWindowMetrics); + Set possibleMaximumWindowMetrics = + null; + try { + possibleMaximumWindowMetrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); + } catch (Throwable t) { + possibleMaximumWindowMetrics = Collections.singleton( + windowManager.getMaximumWindowMetrics()); + } for (WindowMetrics windowMetrics : possibleMaximumWindowMetrics) { CachedDisplayInfo info = getDisplayInfo(windowMetrics, Surface.ROTATION_0); List bounds = estimateWindowBounds(displayInfoContext, info); diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java index 9a010429d35..027dc083ccf 100644 --- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java +++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java @@ -21,6 +21,7 @@ import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.util.Log; import androidx.annotation.Nullable; @@ -45,7 +46,7 @@ public class TISBindHelper implements ServiceConnection { // Max backoff caps at 5 mins private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000; - private final Handler mHandler = new Handler(); + private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Runnable mConnectionRunnable = this::internalBindToTIS; private final Context mContext; private final Consumer mConnectionCallback; diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java deleted file mode 100644 index 98d363ef5c7..00000000000 --- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import androidx.annotation.IntDef; - -import com.android.launcher3.util.IntArray; - -import java.lang.annotation.Retention; - -/** - * Helper class for navigating RecentsView grid tasks via arrow keys and tab. - */ -public class TaskGridNavHelper { - public static final int CLEAR_ALL_PLACEHOLDER_ID = -1; - public static final int INVALID_FOCUSED_TASK_ID = -1; - - public static final int DIRECTION_UP = 0; - public static final int DIRECTION_DOWN = 1; - public static final int DIRECTION_LEFT = 2; - public static final int DIRECTION_RIGHT = 3; - public static final int DIRECTION_TAB = 4; - - @Retention(SOURCE) - @IntDef({DIRECTION_UP, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_TAB}) - public @interface TASK_NAV_DIRECTION {} - - private final IntArray mOriginalTopRowIds; - private IntArray mTopRowIds; - private IntArray mBottomRowIds; - private final int mFocusedTaskId; - - public TaskGridNavHelper(IntArray topIds, IntArray bottomIds, int focusedTaskId) { - mFocusedTaskId = focusedTaskId; - mOriginalTopRowIds = topIds.clone(); - generateTaskViewIdGrid(topIds, bottomIds); - } - - private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray) { - boolean hasFocusedTask = mFocusedTaskId != INVALID_FOCUSED_TASK_ID; - int maxSize = - Math.max(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0); - int minSize = - Math.min(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0); - - // Add the focused task to the beginning of both arrays if it exists. - if (hasFocusedTask) { - topRowIdArray.add(0, mFocusedTaskId); - bottomRowIdArray.add(0, mFocusedTaskId); - } - - // Fill in the shorter array with the ids from the longer one. - for (int i = minSize; i < maxSize; i++) { - if (i >= topRowIdArray.size()) { - topRowIdArray.add(bottomRowIdArray.get(i)); - } else { - bottomRowIdArray.add(topRowIdArray.get(i)); - } - } - - // Add the clear all button to the end of both arrays - topRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID); - bottomRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID); - - mTopRowIds = topRowIdArray; - mBottomRowIds = bottomRowIdArray; - } - - /** - * Returns the id of the next page in the grid or -1 for the clear all button. - */ - public int getNextGridPage(int currentPageTaskViewId, int delta, - @TASK_NAV_DIRECTION int direction, boolean cycle) { - boolean inTop = mTopRowIds.contains(currentPageTaskViewId); - int index = inTop ? mTopRowIds.indexOf(currentPageTaskViewId) - : mBottomRowIds.indexOf(currentPageTaskViewId); - int maxSize = Math.max(mTopRowIds.size(), mBottomRowIds.size()); - int nextIndex = index + delta; - - switch (direction) { - case DIRECTION_UP: - case DIRECTION_DOWN: { - return inTop ? mBottomRowIds.get(index) : mTopRowIds.get(index); - } - case DIRECTION_LEFT: { - int boundedIndex = cycle ? nextIndex % maxSize : Math.min(nextIndex, maxSize - 1); - return inTop ? mTopRowIds.get(boundedIndex) - : mBottomRowIds.get(boundedIndex); - } - case DIRECTION_RIGHT: { - int boundedIndex = - cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex) : Math.max( - nextIndex, 0); - boolean inOriginalTop = mOriginalTopRowIds.contains(currentPageTaskViewId); - return inOriginalTop ? mTopRowIds.get(boundedIndex) - : mBottomRowIds.get(boundedIndex); - } - case DIRECTION_TAB: { - int boundedIndex = - cycle ? nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize : Math.min( - nextIndex, maxSize - 1); - if (delta >= 0) { - return inTop && mTopRowIds.get(index) != mBottomRowIds.get(index) - ? mBottomRowIds.get(index) - : mTopRowIds.get(boundedIndex); - } else { - if (mTopRowIds.contains(currentPageTaskViewId)) { - return mBottomRowIds.get(boundedIndex); - } else { - // Go up to top if there is task above - return mTopRowIds.get(index) != mBottomRowIds.get(index) - ? mTopRowIds.get(index) - : mBottomRowIds.get(boundedIndex); - } - } - } - default: - return currentPageTaskViewId; - } - } -} diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.kt b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.kt new file mode 100644 index 00000000000..0e788015cd4 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util + +import com.android.launcher3.util.IntArray +import kotlin.math.abs +import kotlin.math.max + +/** Helper class for navigating RecentsView grid tasks via arrow keys and tab. */ +class TaskGridNavHelper( + private val topIds: IntArray, + bottomIds: IntArray, + largeTileIds: List, + hasAddDesktopButton: Boolean, +) { + private val topRowIds = mutableListOf() + private val bottomRowIds = mutableListOf() + + init { + // Add AddDesktopButton and lage tiles to both rows. + if (hasAddDesktopButton) { + topRowIds += ADD_DESK_PLACEHOLDER_ID + bottomRowIds += ADD_DESK_PLACEHOLDER_ID + } + topRowIds += largeTileIds + bottomRowIds += largeTileIds + + // Add row ids to their respective rows. + topRowIds += topIds + bottomRowIds += bottomIds + + // Fill in the shorter array with the ids from the longer one. + topRowIds += bottomRowIds.takeLast(max(bottomRowIds.size - topRowIds.size, 0)) + bottomRowIds += topRowIds.takeLast(max(topRowIds.size - bottomRowIds.size, 0)) + + // Add the clear all button to the end of both arrays. + topRowIds += CLEAR_ALL_PLACEHOLDER_ID + bottomRowIds += CLEAR_ALL_PLACEHOLDER_ID + } + + /** Returns the id of the next page in the grid or -1 for the clear all button. */ + fun getNextGridPage( + currentPageTaskViewId: Int, + delta: Int, + direction: TaskNavDirection, + cycle: Boolean, + ): Int { + val inTop = topRowIds.contains(currentPageTaskViewId) + val index = + if (inTop) topRowIds.indexOf(currentPageTaskViewId) + else bottomRowIds.indexOf(currentPageTaskViewId) + val maxSize = max(topRowIds.size, bottomRowIds.size) + val nextIndex = index + delta + + return when (direction) { + TaskNavDirection.UP, + TaskNavDirection.DOWN -> { + if (inTop) bottomRowIds[index] else topRowIds[index] + } + TaskNavDirection.LEFT -> { + val boundedIndex = + if (cycle) nextIndex % maxSize else nextIndex.coerceAtMost(maxSize - 1) + if (inTop) topRowIds[boundedIndex] else bottomRowIds[boundedIndex] + } + TaskNavDirection.RIGHT -> { + val boundedIndex = + if (cycle) (if (nextIndex < 0) maxSize - 1 else nextIndex) + else nextIndex.coerceAtLeast(0) + val inOriginalTop = topIds.contains(currentPageTaskViewId) + if (inOriginalTop) topRowIds[boundedIndex] else bottomRowIds[boundedIndex] + } + TaskNavDirection.TAB -> { + val boundedIndex = + if (cycle) (if (nextIndex < 0) maxSize - 1 else nextIndex % maxSize) + else nextIndex.coerceAtMost(maxSize - 1) + if (delta >= 0) { + if (inTop && topRowIds[index] != bottomRowIds[index]) bottomRowIds[index] + else topRowIds[boundedIndex] + } else { + if (topRowIds.contains(currentPageTaskViewId)) { + if (boundedIndex < 0) { + // If no cycling, always return the first task. + topRowIds[0] + } else { + bottomRowIds[boundedIndex] + } + } else { + // Go up to top if there is task above + if (topRowIds[index] != bottomRowIds[index]) topRowIds[index] + else bottomRowIds[boundedIndex] + } + } + } + else -> currentPageTaskViewId + } + } + + /** + * Returns a sequence of pairs of (TaskView ID, offset) in the grid, ordered according to tab + * navigation, starting from the initial TaskView ID, towards the start or end of the grid. + * + *

A positive delta moves forward in the tab order towards the end of the grid, while a + * negative value moves backward towards the beginning. The offset is the distance between + * columns the tasks are in. + */ + fun gridTaskViewIdOffsetPairInTabOrderSequence( + initialTaskViewId: Int, + towardsStart: Boolean, + ): Sequence> = sequence { + val draggedTaskViewColumn = getColumn(initialTaskViewId) + var nextTaskViewId: Int = initialTaskViewId + var previousTaskViewId: Int = Int.MIN_VALUE + while (nextTaskViewId != previousTaskViewId && nextTaskViewId >= 0) { + previousTaskViewId = nextTaskViewId + nextTaskViewId = + getNextGridPage( + nextTaskViewId, + if (towardsStart) -1 else 1, + TaskNavDirection.TAB, + cycle = false, + ) + if (nextTaskViewId >= 0 && nextTaskViewId != previousTaskViewId) { + val columnOffset = abs(getColumn(nextTaskViewId) - draggedTaskViewColumn) + yield(Pair(nextTaskViewId, columnOffset)) + } + } + } + + /** Returns the column of a task's id in the grid. */ + private fun getColumn(taskViewId: Int): Int = + if (topRowIds.contains(taskViewId)) topRowIds.indexOf(taskViewId) + else bottomRowIds.indexOf(taskViewId) + + enum class TaskNavDirection { + UP, + DOWN, + LEFT, + RIGHT, + TAB, + } + + companion object { + const val CLEAR_ALL_PLACEHOLDER_ID: Int = -1 + const val ADD_DESK_PLACEHOLDER_ID: Int = -2 + } +} diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java index 69137cc1b6a..43ef39c2568 100644 --- a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java +++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java @@ -17,6 +17,7 @@ import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.systemui.shared.recents.model.Task; @@ -94,6 +95,7 @@ public synchronized void removeAll(Predicate keyCheck) { * Gets the entry if it is still valid */ @Override + @Nullable public synchronized V getAndInvalidateIfModified(Task.TaskKey key) { Entry entry = mMap.get(key.id); if (entry != null && entry.mKey.windowingMode == key.windowingMode diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java index 8ee78ab0bab..9df0993a7bd 100644 --- a/quickstep/src/com/android/quickstep/util/TaskKeyCache.java +++ b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java @@ -15,6 +15,8 @@ */ package com.android.quickstep.util; +import androidx.annotation.Nullable; + import com.android.systemui.shared.recents.model.Task; import java.util.function.Predicate; @@ -44,6 +46,7 @@ public interface TaskKeyCache { /** * Gets the entry if it is still valid. */ + @Nullable V getAndInvalidateIfModified(Task.TaskKey key); /** diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java index 89f5d41dad7..9fe8cc954db 100644 --- a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java +++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java @@ -17,6 +17,8 @@ import android.util.Log; +import androidx.annotation.Nullable; + import com.android.systemui.shared.recents.model.Task.TaskKey; import java.util.LinkedHashMap; @@ -59,6 +61,7 @@ public synchronized void removeAll(Predicate keyCheck) { /** * Gets the entry if it is still valid */ + @Nullable public synchronized V getAndInvalidateIfModified(TaskKey key) { Entry entry = mMap.get(key.id); diff --git a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java index e80d2a6d3f8..40a328ccfc8 100644 --- a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java +++ b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java @@ -98,10 +98,7 @@ private void checkTaskLaunchFailed() { final Runnable taskLaunchFailedCallback = mTaskLaunchFailedCallback; RecentsModel.INSTANCE.get(mContext).isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> { if (taskRemoved) { - ActiveGestureLog.INSTANCE.addLog( - new ActiveGestureLog.CompoundString("Launch failed, task (id=") - .append(launchedTaskId) - .append(") finished mid transition")); + ActiveGestureProtoLogProxy.logTaskLaunchFailed(launchedTaskId); taskLaunchFailedCallback.run(); } }, (task) -> true /* filter */); diff --git a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java index 91e83769901..6e2d469d185 100644 --- a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java +++ b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java @@ -16,16 +16,11 @@ package com.android.quickstep.util; -import static android.app.ActivityTaskManager.INVALID_TASK_ID; - -import android.app.Activity; import android.app.ActivityManager; import android.util.Log; import androidx.annotation.NonNull; -import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter; -import com.android.quickstep.RecentsModel; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index fcf303fdd98..58d8b78962e 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -24,10 +24,8 @@ 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.StagePosition; -import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation; -import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell; import android.animation.TimeInterpolator; import android.content.Context; @@ -43,7 +41,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.app.animation.Interpolators; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; @@ -52,9 +49,10 @@ import com.android.launcher3.util.TraceHelper; import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.BaseContainerInterface; +import com.android.quickstep.DesktopFullscreenDrawParams; +import com.android.quickstep.FullscreenDrawParams; import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; -import com.android.quickstep.views.TaskView.FullscreenDrawParams; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.recents.utilities.PreviewPositionHelper; @@ -99,11 +97,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final FullscreenDrawParams mCurrentFullscreenParams; public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat(); public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat(); + public final AnimatedFloat taskGridTranslationX = new AnimatedFloat(); + public final AnimatedFloat taskGridTranslationY = new AnimatedFloat(); // Carousel properties public final AnimatedFloat carouselScale = new AnimatedFloat(); - public final AnimatedFloat carouselPrimaryTranslation = new AnimatedFloat(); - public final AnimatedFloat carouselSecondaryTranslation = new AnimatedFloat(); // RecentsView properties public final AnimatedFloat recentsViewScale = new AnimatedFloat(); @@ -118,19 +116,28 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private SplitBounds mSplitBounds; private Boolean mDrawsBelowRecents = null; private boolean mIsGridTask; - private boolean mIsDesktopTask; - private boolean mScaleToCarouselTaskSize = false; + private final boolean mIsDesktopTask; + private boolean mIsAnimatingToCarousel = false; private int mTaskRectTranslationX; private int mTaskRectTranslationY; + private int mDesktopTaskIndex = 0; - public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy) { + @Nullable + private Matrix mTaskRectTransform = null; + + public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy, + boolean isDesktop, int desktopTaskIndex) { mContext = context; mSizeStrategy = sizeStrategy; + mIsDesktopTask = isDesktop; + mDesktopTaskIndex = desktopTaskIndex; mOrientationState = TraceHelper.allowIpcs("TaskViewSimulator.init", () -> new RecentsOrientedState(context, sizeStrategy, i -> { })); mOrientationState.setGestureActive(true); - mCurrentFullscreenParams = new FullscreenDrawParams(context); + mCurrentFullscreenParams = mIsDesktopTask + ? new DesktopFullscreenDrawParams(context) + : new FullscreenDrawParams(context); mOrientationStateId = mOrientationState.getStateId(); Resources resources = context.getResources(); mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources); @@ -144,6 +151,9 @@ public void setDp(DeviceProfile dp) { mDp = dp; mLayoutValid = false; mOrientationState.setDeviceProfile(dp); + if (enableGridOnlyOverview()) { + mIsGridTask = dp.isTablet && !mIsDesktopTask; + } calculateTaskSize(); } @@ -155,14 +165,16 @@ private void calculateTaskSize() { if (mIsGridTask) { mSizeStrategy.calculateGridTaskSize(mContext, mDp, mFullTaskSize, mOrientationState.getOrientationHandler()); + if (enableGridOnlyOverview()) { + mSizeStrategy.calculateTaskSize(mContext, mDp, mCarouselTaskSize, + mOrientationState.getOrientationHandler()); + } } else { mSizeStrategy.calculateTaskSize(mContext, mDp, mFullTaskSize, mOrientationState.getOrientationHandler()); - } - - if (enableGridOnlyOverview()) { - mSizeStrategy.calculateCarouselTaskSize(mContext, mDp, mCarouselTaskSize, - mOrientationState.getOrientationHandler()); + if (enableGridOnlyOverview()) { + mCarouselTaskSize.set(mFullTaskSize); + } } if (mSplitBounds != null) { @@ -172,7 +184,6 @@ private void calculateTaskSize() { mTaskRect.set(mFullTaskSize); mOrientationState.getOrientationHandler() .setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition); - mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); } else if (mIsDesktopTask) { // For desktop, tasks can take up only part of the screen size. // Full task size represents the whole screen size, but scaled down to fit in recents. @@ -186,10 +197,19 @@ private void calculateTaskSize() { mTaskRect.scale(scale); // Ensure the task rect is inside the full task rect mTaskRect.offset(mFullTaskSize.left, mFullTaskSize.top); + + Rect taskDimension = new Rect(0, 0, (int) fullscreenTaskDimension.x, + (int) fullscreenTaskDimension.y); + mTmpCropRect.set(mThumbnailPosition); + if (mTmpCropRect.setIntersect(taskDimension, mThumbnailPosition)) { + mTmpCropRect.offset(-mThumbnailPosition.left, -mThumbnailPosition.top); + } else { + mTmpCropRect.setEmpty(); + } } else { mTaskRect.set(mFullTaskSize); - mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); } + mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); } /** @@ -209,12 +229,7 @@ public float getFullScreenScale() { } // Copy mFullTaskSize instead of updating it directly so it could be reused next time // without recalculating - Rect scaleRect = new Rect(); - if (mScaleToCarouselTaskSize) { - scaleRect.set(mCarouselTaskSize); - } else { - scaleRect.set(mFullTaskSize); - } + Rect scaleRect = new Rect(mIsAnimatingToCarousel ? mCarouselTaskSize : mFullTaskSize); scaleRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); float scale = mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot); if (mPivotOverride != null) { @@ -248,8 +263,6 @@ public void setPreview(RemoteAnimationTarget runningTarget, SplitBounds splitInf } else { mStagePosition = runningTarget.taskId == splitInfo.leftTopTaskId ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT; - mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds), - mStagePosition); } calculateTaskSize(); } @@ -284,13 +297,6 @@ public void setIsGridTask(boolean isGridTask) { mIsGridTask = isGridTask; } - /** - * Sets whether this task is part of desktop tasks in overview. - */ - public void setIsDesktopTask(boolean desktop) { - mIsDesktopTask = desktop; - } - /** * Apply translations on TaskRect's starting location. */ @@ -302,66 +308,24 @@ public void setTaskRectTranslation(int taskRectTranslationX, int taskRectTransla } /** - * Adds animation for all the components corresponding to transition from an app to overview. + * Override the pivot used to apply scale changes. + */ + public void setPivotOverride(PointF pivotOverride) { + mPivotOverride = pivotOverride; + getFullScreenScale(); + } + + /** + * Adds animation for all the components corresponding to transition from an app to carousel. */ - public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) { + public void addAppToCarouselAnim(PendingAnimation pa, Interpolator interpolator) { pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator); - float fullScreenScale; if (enableGridOnlyOverview() && mDp.isTablet && mDp.isGestureMode) { - // Move pivot to top right edge of the screen, to avoid task scaling down in opposite - // direction of app window movement, otherwise the animation will wiggle left and right. - // Also translate the app window to top right edge of the screen to simplify - // calculations. - taskPrimaryTranslation.value = mIsRecentsRtl - ? mDp.widthPx - mFullTaskSize.right - : -mFullTaskSize.left; - taskSecondaryTranslation.value = -mFullTaskSize.top; - mPivotOverride = new PointF(mIsRecentsRtl ? mDp.widthPx : 0, 0); - - // Scale down to the carousel and use the carousel Rect to calculate fullScreenScale. - mScaleToCarouselTaskSize = true; + mIsAnimatingToCarousel = true; carouselScale.value = mCarouselTaskSize.width() / (float) mFullTaskSize.width(); - fullScreenScale = getFullScreenScale(); - - float carouselPrimaryTranslationTarget = mIsRecentsRtl - ? mCarouselTaskSize.right - mDp.widthPx - : mCarouselTaskSize.left; - float carouselSecondaryTranslationTarget = mCarouselTaskSize.top; - - // Expected carousel position's center is in the middle, and invariant of - // recentsViewScale. - float exceptedCarouselCenterX = mCarouselTaskSize.centerX(); - // Animating carousel translations linearly will result in a curved path, therefore - // we'll need to calculate the expected translation at each recentsView scale. Luckily - // primary and secondary follow the same translation, and primary is used here due to - // it being simpler. - Interpolator carouselTranslationInterpolator = t -> { - // recentsViewScale is calculated rather than using recentsViewScale.value, so that - // this interpolator works independently even if recentsViewScale don't animate. - float recentsViewScale = - Utilities.mapToRange(t, 0, 1, fullScreenScale, 1, Interpolators.LINEAR); - // Without the translation, the app window will animate from fullscreen into top - // right corner. - float expectedTaskCenterX = mIsRecentsRtl - ? mDp.widthPx - mCarouselTaskSize.width() * recentsViewScale / 2f - : mCarouselTaskSize.width() * recentsViewScale / 2f; - // Calculate the expected translation, then work back the animatedFraction that - // results in this value. - float carouselPrimaryTranslation = - (exceptedCarouselCenterX - expectedTaskCenterX) / recentsViewScale; - return carouselPrimaryTranslation / carouselPrimaryTranslationTarget; - }; - - // Use addAnimatedFloat so this animation can later be canceled and animate to a - // different value in RecentsView.onPrepareGestureEndAnimation. - pa.addAnimatedFloat(carouselPrimaryTranslation, 0, carouselPrimaryTranslationTarget, - carouselTranslationInterpolator); - pa.addAnimatedFloat(carouselSecondaryTranslation, 0, carouselSecondaryTranslationTarget, - carouselTranslationInterpolator); - } else { - fullScreenScale = getFullScreenScale(); } - pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, fullScreenScale, 1, interpolator); + pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, + interpolator); } /** @@ -405,6 +369,38 @@ public Matrix getCurrentMatrix() { return mMatrix; } + /** + * Sets a matrix used to transform the position of tasks. If set, this matrix is applied to + * the task rect after the task has been scaled and positioned inside the fulltask, but + * before scaling and translation of the whole recents view is performed. + */ + public void setTaskRectTransform(@Nullable Matrix taskRectTransform) { + mTaskRectTransform = taskRectTransform; + } + + /** + * Calculates the crop rect for desktop tasks given the current matrix. + */ + private void calculateDesktopTaskCropRect() { + // The approach here is to map a rect that represents the untransformed thumbnail position + // using the current matrix. This will give us a rect that can be intersected with + // [mFullTaskSize]. Using the intersection, we then compute how much of the task window that + // needs to be cropped (which will be nothing if the window is entirely within the desktop). + mTempRectF.set(0, 0, mThumbnailPosition.width(), mThumbnailPosition.height()); + mMatrix.mapRect(mTempRectF); + + float offsetX = mTempRectF.left; + float offsetY = mTempRectF.top; + float scale = mThumbnailPosition.width() / mTempRectF.width(); + + if (mTempRectF.intersect(mFullTaskSize.left, mFullTaskSize.top, mFullTaskSize.right, + mFullTaskSize.bottom)) { + mTempRectF.offset(-offsetX, -offsetY); + mTempRectF.scale(scale); + mTempRectF.round(mTmpCropRect); + } + } + /** * Applies the rotation on the matrix to so that it maps from launcher coordinate space to * window coordinate space. @@ -466,18 +462,29 @@ public void apply(TransformParams params, @Nullable SurfaceTransaction surfaceTr mMatrix.set(mPositionHelper.getMatrix()); - // Apply TaskView matrix: taskRect, translate + // Apply TaskView matrix: taskRect, optional transform, translate mMatrix.postTranslate(mTaskRect.left, mTaskRect.top); + if (mTaskRectTransform != null) { + mMatrix.postConcat(mTaskRectTransform); + + // Calculate cropping for desktop tasks. The order is important since it uses the + // current matrix. Therefore we calculate it here, after applying the task rect + // transform, but before applying scaling/translation that affects the whole + // recentsview. + if (mIsDesktopTask) { + calculateDesktopTaskCropRect(); + } + } + mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, taskPrimaryTranslation.value); mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, taskSecondaryTranslation.value); + mMatrix.postTranslate(taskGridTranslationX.value, taskGridTranslationY.value); - mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y); - mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, - carouselPrimaryTranslation.value); - mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, - carouselSecondaryTranslation.value); + mMatrix.postScale(carouselScale.value, carouselScale.value, + mIsRecentsRtl ? mCarouselTaskSize.right : mCarouselTaskSize.left, + mCarouselTaskSize.top); mOrientationState.getOrientationHandler().setPrimary( mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value); @@ -490,10 +497,12 @@ public void apply(TransformParams params, @Nullable SurfaceTransaction surfaceTr recentsViewPrimaryTranslation.value); applyWindowToHomeRotation(mMatrix); - // Crop rect is the inverse of thumbnail matrix - mTempRectF.set(0, 0, taskWidth, taskHeight); - mInversePositionMatrix.mapRect(mTempRectF); - mTempRectF.roundOut(mTmpCropRect); + if (!mIsDesktopTask) { + // Crop rect is the inverse of thumbnail matrix + mTempRectF.set(0, 0, taskWidth, taskHeight); + mInversePositionMatrix.mapRect(mTempRectF); + mTempRectF.roundOut(mTmpCropRect); + } params.setProgress(1f - fullScreenProgress); params.applySurfaceParams(surfaceTransaction == null @@ -511,8 +520,8 @@ public void apply(TransformParams params, @Nullable SurfaceTransaction surfaceTr + " taskRect: " + mTaskRect + " taskPrimaryT: " + taskPrimaryTranslation.value + " taskSecondaryT: " + taskSecondaryTranslation.value - + " carouselPrimaryT: " + carouselPrimaryTranslation.value - + " carouselSecondaryT: " + carouselSecondaryTranslation.value + + " taskGridTranslationX: " + taskGridTranslationX.value + + " taskGridTranslationY: " + taskGridTranslationY.value + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value + " recentsScroll: " + recentsViewScroll.value @@ -529,21 +538,12 @@ public void onBuildTargetParams( // If mDrawsBelowRecents is unset, no reordering will be enforced. if (mDrawsBelowRecents != null) { - // In legacy transitions, the animation leashes remain in same hierarchy in the - // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will - // conflict with layers that WM core positions (ie. the input consumers). For shell - // transitions, the animation leashes are reparented to an animation container so we - // can bump layers as needed. - if (ENABLE_SHELL_TRANSITIONS) { - builder.setLayer(mDrawsBelowRecents - ? Integer.MIN_VALUE + app.prefixOrderIndex - // 1000 is an arbitrary number to give room for multiple layers. - : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex); - } else { - builder.setLayer(mDrawsBelowRecents - ? Integer.MIN_VALUE + app.prefixOrderIndex - : 0); - } + // In shell transitions, the animation leashes are reparented to an animation container + // so we can bump layers as needed. + builder.setLayer(mDrawsBelowRecents + // 1000 is an arbitrary number to give room for multiple layers. + ? Integer.MIN_VALUE + 1000 + app.prefixOrderIndex - mDesktopTaskIndex + : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex - mDesktopTaskIndex); } } @@ -552,7 +552,7 @@ public void onBuildTargetParams( * TaskView */ public float getCurrentCornerRadius() { - float visibleRadius = mCurrentFullscreenParams.getCurrentDrawnCornerRadius(); + float visibleRadius = mCurrentFullscreenParams.getCurrentCornerRadius(); mTempPoint[0] = visibleRadius; mTempPoint[1] = 0; mInversePositionMatrix.mapVectors(mTempPoint); diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java index 66bff730bfc..519ef60ecae 100644 --- a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java +++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java @@ -16,6 +16,7 @@ package com.android.quickstep.util; +import android.annotation.NonNull; import android.os.UserHandle; import com.android.systemui.shared.recents.model.Task; @@ -36,7 +37,7 @@ default Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { /** * Called when the icon for a task changes */ - default void onTaskIconChanged(String pkg, UserHandle user) {} + default void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {} /** * Called when the icon for a task changes diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java index 9bad1108bb8..cb591ed0358 100644 --- a/quickstep/src/com/android/quickstep/util/TransformParams.java +++ b/quickstep/src/com/android/quickstep/util/TransformParams.java @@ -19,9 +19,16 @@ import android.util.FloatProperty; import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import androidx.annotation.VisibleForTesting; import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; +import com.android.window.flags.Flags; + +import java.util.function.Supplier; public class TransformParams { @@ -56,15 +63,24 @@ public Float get(TransformParams params) { private float mTargetAlpha; private float mCornerRadius; private RemoteAnimationTargets mTargetSet; + private TransitionInfo mTransitionInfo; + private boolean mCornerRadiusIsOverridden; private SurfaceTransactionApplier mSyncTransactionApplier; + private Supplier mSurfaceTransactionSupplier; private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; public TransformParams() { + this(SurfaceTransaction::new); + } + + @VisibleForTesting + public TransformParams(Supplier surfaceTransactionSupplier) { mProgress = 0; mTargetAlpha = 1; mCornerRadius = -1; + mSurfaceTransactionSupplier = surfaceTransactionSupplier; } /** @@ -106,6 +122,15 @@ public TransformParams setTargetSet(RemoteAnimationTargets targetSet) { return this; } + /** + * Provides the {@code TransitionInfo} of the transition that this transformation stems from. + */ + public TransformParams setTransitionInfo(TransitionInfo transitionInfo) { + mTransitionInfo = transitionInfo; + mCornerRadiusIsOverridden = false; + return this; + } + /** * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that * are computed based on these TransformParams. @@ -136,26 +161,31 @@ public TransformParams setHomeBuilderProxy(BuilderProxy proxy) { /** Builds the SurfaceTransaction from the given BuilderProxy params. */ public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) { RemoteAnimationTargets targets = mTargetSet; - SurfaceTransaction transaction = new SurfaceTransaction(); + SurfaceTransaction transaction = mSurfaceTransactionSupplier.get(); if (targets == null) { return transaction; } for (int i = 0; i < targets.unfilteredApps.length; i++) { RemoteAnimationTarget app = targets.unfilteredApps[i]; SurfaceProperties builder = transaction.forSurface(app.leash); + BuilderProxy targetProxy = + app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME + ? mHomeBuilderProxy + : (app.mode == targets.targetMode ? proxy : mBaseBuilderProxy); if (app.mode == targets.targetMode) { - int activityType = app.windowConfiguration.getActivityType(); - if (activityType == ACTIVITY_TYPE_HOME) { - mHomeBuilderProxy.onBuildTargetParams(builder, app, this); - } else { - builder.setAlpha(getTargetAlpha()); - proxy.onBuildTargetParams(builder, app, this); - } - } else { - mBaseBuilderProxy.onBuildTargetParams(builder, app, this); + builder.setAlpha(getTargetAlpha()); + } + targetProxy.onBuildTargetParams(builder, app, this); + // Override the corner radius for {@code app} with the leash used by Shell, so that it + // doesn't interfere with the window clip and corner radius applied here. + // Only override the corner radius once - so that we don't accidentally override at the + // end of transition after WM Shell has reset the corner radius of the task. + if (!mCornerRadiusIsOverridden) { + overrideFreeformChangeLeashCornerRadiusToZero(app, transaction.getTransaction()); } } + mCornerRadiusIsOverridden = true; // always put wallpaper layer to bottom. final int wallpaperLength = targets.wallpapers != null ? targets.wallpapers.length : 0; @@ -166,7 +196,33 @@ public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) { return transaction; } - // Pubic getters so outside packages can read the values. + private void overrideFreeformChangeLeashCornerRadiusToZero( + RemoteAnimationTarget app, SurfaceControl.Transaction transaction) { + if (!Flags.enableDesktopRecentsTransitionsCornersBugfix()) { + return; + } + if (app.taskInfo == null || !app.taskInfo.isFreeform()) { + return; + } + + SurfaceControl changeLeash = getChangeLeashForApp(app); + if (changeLeash != null) { + transaction.setCornerRadius(changeLeash, 0); + } + } + + private SurfaceControl getChangeLeashForApp(RemoteAnimationTarget app) { + if (mTransitionInfo == null) return null; + for (TransitionInfo.Change change : mTransitionInfo.getChanges()) { + if (change.getTaskInfo() == null) continue; + if (change.getTaskInfo().taskId == app.taskId) { + return change.getLeash(); + } + } + return null; + } + + // Public getters so outside packages can read the values. public float getProgress() { return mProgress; diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java index 0a97793b289..32e0e132e05 100644 --- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java +++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java @@ -30,6 +30,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.util.FloatProperty; +import android.util.Log; import android.view.View; import com.android.app.animation.Interpolators; @@ -51,6 +52,8 @@ */ public class WorkspaceRevealAnim { + private static final String TAG = "WorkspaceRevealAnim"; + // Should be used for animations running alongside this WorkspaceRevealAnim. public static final int DURATION_MS = 350; private static final FloatProperty> WORKSPACE_SCALE_PROPERTY = @@ -97,6 +100,19 @@ public WorkspaceRevealAnim(Launcher launcher, boolean animateOverviewScrim) { mAnimators.setDuration(DURATION_MS); mAnimators.setInterpolator(Interpolators.DECELERATED_EASE); + mAnimators.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + Log.d(TAG, "onAnimationCancel"); + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + Log.d(TAG, "onAnimationEnd: workspace alpha = " + workspace.getAlpha()); + } + }); } private void addRevealAnimatorsForView(T v, FloatProperty scaleProperty) { diff --git a/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt index 09563f55279..915c9e5305d 100644 --- a/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt +++ b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt @@ -22,7 +22,6 @@ import com.android.launcher3.Alarm import com.android.launcher3.DeviceProfile import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener 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 @@ -30,7 +29,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr /** Controls animations that are happening during unfolding foldable devices */ class LauncherUnfoldTransitionController( private val launcher: QuickstepLauncher, - private val progressProvider: ProxyUnfoldTransitionProvider + private val progressProvider: ProxyUnfoldTransitionProvider, ) : OnDeviceProfileChangeListener, ActivityLifecycleCallbacksAdapter, TransitionProgressListener { private var isTablet: Boolean? = null @@ -57,10 +56,6 @@ class LauncherUnfoldTransitionController( } override fun onDeviceProfileChanged(dp: DeviceProfile) { - if (!FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) { - return - } - if (isTablet != null && dp.isTablet != isTablet) { // We should preemptively start the animation only if: // - We changed to the unfolded screen @@ -93,7 +88,7 @@ class LauncherUnfoldTransitionController( provider = this, factory = this::onPrepareUnfoldAnimation, duration = - 1000L // The expected duration for the animation. Then only comes to play if we have + 1000L, // The expected duration for the animation. Then only comes to play if we have // to run the animation ourselves in case sysui misses the end signal ) timeoutAlarm.cancelAlarm() @@ -119,7 +114,7 @@ class LauncherUnfoldTransitionController( launcher, isVertical, dp.displayInfo.currentSize, - anim + anim, ) } diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt new file mode 100644 index 00000000000..37359a1b356 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.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.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.util.AttributeSet +import android.util.FloatProperty +import android.widget.ImageButton +import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X +import com.android.launcher3.R +import com.android.launcher3.util.KFloatProperty +import com.android.launcher3.util.MultiPropertyDelegate +import com.android.launcher3.util.MultiPropertyFactory +import com.android.launcher3.util.MultiValueAlpha +import com.android.quickstep.util.BorderAnimator +import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator + +/** + * Button for supporting multiple desktop sessions. The button will be next to the first TaskView + * inside overview, while clicking this button will create a new desktop session. + */ +class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + ImageButton(context, attrs) { + + private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size) + var contentAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.CONTENT) + var visibilityAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.VISIBILITY) + + private val multiTranslationX = + MultiPropertyFactory(this, VIEW_TRANSLATE_X, TranslationX.entries.size) { a: Float, b: Float + -> + a + b + } + var gridTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.GRID) + var offsetTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.OFFSET) + + private val focusBorderAnimator: BorderAnimator = + createSimpleBorderAnimator( + context.resources.getDimensionPixelSize(R.dimen.add_desktop_button_size), + context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width), + this::getBorderBounds, + this, + context + .obtainStyledAttributes(attrs, R.styleable.AddDesktopButton) + .getColor( + R.styleable.AddDesktopButton_focusBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR, + ), + ) + + var borderEnabled = false + set(value) { + if (field == value) { + return + } + field = value + focusBorderAnimator.setBorderVisibility(visible = field && isFocused, animated = true) + } + + public override fun onFocusChanged( + gainFocus: Boolean, + direction: Int, + previouslyFocusedRect: Rect?, + ) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect) + if (borderEnabled) { + focusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true) + } + } + + private fun getBorderBounds(bounds: Rect) { + bounds.set(0, 0, width, height) + val outlinePadding = + context.resources.getDimensionPixelSize(R.dimen.add_desktop_button_outline_padding) + bounds.inset(-outlinePadding, -outlinePadding) + } + + override fun draw(canvas: Canvas) { + focusBorderAnimator.drawBorder(canvas) + super.draw(canvas) + } + + companion object { + private enum class Alpha { + CONTENT, + VISIBILITY, + } + + private enum class TranslationX { + GRID, + OFFSET, + } + + @JvmField + val VISIBILITY_ALPHA: FloatProperty = + KFloatProperty(AddDesktopButton::visibilityAlpha) + } +} diff --git a/quickstep/src/com/android/quickstep/views/BlurUtils.kt b/quickstep/src/com/android/quickstep/views/BlurUtils.kt new file mode 100644 index 00000000000..d6b2a055b7b --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/BlurUtils.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.views + +import com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur +import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle + +/** Applies blur either behind launcher surface or live tile app. */ +class BlurUtils(private val recentsView: RecentsView<*, *>) { + + fun setDrawLiveTileBelowRecents(drawBelowRecents: Boolean) { + val liveTileRemoteTargetHandles = + if ( + recentsView.remoteTargetHandles != null && + recentsView.recentsAnimationController != null + ) + recentsView.remoteTargetHandles + else null + setDrawBelowRecents(drawBelowRecents, liveTileRemoteTargetHandles) + } + + /** + * Set surface in [remoteTargetHandles] to be above or below Recents layer, and update the base + * layer to apply blur to in BaseDepthController. + */ + fun setDrawBelowRecents( + drawBelowRecents: Boolean, + remoteTargetHandles: Array? = null, + ) { + remoteTargetHandles?.forEach { it.taskViewSimulator.setDrawsBelowRecents(drawBelowRecents) } + if (enableOverviewBackgroundWallpaperBlur()) { + recentsView.depthController?.setBaseSurfaceOverride( + // Blurs behind launcher layer. + if (!drawBelowRecents || remoteTargetHandles == null) { + null + } else { + // Blurs behind live tile. blur will be applied behind window + // which farthest from user in case of desktop and split apps. + remoteTargetHandles + .maxByOrNull { it.transformParams.targetSet.firstAppTarget.leash.layerId } + ?.transformParams + ?.targetSet + ?.firstAppTarget + ?.leash + } + ) + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java deleted file mode 100644 index a5db439b4c3..00000000000 --- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.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.Flags; -import com.android.launcher3.R; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.util.BorderAnimator; - -import kotlin.Unit; - -import app.lawnchair.font.FontManager; -import app.lawnchair.theme.drawable.DrawableTokens; - -public class ClearAllButton extends Button { - - public static final FloatProperty VISIBILITY_ALPHA = new FloatProperty( - "visibilityAlpha") { - @Override - public Float get(ClearAllButton view) { - return view.mVisibilityAlpha; - } - - @Override - public void setValue(ClearAllButton view, float v) { - view.setVisibilityAlpha(v); - } - }; - - public static final FloatProperty DISMISS_ALPHA = new FloatProperty( - "dismissAlpha") { - @Override - public Float get(ClearAllButton view) { - return view.mDismissAlpha; - } - - @Override - public void setValue(ClearAllButton view, float v) { - view.setDismissAlpha(v); - } - }; - - private final RecentsViewContainer mContainer; - private float mScrollAlpha = 1; - private float mContentAlpha = 1; - private float mVisibilityAlpha = 1; - private float mDismissAlpha = 1; - private float mFullscreenProgress = 1; - private float mGridProgress = 1; - - private boolean mIsRtl; - private float mNormalTranslationPrimary; - private float mFullscreenTranslationPrimary; - private float mGridTranslationPrimary; - private float mGridScrollOffset; - 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; - mContainer = RecentsViewContainer.containerFromContext(context); - FontManager.INSTANCE.get(context).overrideFont(this, attrs); - setBackground(DrawableTokens.BgOverviewClearAllButton.resolve(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 - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - RecentsPagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler(); - mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl); - } - - private RecentsView getRecentsView() { - return (RecentsView) getParent(); - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - public float getScrollAlpha() { - return mScrollAlpha; - } - - public void setContentAlpha(float alpha) { - if (mContentAlpha != alpha) { - mContentAlpha = alpha; - updateAlpha(); - } - } - - public void setVisibilityAlpha(float alpha) { - if (mVisibilityAlpha != alpha) { - mVisibilityAlpha = alpha; - updateAlpha(); - } - } - - public void setDismissAlpha(float alpha) { - if (mDismissAlpha != alpha) { - mDismissAlpha = alpha; - updateAlpha(); - } - } - - public void onRecentsViewScroll(int scroll, boolean gridEnabled) { - RecentsView recentsView = getRecentsView(); - if (recentsView == null) { - return; - } - - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight()); - if (orientationSize == 0) { - return; - } - - int clearAllScroll = recentsView.getClearAllScroll(); - int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll); - float shift = Math.min(adjustedScrollFromEdge, orientationSize); - mNormalTranslationPrimary = mIsRtl ? -shift : shift; - if (!gridEnabled) { - mNormalTranslationPrimary += mSidePadding; - } - applyPrimaryTranslation(); - applySecondaryTranslation(); - float clearAllSpacing = recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing(); - clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing; - mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0); - updateAlpha(); - } - - private void updateAlpha() { - final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha; - setAlpha(alpha); - setClickable(Math.min(alpha, 1) == 1); - } - - public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) { - mFullscreenTranslationPrimary = fullscreenTranslationPrimary; - applyPrimaryTranslation(); - } - - public void setGridTranslationPrimary(float gridTranslationPrimary) { - mGridTranslationPrimary = gridTranslationPrimary; - applyPrimaryTranslation(); - } - - public void setGridScrollOffset(float gridScrollOffset) { - mGridScrollOffset = gridScrollOffset; - } - - public void setScrollOffsetPrimary(float scrollOffsetPrimary) { - mScrollOffsetPrimary = scrollOffsetPrimary; - } - - public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { - float scrollAdjustment = 0; - if (fullscreenEnabled) { - scrollAdjustment += mFullscreenTranslationPrimary; - } - if (gridEnabled) { - scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset; - } - scrollAdjustment += mScrollOffsetPrimary; - return scrollAdjustment; - } - - public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { - return getScrollAdjustment(fullscreenEnabled, gridEnabled); - } - - /** - * Adjust translation when this TaskView is about to be shown fullscreen. - * - * @param progress: 0 = no translation; 1 = translate according to TaskVIew - * translations. - */ - public void setFullscreenProgress(float progress) { - mFullscreenProgress = progress; - applyPrimaryTranslation(); - } - - /** - * Moves ClearAllButton between carousel and 2 row grid. - * - * @param gridProgress 0 = carousel; 1 = 2 row grid. - */ - public void setGridProgress(float gridProgress) { - mGridProgress = gridProgress; - applyPrimaryTranslation(); - } - - private void applyPrimaryTranslation() { - RecentsView recentsView = getRecentsView(); - if (recentsView == null) { - return; - } - - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - orientationHandler.getPrimaryViewTranslate().set(this, - orientationHandler.getPrimaryValue(0f, getOriginalTranslationY()) - + mNormalTranslationPrimary + getFullscreenTrans( - mFullscreenTranslationPrimary) - + getGridTrans(mGridTranslationPrimary)); - } - - private void applySecondaryTranslation() { - RecentsView recentsView = getRecentsView(); - if (recentsView == null) { - return; - } - - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - orientationHandler.getSecondaryViewTranslate().set(this, - orientationHandler.getSecondaryValue(0f, getOriginalTranslationY())); - } - - private float getFullscreenTrans(float endTranslation) { - return mFullscreenProgress > 0 ? endTranslation : 0; - } - - private float getGridTrans(float endTranslation) { - return mGridProgress > 0 ? endTranslation : 0; - } - - /** - * Get the Y translation that is set in the original layout position, before - * scrolling. - */ - private float getOriginalTranslationY() { - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - if (deviceProfile.isTablet) { - if (enableGridOnlyOverview()) { - return (getRecentsView().getLastComputedTaskSize().height() - + deviceProfile.overviewTaskThumbnailTopMarginPx) / 2.0f - + deviceProfile.overviewRowSpacing; - } else { - return deviceProfile.overviewRowSpacing; - } - } - return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f; - } -} diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt new file mode 100644 index 00000000000..69c85eeaeb0 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.util.AttributeSet +import android.util.FloatProperty +import android.widget.Button +import com.android.launcher3.Flags.enableFocusOutline +import com.android.launcher3.R +import com.android.launcher3.util.KFloatProperty +import com.android.launcher3.util.MultiPropertyDelegate +import com.android.launcher3.util.MultiValueAlpha +import com.android.quickstep.util.BorderAnimator +import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator +import kotlin.math.abs +import kotlin.math.min + +class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + Button(context, attrs) { + + private val clearAllButtonAlpha = + object : MultiValueAlpha(this, Alpha.entries.size) { + override fun apply(value: Float) { + super.apply(value) + isClickable = value >= 1f + } + } + var scrollAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.SCROLL) + var contentAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.CONTENT) + var visibilityAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.VISIBILITY) + var dismissAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.DISMISS) + + var fullscreenProgress = 1f + set(value) { + if (field == value) { + return + } + field = value + applyPrimaryTranslation() + } + + /** + * Moves ClearAllButton between carousel and 2 row grid. + * + * 0 = carousel; 1 = 2 row grid. + */ + var gridProgress = 1f + set(value) { + if (field == value) { + return + } + field = value + applyPrimaryTranslation() + } + + private var normalTranslationPrimary = 0f + var fullscreenTranslationPrimary = 0f + set(value) { + if (field == value) { + return + } + field = value + applyPrimaryTranslation() + } + + var gridTranslationPrimary = 0f + set(value) { + if (field == value) { + return + } + field = value + applyPrimaryTranslation() + } + + /** Used to put the button at the middle in the secondary coordinate. */ + var taskAlignmentTranslationY = 0f + set(value) { + if (field == value) { + return + } + field = value + applySecondaryTranslation() + } + + var gridScrollOffset = 0f + var scrollOffsetPrimary = 0f + + private var sidePadding = 0 + var borderEnabled = false + set(value) { + if (field == value) { + return + } + field = value + focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true) + } + + private val focusBorderAnimator: BorderAnimator? = + if (enableFocusOutline()) + createSimpleBorderAnimator( + context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius), + context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width), + this::getBorderBounds, + this, + context + .obtainStyledAttributes(attrs, R.styleable.ClearAllButton) + .getColor( + R.styleable.ClearAllButton_focusBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR, + ), + ) + else null + + private fun getBorderBounds(bounds: Rect) { + bounds.set(0, 0, width, height) + val outlinePadding = + context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding) + // Make the value negative to form a padding between button and outline + bounds.inset(-outlinePadding, -outlinePadding) + } + + public override fun onFocusChanged( + gainFocus: Boolean, + direction: Int, + previouslyFocusedRect: Rect?, + ) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect) + if (borderEnabled) { + focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true) + } + } + + override fun draw(canvas: Canvas) { + focusBorderAnimator?.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) + sidePadding = + recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) } + ?: 0 + } + + private val recentsView: RecentsView<*, *>? + get() = parent as? RecentsView<*, *>? + + override fun hasOverlappingRendering() = false + + fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) { + val recentsView = recentsView ?: return + + val orientationSize = + recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat() + if (orientationSize == 0f) { + return + } + + val clearAllScroll = recentsView.clearAllScroll + val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat() + val shift = min(adjustedScrollFromEdge, orientationSize) + normalTranslationPrimary = if (isLayoutRtl) -shift else shift + if (!gridEnabled) { + normalTranslationPrimary += sidePadding.toFloat() + } + applyPrimaryTranslation() + applySecondaryTranslation() + var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing + clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing + scrollAlpha = + ((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast( + 0f + ) + } + + fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float { + var scrollAdjustment = 0f + if (fullscreenEnabled) { + scrollAdjustment += fullscreenTranslationPrimary + } + if (gridEnabled) { + scrollAdjustment += gridTranslationPrimary + gridScrollOffset + } + scrollAdjustment += scrollOffsetPrimary + return scrollAdjustment + } + + fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) = + getScrollAdjustment(fullscreenEnabled, gridEnabled) + + private fun applyPrimaryTranslation() { + val recentsView = recentsView ?: return + val orientationHandler = recentsView.pagedOrientationHandler + orientationHandler.primaryViewTranslate.set( + this, + (orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) + + normalTranslationPrimary + + getFullscreenTrans(fullscreenTranslationPrimary) + + getGridTrans(gridTranslationPrimary)), + ) + } + + private fun applySecondaryTranslation() { + val recentsView = recentsView ?: return + val orientationHandler = recentsView.pagedOrientationHandler + orientationHandler.secondaryViewTranslate.set( + this, + orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY), + ) + } + + private fun getFullscreenTrans(endTranslation: Float) = + if (fullscreenProgress > 0) endTranslation else 0f + + private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f + + companion object { + private enum class Alpha { + SCROLL, + CONTENT, + VISIBILITY, + DISMISS, + } + + @JvmField + val VISIBILITY_ALPHA: FloatProperty = + KFloatProperty(ClearAllButton::visibilityAlpha) + + @JvmField + val DISMISS_ALPHA: FloatProperty = + KFloatProperty(ClearAllButton::dismissAlpha) + } +} diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskContentView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskContentView.kt new file mode 100644 index 00000000000..ef044f4fda6 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskContentView.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.views + +import android.content.Context +import android.graphics.Outline +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import android.view.ViewOutlineProvider +import android.widget.FrameLayout + +class DesktopTaskContentView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { + var cornerRadius: Float = 0f + set(value) { + field = value + invalidateOutline() + } + + private val bounds = Rect() + + init { + clipToOutline = true + outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect(bounds, cornerRadius) + } + } + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + bounds.set(0, 0, w, h) + invalidateOutline() + } +} diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt index c56d7db5c61..bbd06be365f 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt @@ -15,271 +15,517 @@ */ package com.android.quickstep.views +import android.annotation.SuppressLint import android.content.Context -import android.graphics.Point +import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect -import android.graphics.drawable.LayerDrawable -import android.graphics.drawable.ShapeDrawable -import android.graphics.drawable.shapes.RoundRectShape +import android.graphics.Rect.intersects +import android.graphics.RectF import android.util.AttributeSet import android.util.Log +import android.util.Size +import android.view.Display.INVALID_DISPLAY +import android.view.Gravity import android.view.View -import android.view.ViewGroup +import android.view.ViewStub +import androidx.core.content.res.ResourcesCompat import androidx.core.view.updateLayoutParams -import app.lawnchair.theme.color.tokens.ColorTokens +import com.android.internal.hidden_from_bootclasspath.com.android.window.flags.Flags.enableDesktopRecentsTransitionsCornersBugfix +import com.android.launcher3.Flags.enableDesktopExplodedView +import com.android.launcher3.Flags.enableOverviewIconMenu +import com.android.launcher3.Flags.enableRefactorTaskThumbnail import com.android.launcher3.R +import com.android.launcher3.statehandlers.DesktopVisibilityController +import com.android.launcher3.testing.TestLogging +import com.android.launcher3.testing.shared.TestProtocol import com.android.launcher3.Utilities import com.android.launcher3.util.RunnableList import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.TransformingTouchDelegate import com.android.launcher3.util.ViewPool +import com.android.launcher3.util.rects.lerpRect import com.android.launcher3.util.rects.set import com.android.quickstep.BaseContainerInterface +import com.android.quickstep.DesktopFullscreenDrawParams +import com.android.quickstep.FullscreenDrawParams +import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle import com.android.quickstep.TaskOverlayFactory +import com.android.quickstep.ViewUtils +import com.android.quickstep.recents.di.RecentsDependencies +import com.android.quickstep.recents.di.get +import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData +import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel +import com.android.quickstep.recents.ui.viewmodel.TaskData +import com.android.quickstep.task.thumbnail.TaskThumbnailView +import com.android.quickstep.util.DesktopTask import com.android.quickstep.util.RecentsOrientedState -import com.android.systemui.shared.recents.model.Task +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.enableMultipleDesktops +import kotlin.math.roundToInt + +import app.lawnchair.theme.color.tokens.ColorTokens /** TaskView that contains all tasks that are part of the desktop. */ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - TaskView(context, attrs) { + TaskView( + context, + attrs, + type = TaskViewType.DESKTOP, + thumbnailFullscreenParams = DesktopFullscreenDrawParams(context), + ) { + val deskId + get() = desktopTask?.deskId ?: DesktopVisibilityController.INACTIVE_DESK_ID + + private var desktopTask: DesktopTask? = null + + private val contentViewFullscreenParams = FullscreenDrawParams(context) + + private val taskThumbnailViewDeprecatedPool = + if (!enableRefactorTaskThumbnail()) { + ViewPool( + context, + this, + R.layout.task_thumbnail_deprecated, + VIEW_POOL_MAX_SIZE, + VIEW_POOL_INITIAL_SIZE, + ) + } else null - 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( - context, - this, - R.layout.task_thumbnail, - VIEW_POOL_MAX_SIZE, - VIEW_POOL_INITIAL_SIZE - ) + if (enableRefactorTaskThumbnail()) { + ViewPool( + context, + this, + R.layout.task_thumbnail, + VIEW_POOL_MAX_SIZE, + VIEW_POOL_INITIAL_SIZE, + ) + } else null + private val tempPointF = PointF() - private val tempRect = Rect() - private lateinit var backgroundView: View + private val lastComputedTaskSize = Rect() private lateinit var iconView: TaskViewIcon - private var childCountAtInflation = 0 + private lateinit var contentView: DesktopTaskContentView + private lateinit var backgroundView: View + + private var viewModel: DesktopTaskViewModel? = null + + /** + * Holds the default (user placed) positions of task windows. This can be moved into the + * viewModel once RefactorTaskThumbnail has been launched. + */ + private var fullscreenTaskPositions: List = emptyList() + + /** + * When enableDesktopExplodedView is enabled, this controls the gradual transition from the + * default positions to the organized non-overlapping positions. + */ + var explodeProgress = 0.0f + set(value) { + field = value + positionTaskWindows() + } + + var remoteTargetHandles: Array? = null + set(value) { + field = value + positionTaskWindows() + } + + override val displayId: Int + get() = + if (enableMultipleDesktops(context)) { + desktopTask?.displayId ?: INVALID_DISPLAY + } else { + super.displayId + } + + private fun getRemoteTargetHandle(taskId: Int): RemoteTargetHandle? = + remoteTargetHandles?.firstOrNull { + it.transformParams.targetSet.firstAppTargetTaskId == taskId + } override fun onFinishInflate() { super.onFinishInflate() - backgroundView = - findViewById(R.id.background)!!.apply { + iconView = + (findViewById(R.id.icon) as TaskViewIcon).apply { + setIcon( + this, + ResourcesCompat.getDrawable( + context.resources, + R.drawable.ic_desktop_with_bg, + context.theme, + ), + ) + setText(resources.getText(R.string.recent_task_desktop)) + } + contentView = + findViewById(R.id.desktop_content).apply { updateLayoutParams { topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx } - background = - ShapeDrawable(RoundRectShape(FloatArray(8) { taskCornerRadius }, null, null)) - .apply { - setTint( - ColorTokens.Neutral2_300.resolveColor(context) - ) - } - } - 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))) + cornerRadius = contentViewFullscreenParams.currentCornerRadius + backgroundView = findViewById(R.id.background) + backgroundView.setBackgroundColor( + resources.getColor(ColorTokens.Neutral2_300.resolveColor(context), context.theme) + ) } - 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) + override fun inflateViewStubs() { + findViewById(R.id.icon) + ?.apply { + layoutResource = + if (enableOverviewIconMenu()) R.layout.icon_app_chip_view + else R.layout.icon_view + } + ?.inflate() + } + private fun positionTaskWindows() { if (taskContainers.isEmpty()) { return } val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx - containerHeight -= thumbnailTopMarginPx - BaseContainerInterface.getTaskDimension(context, 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]" - ) - } + val taskViewWidth = layoutParams.width + val taskViewHeight = layoutParams.height - thumbnailTopMarginPx + + BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF) + + val screenWidth = tempPointF.x.toInt() + val screenHeight = tempPointF.y.toInt() + val screenRect = Rect(0, 0, screenWidth, screenHeight) + val scaleWidth = taskViewWidth / screenWidth.toFloat() + val scaleHeight = taskViewHeight / screenHeight.toFloat() - // 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) - ) + val taskId = it.task.key.id + val fullscreenTaskPosition = + fullscreenTaskPositions.firstOrNull { it.taskId == taskId } ?: return + val overviewTaskPosition = + if (enableDesktopExplodedView()) { + viewModel!! + .organizedDesktopTaskPositions + .firstOrNull { it.taskId == taskId } + ?.let { organizedPosition -> + TEMP_OVERVIEW_TASK_POSITION.apply { + lerpRect( + fullscreenTaskPosition.bounds, + organizedPosition.bounds, + explodeProgress, + ) + } + } ?: fullscreenTaskPosition.bounds + } else { + fullscreenTaskPosition.bounds + } - // 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]" - ) + if (enableDesktopExplodedView()) { + getRemoteTargetHandle(taskId)?.let { remoteTargetHandle -> + val fromRect = + TEMP_FROM_RECTF.apply { + set(fullscreenTaskPosition.bounds) + scale(scaleWidth) + offset( + lastComputedTaskSize.left.toFloat(), + lastComputedTaskSize.top.toFloat(), + ) + } + val toRect = + TEMP_TO_RECTF.apply { + set(overviewTaskPosition) + scale(scaleWidth) + offset( + lastComputedTaskSize.left.toFloat(), + lastComputedTaskSize.top.toFloat(), + ) + } + val transform = Matrix() + transform.setRectToRect(fromRect, toRect, Matrix.ScaleToFit.FILL) + remoteTargetHandle.taskViewSimulator.setTaskRectTransform(transform) + remoteTargetHandle.taskViewSimulator.apply(remoteTargetHandle.transformParams) + } } - } - } - override fun onRecycle() { - super.onRecycle() - visibility = VISIBLE + val taskLeft = overviewTaskPosition.left * scaleWidth + val taskTop = overviewTaskPosition.top * scaleHeight + val taskWidth = overviewTaskPosition.width() * scaleWidth + val taskHeight = overviewTaskPosition.height() * scaleHeight + // TODO(b/394660950): Revisit the choice to update the layout when explodeProgress == 1. + // To run the explode animation in reverse, it may be simpler to use translation/scale + // for all cases where the progress is non-zero. + if (explodeProgress == 0.0f || explodeProgress == 1.0f) { + // Reset scaling and translation that may have been applied during animation. + it.snapshotView.apply { + scaleX = 1.0f + scaleY = 1.0f + translationX = 0.0f + translationY = 0.0f + } + + // Position the task to the same position as it would be on the desktop + it.snapshotView.updateLayoutParams { + gravity = Gravity.LEFT or Gravity.TOP + width = taskWidth.toInt() + height = taskHeight.toInt() + leftMargin = taskLeft.toInt() + topMargin = taskTop.toInt() + } + + if ( + enableDesktopRecentsTransitionsCornersBugfix() && enableRefactorTaskThumbnail() + ) { + it.thumbnailView.outlineBounds = + if (intersects(overviewTaskPosition, screenRect)) + Rect(overviewTaskPosition).apply { + intersectUnchecked(screenRect) + // Offset to 0,0 to transform into TaskThumbnailView's coordinate + // system. + offset(-overviewTaskPosition.left, -overviewTaskPosition.top) + left = (left * scaleWidth).roundToInt() + top = (top * scaleHeight).roundToInt() + right = (right * scaleWidth).roundToInt() + bottom = (bottom * scaleHeight).roundToInt() + } + else null + } + } else { + // During the animation, apply translation and scale such that the view is + // transformed to where we want, without triggering layout. + it.snapshotView.apply { + pivotX = 0.0f + pivotY = 0.0f + translationX = taskLeft - left + translationY = taskTop - top + scaleX = taskWidth / width.toFloat() + scaleY = taskHeight / height.toFloat() + } + } + } } /** Updates this desktop task to the gives task list defined in `tasks` */ fun bind( - tasks: List, + desktopTask: DesktopTask, orientedState: RecentsOrientedState, - taskOverlayFactory: TaskOverlayFactory + taskOverlayFactory: TaskOverlayFactory, ) { + this.desktopTask = desktopTask + // TODO(b/370495260): Minimized tasks should not be filtered with desktop exploded view + // support. + // Minimized tasks should not be shown in Overview. + val tasks = desktopTask.tasks.filterNot { it.isMinimized } 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() + val backgroundViewIndex = contentView.indexOfChild(backgroundView) + taskContainers = + tasks.map { task -> + val snapshotView = + if (enableRefactorTaskThumbnail()) { + taskThumbnailViewPool!!.view + } else { + taskThumbnailViewDeprecatedPool!!.view + } + contentView.addView(snapshotView, backgroundViewIndex + 1) - 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 + this, + task, + snapshotView, + iconView, + TransformingTouchDelegate(iconView.asView()), + SplitConfigurationOptions.STAGE_POSITION_UNDEFINED, + digitalWellBeingToast = null, + showWindowsView = null, + taskOverlayFactory, + ) } + onBind(orientedState) + } + + override fun onBind(orientedState: RecentsOrientedState) { + super.onBind(orientedState) + + if (enableRefactorTaskThumbnail()) { + viewModel = + DesktopTaskViewModel(organizeDesktopTasksUseCase = RecentsDependencies.get(context)) } - repeat(taskContainers.size - tasks.size) { - if (Utilities.ATLEAST_T) { - with(taskContainers.removeLast()) { - removeView(thumbnailViewDeprecated) - taskThumbnailViewPool.recycle(thumbnailViewDeprecated) - } - } else { - taskContainers.removeAt(taskContainers.lastIndex) - } + } + + override fun onRecycle() { + super.onRecycle() + desktopTask = null + explodeProgress = 0.0f + viewModel = null + visibility = VISIBLE + taskContainers.forEach { removeAndRecycleThumbnailView(it) } + } + + override fun setOrientationState(orientationState: RecentsOrientedState) { + super.setOrientationState(orientationState) + iconView.setIconOrientation(orientationState, isGridTask) + } + + @SuppressLint("RtlHardcoded") + override fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) { + super.updateTaskSize(lastComputedTaskSize, lastComputedGridTaskSize) + this.lastComputedTaskSize.set(lastComputedTaskSize) + + updateTaskPositions() + } + + override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) { + super.onTaskListVisibilityChanged(visible, changes) + if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) { + contentViewFullscreenParams.updateCornerRadius(context) } + } - setOrientationState(orientedState) + override fun onIconLoaded(taskContainer: TaskContainer) { + // Update contentDescription of snapshotView only, individual task icon is unused. + taskContainer.snapshotView.contentDescription = taskContainer.task.titleDescription } - override fun needsUpdate(dataChange: Int, flag: Int) = - if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false + override fun setIconState(container: TaskContainer, state: TaskData?) { + container.snapshotView.contentDescription = (state as? TaskData.Data)?.titleDescription + } + + // Ignoring [onIconUnloaded] as all tasks shares the same Desktop icon + override fun onIconUnloaded(taskContainer: TaskContainer) {} // 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) + container.dragLayer.getDescendantRectRelativeToSelf(contentView, bounds) } else { - bounds.set(backgroundView) + bounds.set(contentView) } } - override fun launchTaskAnimated(): RunnableList? { + private fun launchTaskWithDesktopController(animated: Boolean): RunnableList? { val recentsView = recentsView ?: return null + TestLogging.recordEvent( + TestProtocol.SEQUENCE_MAIN, + "launchDesktopFromRecents", + taskIds.contentToString(), + ) val endCallback = RunnableList() val desktopController = recentsView.desktopRecentsController checkNotNull(desktopController) { "recentsController is null" } - desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() } - Log.d(TAG, "launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}") + desktopController.launchDesktopFromRecents(this, animated) { + endCallback.executeAllAndDestroy() + } + Log.d( + TAG, + "launchTaskWithDesktopController: ${taskIds.contentToString()}, withRemoteTransition: $animated", + ) // 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) - } + override fun launchAsStaticTile() = launchTaskWithDesktopController(animated = true) - // Desktop tile can't be in split screen - override fun confirmSecondSplitSelectApp(): Boolean = false + override fun launchWithoutAnimation( + isQuickSwitch: Boolean, + callback: (launched: Boolean) -> Unit, + ) = launchTaskWithDesktopController(animated = false)?.add { callback(true) } ?: callback(false) + + // Return true when Task cannot be launched as fullscreen (i.e. in split select state) to skip + // putting DesktopTaskView to split as it's not supported. + override fun confirmSecondSplitSelectApp(): Boolean = + recentsView?.canLaunchFullscreenTask() != true // TODO(b/330685808) support overlay for Screenshot action override fun setOverlayEnabled(overlayEnabled: Boolean) {} override fun onFullscreenProgressChanged(fullscreenProgress: Float) { - // Don't show background while we are transitioning to/from fullscreen - backgroundView.visibility = if (fullscreenProgress > 0) INVISIBLE else VISIBLE + backgroundView.alpha = 1 - fullscreenProgress + } + + override fun updateFullscreenParams() { + super.updateFullscreenParams() + updateFullscreenParams(contentViewFullscreenParams) + contentView.cornerRadius = contentViewFullscreenParams.currentCornerRadius + } + + override fun addChildrenForAccessibility(outChildren: ArrayList) { + super.addChildrenForAccessibility(outChildren) + ViewUtils.addAccessibleChildToList(backgroundView, outChildren) + } + + fun removeTaskFromExplodedView(taskId: Int, animate: Boolean) { + if (!enableDesktopExplodedView()) { + Log.e( + TAG, + "removeTaskFromExplodedView called when enableDesktopExplodedView flag is false", + ) + return + } + + // Remove the task's [taskContainer] and its associated Views. + val taskContainer = getTaskContainerById(taskId) ?: return + removeAndRecycleThumbnailView(taskContainer) + taskContainer.destroy() + taskContainers = taskContainers.filterNot { it == taskContainer } + + // Dismiss the current DesktopTaskView if all its windows are closed. + if (taskContainers.isEmpty()) { + recentsView?.dismissTaskView(this, animate, /* removeTask= */ true) + } else { + // Otherwise, re-position the remaining task windows. + // TODO(b/353949276): Implement the re-layout animations. + updateTaskPositions() + } } - override fun updateCurrentFullscreenParams() { - super.updateCurrentFullscreenParams() - updateFullscreenParams(snapshotDrawParams) + private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) { + contentView.removeView(taskContainer.snapshotView) + if (enableRefactorTaskThumbnail()) { + taskThumbnailViewPool!!.recycle(taskContainer.thumbnailView) + } else { + taskThumbnailViewDeprecatedPool!!.recycle(taskContainer.thumbnailViewDeprecated) + } } - override fun getThumbnailFullscreenParams() = snapshotDrawParams + private fun updateTaskPositions() { + BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF) + val desktopSize = Size(tempPointF.x.toInt(), tempPointF.y.toInt()) + DEFAULT_BOUNDS.set(0, 0, desktopSize.width / 4, desktopSize.height / 4) + + fullscreenTaskPositions = + taskContainers.map { + DesktopTaskBoundsData(it.task.key.id, it.task.appBounds ?: DEFAULT_BOUNDS) + } + + if (enableDesktopExplodedView()) { + viewModel?.organizeDesktopTasks(desktopSize, fullscreenTaskPositions) + } + positionTaskWindows() + } companion object { private const val TAG = "DesktopTaskView" private const val DEBUG = false - private const val VIEW_POOL_MAX_SIZE = 10 + private const val VIEW_POOL_MAX_SIZE = 5 + // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool. private const val VIEW_POOL_INITIAL_SIZE = 0 - private val ORIGIN = Point(0, 0) + private val DEFAULT_BOUNDS = Rect() + // Temporaries used for various purposes to avoid allocations. + private val TEMP_OVERVIEW_TASK_POSITION = Rect() + private val TEMP_FROM_RECTF = RectF() + private val TEMP_TO_RECTF = RectF() } } diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java deleted file mode 100644 index a9236197dfc..00000000000 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.views; - -import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS; - -import static com.android.launcher3.Utilities.prefixTextWithIcon; -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; -import android.graphics.Outline; -import android.graphics.Paint; -import android.icu.text.MeasureFormat; -import android.icu.text.MeasureFormat.FormatWidth; -import android.icu.util.Measure; -import android.icu.util.MeasureUnit; -import android.os.UserHandle; -import android.util.Log; -import android.util.Pair; -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; - -import androidx.annotation.IntDef; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -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; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.time.Duration; -import java.util.Locale; - -public final class DigitalWellBeingToast { - - private static final float THRESHOLD_LEFT_ICON_ONLY = 0.4f; - private static final float THRESHOLD_RIGHT_ICON_ONLY = 0.6f; - - /** Will span entire width of taskView with full text */ - private static final int SPLIT_BANNER_FULLSCREEN = 0; - /** Used for grid task view, only showing icon and time */ - 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 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"; - - private final RecentsViewContainer mContainer; - private final TaskView mTaskView; - private final LauncherApps mLauncherApps; - - private final int mBannerHeight; - - private Task mTask; - private boolean mHasLimit; - - private long mAppRemainingTimeMs; - @Nullable - private View mBanner; - private ViewOutlineProvider mOldBannerOutlineProvider; - private float mBannerOffsetPercentage; - @Nullable - private SplitBounds mSplitBounds; - private float mSplitOffsetTranslationY; - private float mSplitOffsetTranslationX; - - private boolean mIsDestroyed = false; - - public DigitalWellBeingToast(RecentsViewContainer container, TaskView taskView) { - mContainer = container; - mTaskView = taskView; - 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); - mAppRemainingTimeMs = -1; - } - - private void setLimit(long appUsageLimitTimeMs, long appRemainingTimeMs) { - mAppRemainingTimeMs = appRemainingTimeMs; - mHasLimit = true; - 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); - - mTaskView.setContentDescription( - getContentDescriptionForTask(mTask, appUsageLimitTimeMs, appRemainingTimeMs)); - } - - public String getText() { - return getText(mAppRemainingTimeMs, false /* forContentDesc */); - } - - public boolean hasLimit() { - return mHasLimit; - } - - public void initialize(Task task) { - if (mIsDestroyed) { - throw new IllegalStateException("Cannot re-initialize a destroyed toast"); - } - mTask = task; - 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 (mIsDestroyed) { - return; - } - if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) { - setNoLimit(); - } else { - setLimit(appUsageLimitTimeMs, appRemainingTimeMs); - } - }); - }); - } - - /** - * 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 - || !mContainer.getDeviceProfile().isTablet - || mTaskView.isFocusedTask()) { - return SPLIT_BANNER_FULLSCREEN; - } - - // For portrait grid only height of task changes, not width. So we keep the text - // the same - 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) { - return mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY - ? SPLIT_GRID_BANNER_SMALL - : SPLIT_GRID_BANNER_LARGE; - } else { - return mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY - ? SPLIT_GRID_BANNER_SMALL - : SPLIT_GRID_BANNER_LARGE; - } - } - - private String getReadableDuration( - Duration duration, - @StringRes int durationLessThanOneMinuteStringId) { - int hours = Math.toIntExact(duration.toHours()); - int minutes = Math.toIntExact(duration.minusHours(hours).toMinutes()); - - // 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(), FormatWidth.NARROW) - .formatMeasures( - new Measure(hours, MeasureUnit.HOUR), - new Measure(minutes, MeasureUnit.MINUTE)); - } - - // Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced). - if (hours > 0) { - return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures( - new Measure(hours, MeasureUnit.HOUR)); - } - - // Apply FormatWidth.WIDE if only the minute part is non-zero (unless forced). - if (minutes > 0) { - 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 mContainer.asContext().getString(durationLessThanOneMinuteStringId); - } - - // Otherwise, return 0-minute string. - return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures( - new Measure(0, MeasureUnit.MINUTE)); - } - - /** - * 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} - */ - private String getText(long remainingTime, boolean forContentDesc) { - final Duration duration = Duration.ofMillis( - remainingTime > MINUTE_MS ? (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS : remainingTime); - String readableDuration = getReadableDuration(duration, - 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 (splitBannerConfig == SPLIT_GRID_BANNER_SMALL) { - // show no text - return ""; - } else { // SPLIT_GRID_BANNER_LARGE - // only show time - return readableDuration; - } - } - - public void openAppUsageSettings(View view) { - final Intent intent = new Intent(OPEN_APP_USAGE_SETTINGS_TEMPLATE) - .putExtra(Intent.EXTRA_PACKAGE_NAME, - mTask.getTopComponent().getPackageName()) - .addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - try { - final RecentsViewContainer container = RecentsViewContainer.containerFromContext(view.getContext()); - final ActivityOptions options = ActivityOptions.makeScaleUpAnimation( - view, 0, 0, - view.getWidth(), view.getHeight()); - container.asContext().startActivity(intent, options.toBundle()); - - // TODO: add WW logging on the app usage settings click. - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Failed to open app usage settings for task " - + mTask.getTopComponent().getPackageName(), e); - } - } - - private String getContentDescriptionForTask( - Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) { - return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ? mContainer.asContext().getString( - R.string.task_contents_description_with_remaining_time, - task.titleDescription, - getText(appRemainingTimeMs, true /* forContentDesc */)) : task.titleDescription; - } - - private void replaceBanner(@Nullable View view) { - resetOldBanner(); - setBanner(view); - } - - private void resetOldBanner() { - if (mBanner != null) { - mBanner.setOutlineProvider(mOldBannerOutlineProvider); - mTaskView.removeView(mBanner); - mBanner.setOnClickListener(null); - mContainer.getViewCache().recycleView(R.layout.digital_wellbeing_toast, mBanner); - } - } - - private void setBanner(@Nullable View view) { - mBanner = view; - if (mBanner != null && mTaskView.getRecentsView() != null) { - setupAndAddBanner(); - setBannerOutline(); - } - } - - private void setupAndAddBanner() { - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mBanner.getLayoutParams(); - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams) mTaskView.getFirstThumbnailViewDeprecated() - .getLayoutParams()).bottomMargin; - RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler(); - Pair translations = orientationHandler - .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(), - mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile, - mTaskView.getThumbnailViews(), mTask.key.id, mBanner); - mSplitOffsetTranslationX = translations.first; - mSplitOffsetTranslationY = translations.second; - updateTranslationY(); - updateTranslationX(); - mTaskView.addView(mBanner); - } - - private void setBannerOutline() { - // TODO(b\273367585) to investigate why mBanner.getOutlineProvider() can be null - mOldBannerOutlineProvider = mBanner.getOutlineProvider() != null - ? mBanner.getOutlineProvider() - : ViewOutlineProvider.BACKGROUND; - - mBanner.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - mOldBannerOutlineProvider.getOutline(view, outline); - float verticalTranslation = -view.getTranslationY() + mSplitOffsetTranslationY; - outline.offset(0, Math.round(verticalTranslation)); - } - }); - mBanner.setClipToOutline(true); - } - - void updateBannerOffset(float offsetPercentage) { - if (mBannerOffsetPercentage != offsetPercentage) { - mBannerOffsetPercentage = offsetPercentage; - if (mBanner != null) { - updateTranslationY(); - mBanner.invalidateOutline(); - } - } - } - - private void updateTranslationY() { - if (mBanner == null) { - return; - } - - mBanner.setTranslationY( - (mBannerOffsetPercentage * mBannerHeight) + mSplitOffsetTranslationY); - } - - private void updateTranslationX() { - if (mBanner == null) { - return; - } - - mBanner.setTranslationX(mSplitOffsetTranslationX); - } - - void setBannerColorTint(int color, float amount) { - if (mBanner == null) { - return; - } - if (amount == 0) { - mBanner.setLayerType(View.LAYER_TYPE_NONE, null); - } - Paint layerPaint = new Paint(); - layerPaint.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount)); - mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint); - mBanner.setLayerPaint(layerPaint); - } - - void setBannerVisibility(int visibility) { - if (mBanner == null) { - return; - } - - 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/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt new file mode 100644 index 00000000000..5c4a35da633 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views + +import android.annotation.SuppressLint +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 +import android.graphics.Outline +import android.graphics.Paint +import android.icu.text.MeasureFormat +import android.icu.util.Measure +import android.icu.util.MeasureUnit +import android.os.UserHandle +import android.provider.Settings +import android.util.AttributeSet +import android.util.Log +import android.view.View +import android.view.ViewOutlineProvider +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.annotation.VisibleForTesting +import androidx.core.util.component1 +import androidx.core.util.component2 +import androidx.core.view.isVisible +import com.android.launcher3.R +import com.android.launcher3.Utilities +import com.android.launcher3.util.Executors +import com.android.launcher3.util.SplitConfigurationOptions +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.StagePosition +import com.android.quickstep.TaskUtils +import com.android.systemui.shared.recents.model.Task +import java.time.Duration +import java.util.Locale + +@SuppressLint("AppCompatCustomView") +class DigitalWellBeingToast +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0, +) : TextView(context, attrs, defStyleAttr, defStyleRes) { + private val recentsViewContainer: RecentsViewContainer = + RecentsViewContainer.containerFromContext(context) + + private val launcherApps: LauncherApps? = context.getSystemService(LauncherApps::class.java) + + private val bannerHeight = + context.resources.getDimensionPixelSize(R.dimen.digital_wellbeing_toast_height) + + private lateinit var task: Task + private lateinit var taskView: TaskView + private lateinit var snapshotView: View + @StagePosition private var stagePosition = STAGE_POSITION_UNDEFINED + + private var appRemainingTimeMs: Long = 0 + private var splitOffsetTranslationY = 0f + set(value) { + if (field != value) { + field = value + updateTranslationY() + } + } + + private var isDestroyed = false + + var hasLimit = false + var splitBounds: SplitConfigurationOptions.SplitBounds? = null + var bannerOffsetPercentage = 0f + set(value) { + if (field != value) { + field = value + updateTranslationY() + } + } + + init { + setOnClickListener(::openAppUsageSettings) + outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + BACKGROUND.getOutline(view, outline) + val verticalTranslation = splitOffsetTranslationY - translationY + outline.offset(0, Math.round(verticalTranslation)) + } + } + clipToOutline = true + } + + private fun setNoLimit() { + isVisible = false + hasLimit = false + appRemainingTimeMs = -1 + setContentDescription(appUsageLimitTimeMs = -1, appRemainingTimeMs = -1) + } + + private fun setLimit(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) { + isVisible = true + hasLimit = true + this.appRemainingTimeMs = appRemainingTimeMs + setContentDescription(appUsageLimitTimeMs, appRemainingTimeMs) + text = Utilities.prefixTextWithIcon(context, R.drawable.ic_hourglass_top, getBannerText()) + } + + private fun setContentDescription(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) { + val contentDescription = + getContentDescriptionForTask(task, appUsageLimitTimeMs, appRemainingTimeMs) + snapshotView.contentDescription = contentDescription + } + + fun initialize() { + check(!isDestroyed) { "Cannot re-initialize a destroyed toast" } + setupTranslations() + Executors.ORDERED_BG_EXECUTOR.execute { + var usageLimit: AppUsageLimit? = null + try { + usageLimit = + launcherApps?.getAppUsageLimit( + task.topComponent.packageName, + UserHandle.of(task.key.userId), + ) + } catch (e: Exception) { + Log.e(TAG, "Error initializing digital well being toast", e) + } + val appUsageLimitTimeMs = usageLimit?.totalUsageLimit ?: -1 + val appRemainingTimeMs = usageLimit?.usageRemaining ?: -1 + + taskView.post { + if (isDestroyed) return@post + if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) { + setNoLimit() + } else { + setLimit(appUsageLimitTimeMs, appRemainingTimeMs) + } + } + } + } + + /** Bind the DWB toast to its dependencies. */ + fun bind( + task: Task, + taskView: TaskView, + snapshotView: View, + @StagePosition stagePosition: Int, + ) { + this.task = task + this.taskView = taskView + this.snapshotView = snapshotView + this.stagePosition = stagePosition + isDestroyed = false + } + + /** Mark the DWB toast as destroyed and hide it. */ + fun destroy() { + isVisible = false + isDestroyed = true + } + + private fun getSplitBannerConfig(): SplitBannerConfig { + val splitBounds = splitBounds + return when { + splitBounds == null || + !recentsViewContainer.deviceProfile.isTablet || + taskView.isLargeTile -> SplitBannerConfig.SPLIT_BANNER_FULLSCREEN + // For portrait grid only height of task changes, not width. So we keep the text the + // same + !recentsViewContainer.deviceProfile.isLeftRightSplit -> + SplitBannerConfig.SPLIT_GRID_BANNER_LARGE + // For landscape grid, for 30% width we only show icon, otherwise show icon and time + task.key.id == splitBounds.leftTopTaskId -> + if (splitBounds.leftTopTaskPercent < THRESHOLD_LEFT_ICON_ONLY) + SplitBannerConfig.SPLIT_GRID_BANNER_SMALL + else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE + else -> + if (splitBounds.leftTopTaskPercent > THRESHOLD_RIGHT_ICON_ONLY) + SplitBannerConfig.SPLIT_GRID_BANNER_SMALL + else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE + } + } + + private fun getReadableDuration( + duration: Duration, + @StringRes durationLessThanOneMinuteStringId: Int, + ): String { + val hours = Math.toIntExact(duration.toHours()) + val minutes = Math.toIntExact(duration.minusHours(hours.toLong()).toMinutes()) + return when { + // Apply FormatWidth.WIDE if both the hour part and the minute part are non-zero. + hours > 0 && minutes > 0 -> + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.NARROW) + .formatMeasures( + Measure(hours, MeasureUnit.HOUR), + Measure(minutes, MeasureUnit.MINUTE), + ) + // Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced). + hours > 0 -> + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures(Measure(hours, MeasureUnit.HOUR)) + // Apply FormatWidth.WIDE if only the minute part is non-zero (unless forced). + minutes > 0 -> + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures(Measure(minutes, MeasureUnit.MINUTE)) + // Use a specific string for usage less than one minute but non-zero. + duration > Duration.ZERO -> context.getString(durationLessThanOneMinuteStringId) + // Otherwise, return 0-minute string. + else -> + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures(Measure(0, MeasureUnit.MINUTE)) + } + } + + /** + * Returns text to show for the banner depending on [.getSplitBannerConfig] If {@param + * forContentDesc} is `true`, this will always return the full string corresponding to + * [.SPLIT_BANNER_FULLSCREEN] + */ + @JvmOverloads + @VisibleForTesting + fun getBannerText( + remainingTime: Long = appRemainingTimeMs, + forContentDesc: Boolean = false, + ): String { + val duration = + Duration.ofMillis( + if (remainingTime > MINUTE_MS) + (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS + else remainingTime + ) + val readableDuration = + getReadableDuration( + duration, + R.string.shorter_duration_less_than_one_minute, /* forceFormatWidth */ + ) + val splitBannerConfig = getSplitBannerConfig() + return when { + forContentDesc || splitBannerConfig == SplitBannerConfig.SPLIT_BANNER_FULLSCREEN -> + context.getString(R.string.time_left_for_app, readableDuration) + // show no text + splitBannerConfig == SplitBannerConfig.SPLIT_GRID_BANNER_SMALL -> "" + // SPLIT_GRID_BANNER_LARGE only show time + else -> readableDuration + } + } + + private fun openAppUsageSettings(view: View) { + val intent = + Intent(OPEN_APP_USAGE_SETTINGS_TEMPLATE) + .putExtra(Intent.EXTRA_PACKAGE_NAME, task.topComponent.packageName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + try { + val options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.width, view.height) + context.startActivity(intent, options.toBundle()) + + // TODO: add WW logging on the app usage settings click. + } catch (e: ActivityNotFoundException) { + Log.e( + TAG, + "Failed to open app usage settings for task " + task.topComponent.packageName, + e, + ) + } + } + + private fun getContentDescriptionForTask( + task: Task, + appUsageLimitTimeMs: Long, + appRemainingTimeMs: Long, + ): String? = + if (appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0) + context.getString( + R.string.task_contents_description_with_remaining_time, + task.titleDescription, + getBannerText(appRemainingTimeMs, true /* forContentDesc */), + ) + else task.titleDescription + + fun setupLayout() { + val snapshotWidth: Int + val snapshotHeight: Int + val splitBounds = splitBounds + if (splitBounds == null) { + snapshotWidth = taskView.layoutParams.width + snapshotHeight = + taskView.layoutParams.height - + recentsViewContainer.deviceProfile.overviewTaskThumbnailTopMarginPx + } else { + val groupedTaskSize = + taskView.pagedOrientationHandler.getGroupedTaskViewSizes( + recentsViewContainer.deviceProfile, + splitBounds, + taskView.layoutParams.width, + taskView.layoutParams.height, + ) + if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) { + snapshotWidth = groupedTaskSize.first.x + snapshotHeight = groupedTaskSize.first.y + } else { + snapshotWidth = groupedTaskSize.second.x + snapshotHeight = groupedTaskSize.second.y + } + } + taskView.pagedOrientationHandler.updateDwbBannerLayout( + taskView.layoutParams.width, + taskView.layoutParams.height, + taskView is GroupedTaskView, + recentsViewContainer.deviceProfile, + snapshotWidth, + snapshotHeight, + this, + ) + } + + private fun setupTranslations() { + val (translationX, translationY) = + taskView.pagedOrientationHandler.getDwbBannerTranslations( + taskView.layoutParams.width, + taskView.layoutParams.height, + splitBounds, + recentsViewContainer.deviceProfile, + taskView.snapshotViews, + task.key.id, + this, + ) + this.translationX = translationX + this.splitOffsetTranslationY = translationY + } + + private fun updateTranslationY() { + translationY = bannerOffsetPercentage * bannerHeight + splitOffsetTranslationY + invalidateOutline() + } + + fun setColorTint(color: Int, amount: Float) { + if (amount == 0f) { + setLayerType(View.LAYER_TYPE_NONE, null) + } + val layerPaint = Paint() + layerPaint.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount)) + setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint) + setLayerPaint(layerPaint) + } + + private fun getAccessibilityActionId(): Int = + if (splitBounds?.rightBottomTaskId == task.key.id) + R.id.action_digital_wellbeing_bottom_right + else R.id.action_digital_wellbeing_top_left + + fun getDWBAccessibilityAction(): AccessibilityNodeInfo.AccessibilityAction? { + if (!hasLimit) return null + val label = + if (taskView.containsMultipleTasks()) + context.getString( + R.string.split_app_usage_settings, + TaskUtils.getTitle(context, task), + ) + else context.getString(R.string.accessibility_app_usage_settings) + return AccessibilityNodeInfo.AccessibilityAction(getAccessibilityActionId(), label) + } + + fun handleAccessibilityAction(action: Int): Boolean { + if (getAccessibilityActionId() != action) return false + openAppUsageSettings(taskView) + return true + } + + companion object { + private const val THRESHOLD_LEFT_ICON_ONLY = 0.4f + private const val THRESHOLD_RIGHT_ICON_ONLY = 0.6f + + enum class SplitBannerConfig { + /** Will span entire width of taskView with full text */ + SPLIT_BANNER_FULLSCREEN, + /** Used for grid task view, only showing icon and time */ + SPLIT_GRID_BANNER_LARGE, + /** Used for grid task view, only showing icon */ + SPLIT_GRID_BANNER_SMALL, + } + + val OPEN_APP_USAGE_SETTINGS_TEMPLATE: Intent = Intent(Settings.ACTION_APP_USAGE_SETTINGS) + const val MINUTE_MS: Int = 60000 + + private const val TAG = "DigitalWellBeingToast" + } +} diff --git a/quickstep/src/com/android/quickstep/views/FixedSizeImageView.kt b/quickstep/src/com/android/quickstep/views/FixedSizeImageView.kt new file mode 100644 index 00000000000..c8930165b58 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/FixedSizeImageView.kt @@ -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.quickstep.views + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.ViewGroup +import android.widget.ImageView + +/** + * An [ImageView] that does not requestLayout() unless setLayoutParams is called. + * + * This is useful, particularly during animations, for [ImageView]s that are not supposed to be + * resized. + */ +@SuppressLint("AppCompatCustomView") +class FixedSizeImageView : ImageView { + private var shouldRequestLayoutOnChanges = false + + 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 setLayoutParams(params: ViewGroup.LayoutParams?) { + shouldRequestLayoutOnChanges = true + super.setLayoutParams(params) + shouldRequestLayoutOnChanges = false + } + + override fun requestLayout() { + if (shouldRequestLayoutOnChanges) { + super.requestLayout() + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt index e024995aa30..6bbd6b2c153 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt +++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt @@ -57,6 +57,7 @@ open class FloatingAppPairBackground( private val container: RecentsViewContainer private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val dividerPaint = Paint(Paint.ANTI_ALIAS_FLAG) // Animation interpolators protected val expandXInterpolator: Interpolator @@ -105,13 +106,15 @@ open class FloatingAppPairBackground( ) // Find device-specific measurements - deviceCornerRadius = QuickStepContract.getWindowCornerRadius(container.asContext()) + val resources = context.resources + deviceCornerRadius = QuickStepContract.getWindowCornerRadius(context) deviceHalfDividerSize = - container.asContext().resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f + resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f val dividerCenterPos = dividerPos + deviceHalfDividerSize desiredSplitRatio = if (dp.isLeftRightSplit) dividerCenterPos / dp.widthPx else dividerCenterPos / dp.heightPx + dividerPaint.color = resources.getColor(R.color.taskbar_background_dark, null /*theme*/) } override fun draw(canvas: Canvas) { @@ -153,8 +156,12 @@ open class FloatingAppPairBackground( val leftSide = RectF(0f, 0f, dividerCenterPos - changingDividerSize, height) // The right half of the background image val rightSide = RectF(dividerCenterPos + changingDividerSize, 0f, width, height) + // Middle part is for divider background + val middleRect = RectF(leftSide.right - deviceHalfDividerSize, 0f, + rightSide.left + deviceHalfDividerSize, height) // Draw background + canvas.drawRect(middleRect, dividerPaint) drawCustomRoundedRect( canvas, leftSide, @@ -251,8 +258,12 @@ open class FloatingAppPairBackground( val topSide = RectF(0f, 0f, width, dividerCenterPos - changingDividerSize) // The bottom half of the background image val bottomSide = RectF(0f, dividerCenterPos + changingDividerSize, width, height) + // Middle part is for divider background + val middleRect = RectF(0f, topSide.bottom - deviceHalfDividerSize, + width, bottomSide.top + deviceHalfDividerSize) // Draw background + canvas.drawRect(middleRect, dividerPaint) drawCustomRoundedRect( canvas, topSide, diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java index e5f241fdbeb..b060168b1bc 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java @@ -33,7 +33,6 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.widget.LauncherAppWidgetHostView; -import com.android.launcher3.widget.RoundedCornerEnforcement; import java.util.stream.IntStream; @@ -180,8 +179,7 @@ private static boolean isSupportedDrawable(Drawable drawable) { /** Corner radius from source view's outline, or enforced view. */ private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) { - if (RoundedCornerEnforcement.isRoundedCornerEnabled() - && hostView.hasEnforcedCornerRadius()) { + if (hostView.hasEnforcedCornerRadius()) { return hostView.getEnforcedCornerRadius(); } else if (Utilities.ATLEAST_S && v.getOutlineProvider() instanceof RemoteViewOutlineProvider diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java index fc52b8ee939..b719ee5b73a 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java @@ -18,6 +18,7 @@ import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.annotation.TargetApi; +import android.app.TaskInfo; import android.content.Context; import android.graphics.Matrix; import android.graphics.RectF; @@ -122,10 +123,9 @@ protected void onDetachedFromWindow() { @Override public void onGlobalLayout() { - if (isUninitialized()) - return; - positionViews(); - if (mOnTargetChangeRunnable != null) { + if (isUninitialized()) return; + boolean positionsChanged = positionViews(); + if (mOnTargetChangeRunnable != null && positionsChanged) { mOnTargetChangeRunnable.run(); } } @@ -143,8 +143,7 @@ public void setFastFinishRunnable(Runnable runnable) { /** Callback at the end or early exit of the animation. */ @Override public void fastFinish() { - if (isUninitialized()) - return; + if (isUninitialized()) return; Runnable fastFinishRunnable = mFastFinishRunnable; if (fastFinishRunnable != null) { fastFinishRunnable.run(); @@ -189,23 +188,18 @@ private void init(DragLayer dragLayer, LauncherAppWidgetHostView originalView, /** * Updates the position and opacity of the floating widget's components. * - * @param backgroundPosition the new position of the widget's background - * relative to the + * @param backgroundPosition the new position of the widget's background relative to the * {@link FloatingWidgetView}'s parent - * @param floatingWidgetAlpha the overall opacity of the - * {@link FloatingWidgetView} + * @param floatingWidgetAlpha the overall opacity of the {@link FloatingWidgetView} * @param foregroundAlpha the opacity of the foreground layer - * @param fallbackBackgroundAlpha the opacity of the fallback background used - * when the App + * @param fallbackBackgroundAlpha the opacity of the fallback background used when the App * Widget doesn't have a background - * @param cornerRadiusProgress progress of the corner radius animation, where - * 0 is the + * @param cornerRadiusProgress progress of the corner radius animation, where 0 is the * original radius and 1 is the window radius */ public void update(RectF backgroundPosition, float floatingWidgetAlpha, float foregroundAlpha, float fallbackBackgroundAlpha, float cornerRadiusProgress) { - if (isUninitialized() || mAppTargetIsTranslucent) - return; + if (isUninitialized() || mAppTargetIsTranslucent) return; setAlpha(floatingWidgetAlpha); mBackgroundView.update(cornerRadiusProgress, fallbackBackgroundAlpha); mAppWidgetView.setAlpha(foregroundAlpha); @@ -220,34 +214,61 @@ public void setPositionOffsetY(float y) { } /** - * Sets the layout parameters of the floating view and its background view - * child. + * Sets the layout parameters of the floating view and its background view child. + * @return true if any of the views positions change due to this call. */ - private void positionViews() { + private boolean positionViews() { + boolean positionsChanged = false; + LayoutParams layoutParams = (LayoutParams) getLayoutParams(); - layoutParams.setMargins(0, 0, 0, 0); - setLayoutParams(layoutParams); + + if (layoutParams.topMargin != 0 || layoutParams.bottomMargin != 0 + || layoutParams.rightMargin != 0 || layoutParams.leftMargin != 0) { + positionsChanged = true; + layoutParams.setMargins(0, 0, 0, 0); + setLayoutParams(layoutParams); + } // FloatingWidgetView layout is forced LTR - mBackgroundView.setTranslationX(mBackgroundPosition.left); - mBackgroundView.setTranslationY(mBackgroundPosition.top + mIconOffsetY); + float targetY = mBackgroundPosition.top + mIconOffsetY; + if (mBackgroundView.getTranslationX() != mBackgroundPosition.left + || mBackgroundView.getTranslationY() != targetY) { + positionsChanged = true; + mBackgroundView.setTranslationX(mBackgroundPosition.left); + mBackgroundView.setTranslationY(targetY); + } + LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams(); - backgroundParams.leftMargin = 0; - backgroundParams.topMargin = 0; - backgroundParams.width = (int) mBackgroundPosition.width(); - backgroundParams.height = (int) mBackgroundPosition.height(); - mBackgroundView.setLayoutParams(backgroundParams); + if (backgroundParams.leftMargin != 0 || backgroundParams.topMargin != 0 + || backgroundParams.width != Math.round(mBackgroundPosition.width()) + || backgroundParams.height != Math.round(mBackgroundPosition.height())) { + positionsChanged = true; + + backgroundParams.leftMargin = 0; + backgroundParams.topMargin = 0; + backgroundParams.width = Math.round(mBackgroundPosition.width()); + backgroundParams.height = Math.round(mBackgroundPosition.height()); + mBackgroundView.setLayoutParams(backgroundParams); + } if (mForegroundOverlayView != null) { sTmpMatrix.reset(); - float foregroundScale = mBackgroundPosition.width() / mAppWidgetBackgroundView.getWidth(); + float foregroundScale = + mBackgroundPosition.width() / mAppWidgetBackgroundView.getWidth(); sTmpMatrix.setTranslate(-mBackgroundOffset.left - mAppWidgetView.getLeft(), -mBackgroundOffset.top - mAppWidgetView.getTop()); sTmpMatrix.postScale(foregroundScale, foregroundScale); sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top + mIconOffsetY); - mForegroundOverlayView.setMatrix(sTmpMatrix); + + // We use the animation matrix here, because calling setMatrix on the GhostView + // actually sets the animation matrix, not the regular one. + if (!sTmpMatrix.equals(mForegroundOverlayView.getAnimationMatrix())) { + positionsChanged = true; + mForegroundOverlayView.setMatrix(sTmpMatrix); + } } + return positionsChanged; } private void finish(DragLayer dragLayer) { @@ -284,12 +305,10 @@ private void recycle() { } /** - * Configures and returns a an instance of {@link FloatingWidgetView} matching - * the appearance of + * Configures and returns a an instance of {@link FloatingWidgetView} matching the appearance of * {@param originalView}. * - * @param widgetBackgroundPosition a {@link RectF} that will be updated with the - * widget's + * @param widgetBackgroundPosition a {@link RectF} that will be updated with the widget's * background bounds * @param windowSize the size of the window when launched * @param windowCornerRadius the corner radius of the window @@ -300,8 +319,8 @@ public static FloatingWidgetView getFloatingWidgetView(QuickstepLauncher launche int fallbackBackgroundColor) { final DragLayer dragLayer = launcher.getDragLayer(); ViewGroup parent = (ViewGroup) dragLayer.getParent(); - FloatingWidgetView floatingView = launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, - parent); + FloatingWidgetView floatingView = + launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, parent); floatingView.recycle(); floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowSize, @@ -311,20 +330,24 @@ public static FloatingWidgetView getFloatingWidgetView(QuickstepLauncher launche } /** - * Extract a background color from a target's task description, or fall back to - * the given + * Extract a background color from a target's task description, or fall back to the given * context's theme background color. */ public static int getDefaultBackgroundColor( - Context context, RemoteAnimationTarget target) { - return (target != null && target.taskInfo != null - && target.taskInfo.taskDescription != null) - ? target.taskInfo.taskDescription.getBackgroundColor() - : Themes.getColorBackground(context); + Context context, @Nullable RemoteAnimationTarget target) { + final int fallbackColor = Themes.getColorBackground(context); + if (target == null) { + return fallbackColor; + } + final TaskInfo taskInfo = target.taskInfo; + if (taskInfo == null) { + return fallbackColor; + } + return taskInfo.taskDescription.getBackgroundColor(); } private static void getRelativePosition(View descendant, View ancestor, RectF position) { - float[] points = new float[] { 0, 0, descendant.getWidth(), descendant.getHeight() }; + float[] points = new float[]{0, 0, descendant.getWidth(), descendant.getHeight()}; Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, points, false /* includeRootScroll */, true /* ignoreTransform */); position.set( diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt index d6a3376c533..faa9e2893b4 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt @@ -21,11 +21,12 @@ import android.graphics.PointF import android.util.AttributeSet import android.util.Log import android.view.View +import android.view.ViewStub import com.android.internal.jank.Cuj import com.android.launcher3.Flags.enableOverviewIconMenu +import com.android.launcher3.Flags.enableRefactorTaskThumbnail 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 @@ -33,12 +34,11 @@ import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_O 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 +import com.android.wm.shell.Flags.enableFlexibleTwoAppSplit +import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition /** * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks @@ -51,7 +51,16 @@ import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosi * (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) { + TaskView(context, attrs, type = TaskViewType.GROUPED) { + + private val MINIMUM_RATIO_TO_SHOW_ICON = 0.2f + + val leftTopTaskContainer: TaskContainer + get() = taskContainers[0] + + val rightBottomTaskContainer: TaskContainer + get() = taskContainers[1] + // TODO(b/336612373): Support new TTV for GroupedTaskView var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null private set @@ -67,52 +76,41 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu 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 - ) - ) - } + val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID + pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds( + leftTopTaskContainer.snapshotView, + rightBottomTaskContainer.snapshotView, + widthSize, + heightSize, + splitBoundsConfig, + container.deviceProfile, + layoutDirection == LAYOUT_DIRECTION_RTL, + inSplitSelection, + ) + if (!enableOverviewIconMenu()) { updateIconPlacement() } } + override fun inflateViewStubs() { + super.inflateViewStubs() + findViewById(R.id.bottomright_snapshot) + ?.apply { + layoutResource = + if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail + else R.layout.task_thumbnail_deprecated + } + ?.inflate() + findViewById(R.id.bottomRight_icon) + ?.apply { + layoutResource = + if (enableOverviewIconMenu()) R.layout.icon_app_chip_view + else R.layout.icon_view + } + ?.inflate() + } + override fun onRecycle() { super.onRecycle() splitBoundsConfig = null @@ -133,37 +131,23 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu R.id.snapshot, R.id.icon, R.id.show_windows, + R.id.digital_wellbeing_toast, STAGE_POSITION_TOP_OR_LEFT, - taskOverlayFactory + taskOverlayFactory, ), createTaskContainer( secondaryTask, R.id.bottomright_snapshot, R.id.bottomRight_icon, R.id.show_windows_right, + R.id.bottomRight_digital_wellbeing_toast, STAGE_POSITION_BOTTOM_OR_RIGHT, - taskOverlayFactory - ) + 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) + this.splitBoundsConfig = splitBoundsConfig + taskContainers.forEach { it.digitalWellBeingToast?.splitBounds = splitBoundsConfig } + onBind(orientedState) } override fun setOrientationState(orientationState: RecentsOrientedState) { @@ -174,7 +158,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu container.deviceProfile, it, layoutParams.width, - layoutParams.height + layoutParams.height, ) val iconViewMarginStart = resources.getDimensionPixelSize( @@ -187,12 +171,10 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2 // setMaxWidth() needs to be called before mIconView.setIconOrientation which is // called in the super below. - (taskContainers[0].iconView as IconAppChipView).setMaxWidth( + (leftTopTaskContainer.iconView as IconAppChipView).maxWidth = groupedTaskViewSizes.first.x - iconMargins - ) - (taskContainers[1].iconView as IconAppChipView).setMaxWidth( + (rightBottomTaskContainer.iconView as IconAppChipView).maxWidth = groupedTaskViewSizes.second.x - iconMargins - ) } } super.setOrientationState(orientationState) @@ -201,40 +183,72 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu private fun updateIconPlacement() { val splitBoundsConfig = splitBoundsConfig ?: return - val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx - val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL + val deviceProfile = container.deviceProfile + val taskIconHeight = deviceProfile.overviewTaskIconSizePx + val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID + var oneIconHiddenDueToSmallWidth = false + + if (enableFlexibleTwoAppSplit()) { + // Update values for both icons' setFlexSplitAlpha. Mainly, we want to hide an icon if + // its app tile is too small. But we also have to set the alphas back if we go to + // split selection. + val hideLeftTopIcon: Boolean + val hideRightBottomIcon: Boolean + if (inSplitSelection) { + hideLeftTopIcon = + getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.leftTopTaskId + hideRightBottomIcon = + getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.rightBottomTaskId + } else { + hideLeftTopIcon = splitBoundsConfig.leftTopTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON + hideRightBottomIcon = + splitBoundsConfig.rightBottomTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON + if (hideLeftTopIcon || hideRightBottomIcon) { + oneIconHiddenDueToSmallWidth = true + } + } + + leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideLeftTopIcon) 0f else 1f) + rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideRightBottomIcon) 0f else 1f) + } + if (enableOverviewIconMenu()) { + val isDeviceRtl = Utilities.isRtl(resources) val groupedTaskViewSizes = pagedOrientationHandler.getGroupedTaskViewSizes( - container.deviceProfile, + deviceProfile, splitBoundsConfig, layoutParams.width, - layoutParams.height + layoutParams.height, ) pagedOrientationHandler.setSplitIconParams( - taskContainers[0].iconView.asView(), - taskContainers[1].iconView.asView(), + leftTopTaskContainer.iconView.asView(), + rightBottomTaskContainer.iconView.asView(), taskIconHeight, groupedTaskViewSizes.first.x, groupedTaskViewSizes.first.y, layoutParams.height, layoutParams.width, - isRtl, - container.deviceProfile, - splitBoundsConfig + isDeviceRtl, + deviceProfile, + splitBoundsConfig, + inSplitSelection, + oneIconHiddenDueToSmallWidth, ) } else { pagedOrientationHandler.setSplitIconParams( - taskContainers[0].iconView.asView(), - taskContainers[1].iconView.asView(), + leftTopTaskContainer.iconView.asView(), + rightBottomTaskContainer.iconView.asView(), taskIconHeight, - taskContainers[0].thumbnailViewDeprecated.measuredWidth, - taskContainers[0].thumbnailViewDeprecated.measuredHeight, + leftTopTaskContainer.snapshotView.measuredWidth, + leftTopTaskContainer.snapshotView.measuredHeight, measuredHeight, measuredWidth, - isRtl, - container.deviceProfile, - splitBoundsConfig + isLayoutRtl, + deviceProfile, + splitBoundsConfig, + inSplitSelection, + oneIconHiddenDueToSmallWidth, ) } } @@ -242,24 +256,20 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) { splitBoundsConfig = splitBounds taskContainers.forEach { - it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) - it.digitalWellBeingToast?.initialize(it.task) + it.digitalWellBeingToast?.splitBounds = splitBoundsConfig + it.digitalWellBeingToast?.initialize() } invalidate() } - override fun launchTaskAnimated(): RunnableList? { - if (taskContainers.isEmpty()) { - Log.d(TAG, "launchTaskAnimated - task is not bound") - return null - } + override fun launchAsStaticTile(): RunnableList? { 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" + "Enter form GroupedTaskView", ) launchTaskInternal(isQuickSwitch = false, launchingExistingTaskView = true) { endCallback.executeAllAndDestroy() @@ -271,8 +281,11 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu return endCallback } - override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) { - launchTaskInternal(isQuickSwitch, false, callback /*launchingExistingTaskview*/) + override fun launchWithoutAnimation( + isQuickSwitch: Boolean, + callback: (launched: Boolean) -> Unit, + ) { + launchTaskInternal(isQuickSwitch, launchingExistingTaskView = false, callback) } /** @@ -284,19 +297,22 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu private fun launchTaskInternal( isQuickSwitch: Boolean, launchingExistingTaskView: Boolean, - callback: (launched: Boolean) -> Unit + callback: (launched: Boolean) -> Unit, ) { recentsView?.let { it.splitSelectController.launchExistingSplitPair( if (launchingExistingTaskView) this else null, - taskContainers[0].task.key.id, - taskContainers[1].task.key.id, + leftTopTaskContainer.task.key.id, + rightBottomTaskContainer.task.key.id, STAGE_POSITION_TOP_OR_LEFT, callback, isQuickSwitch, - snapPosition + snapPosition, + ) + Log.d( + TAG, + "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}, launchingExistingTaskView: $launchingExistingTaskView", ) - Log.d(TAG, "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}") } } @@ -317,14 +333,14 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu // checks below aren't reliable since both of those views may be gone/transformed val initSplitTaskId = getThisTaskCurrentlyInSplitSelection() if (initSplitTaskId != INVALID_TASK_ID) { - return if (initSplitTaskId == taskContainers[0].task.key.id) 1 else 0 + return if (initSplitTaskId == leftTopTaskContainer.task.key.id) 1 else 0 } } // Check which of the two apps was selected if ( - taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) || - taskContainers[1].thumbnailViewDeprecated.containsPoint(lastTouchDownPosition) + rightBottomTaskContainer.iconView.asView().containsPoint(lastTouchDownPosition) || + rightBottomTaskContainer.snapshotView.containsPoint(lastTouchDownPosition) ) { return 1 } @@ -337,14 +353,6 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu 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/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java deleted file mode 100644 index ba42594e99c..00000000000 --- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java +++ /dev/null @@ -1,464 +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; -import static com.android.app.animation.Interpolators.LINEAR; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.RectEvaluator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Outline; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewAnimationUtils; -import android.view.ViewOutlineProvider; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.util.MultiPropertyFactory; -import com.android.launcher3.util.MultiValueAlpha; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.util.RecentsOrientedState; - -/** - * An icon app menu view which can be used in place of an IconView in overview TaskViews. - */ -public class IconAppChipView extends FrameLayout implements TaskViewIcon { - - private static final int MENU_BACKGROUND_REVEAL_DURATION = 417; - private static final int MENU_BACKGROUND_HIDE_DURATION = 333; - - private static final int NUM_ALPHA_CHANNELS = 3; - private static final int INDEX_CONTENT_ALPHA = 0; - private static final int INDEX_COLOR_FILTER_ALPHA = 1; - private static final int INDEX_MODAL_ALPHA = 2; - - private final MultiValueAlpha mMultiValueAlpha; - - private View mMenuAnchorView; - private IconView mIconView; - // Two textview so we can ellipsize the collapsed view and crossfade on expand to the full name. - private TextView mIconTextCollapsedView; - private TextView mIconTextExpandedView; - private ImageView mIconArrowView; - private final Rect mBackgroundRelativeLtrLocation = new Rect(); - final RectEvaluator mBackgroundAnimationRectEvaluator = - new RectEvaluator(mBackgroundRelativeLtrLocation); - private final int mCollapsedMenuDefaultWidth; - private final int mExpandedMenuDefaultWidth; - private final int mCollapsedMenuDefaultHeight; - private final int mExpandedMenuDefaultHeight; - private final int mIconMenuMarginTopStart; - private final int mMenuToChipGap; - private final int mBackgroundMarginTopStart; - private final int mAppNameHorizontalMargin; - private final int mIconViewMarginStart; - private final int mAppIconSize; - private final int mArrowSize; - private final int mIconViewDrawableExpandedSize; - private final int mArrowMarginEnd; - private AnimatorSet mAnimator; - - private int mMaxWidth = Integer.MAX_VALUE; - - private static final int INDEX_SPLIT_TRANSLATION = 0; - private static final int INDEX_MENU_TRANSLATION = 1; - private static final int INDEX_COUNT_TRANSLATION = 2; - - private final MultiPropertyFactory mViewTranslationX; - private final MultiPropertyFactory mViewTranslationY; - - /** - * Gets the view split x-axis translation - */ - public MultiPropertyFactory.MultiProperty getSplitTranslationX() { - return mViewTranslationX.get(INDEX_SPLIT_TRANSLATION); - } - - /** - * Sets the view split x-axis translation - * @param translationX x-axis translation - */ - public void setSplitTranslationX(float translationX) { - getSplitTranslationX().setValue(translationX); - } - - /** - * Gets the view split y-axis translation - */ - public MultiPropertyFactory.MultiProperty getSplitTranslationY() { - return mViewTranslationY.get(INDEX_SPLIT_TRANSLATION); - } - - /** - * Sets the view split y-axis translation - * @param translationY y-axis translation - */ - public void setSplitTranslationY(float translationY) { - getSplitTranslationY().setValue(translationY); - } - - /** - * Gets the menu x-axis translation for split task - */ - public MultiPropertyFactory.MultiProperty getMenuTranslationX() { - return mViewTranslationX.get(INDEX_MENU_TRANSLATION); - } - - /** - * Gets the menu y-axis translation for split task - */ - public MultiPropertyFactory.MultiProperty getMenuTranslationY() { - return mViewTranslationY.get(INDEX_MENU_TRANSLATION); - } - - public IconAppChipView(Context context) { - this(context, null); - } - - public IconAppChipView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public IconAppChipView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public IconAppChipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - Resources res = getResources(); - mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS); - mMultiValueAlpha.setUpdateVisibility(/* updateVisibility= */ true); - - // Menu dimensions - mCollapsedMenuDefaultWidth = - res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_width); - mExpandedMenuDefaultWidth = - res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_width); - mCollapsedMenuDefaultHeight = - res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_height); - mExpandedMenuDefaultHeight = - res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_height); - mIconMenuMarginTopStart = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin); - mMenuToChipGap = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_expanded_gap); - - // Background dimensions - mBackgroundMarginTopStart = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_background_margin_top_start); - - // Contents dimensions - mAppNameHorizontalMargin = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_app_name_margin_horizontal_collapsed); - mArrowMarginEnd = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_margin); - mIconViewMarginStart = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_view_start_margin); - mAppIconSize = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_app_icon_collapsed_size); - mArrowSize = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_arrow_size); - mIconViewDrawableExpandedSize = res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_app_icon_expanded_size); - - mViewTranslationX = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_X, - INDEX_COUNT_TRANSLATION, - Float::sum); - mViewTranslationY = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_Y, - INDEX_COUNT_TRANSLATION, - Float::sum); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mIconView = findViewById(R.id.icon_view); - mIconTextCollapsedView = findViewById(R.id.icon_text_collapsed); - mIconTextExpandedView = findViewById(R.id.icon_text_expanded); - mIconArrowView = findViewById(R.id.icon_arrow); - mMenuAnchorView = findViewById(R.id.icon_view_menu_anchor); - } - - protected IconView getIconView() { - return mIconView; - } - - @Override - public void setText(CharSequence text) { - if (mIconTextCollapsedView != null) { - mIconTextCollapsedView.setText(text); - } - if (mIconTextExpandedView != null) { - mIconTextExpandedView.setText(text); - } - } - - @Override - public Drawable getDrawable() { - return mIconView == null ? null : mIconView.getDrawable(); - } - - @Override - public void setDrawable(Drawable icon) { - if (mIconView != null) { - mIconView.setDrawable(icon); - } - } - - @Override - public void setDrawableSize(int iconWidth, int iconHeight) { - if (mIconView != null) { - mIconView.setDrawableSize(iconWidth, iconHeight); - } - } - - /** - * Sets the maximum width of this Icon Menu. This is usually used when space is limited for - * split screen. - */ - public void setMaxWidth(int maxWidth) { - // Width showing only the app icon and arrow. Max width should not be set to less than this. - int minimumMaxWidth = mIconViewMarginStart + mAppIconSize + mArrowSize + mArrowMarginEnd; - mMaxWidth = Math.max(maxWidth, minimumMaxWidth); - } - - @Override - public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) { - RecentsPagedOrientationHandler orientationHandler = - orientationState.getOrientationHandler(); - // Layout params for anchor view - LayoutParams anchorLayoutParams = (LayoutParams) mMenuAnchorView.getLayoutParams(); - anchorLayoutParams.topMargin = mExpandedMenuDefaultHeight + mMenuToChipGap; - mMenuAnchorView.setLayoutParams(anchorLayoutParams); - - // Layout Params for the Menu View (this) - LayoutParams iconMenuParams = (LayoutParams) getLayoutParams(); - iconMenuParams.width = mExpandedMenuDefaultWidth; - iconMenuParams.height = mExpandedMenuDefaultHeight; - orientationHandler.setIconAppChipMenuParams(this, iconMenuParams, mIconMenuMarginTopStart, - mIconMenuMarginTopStart); - setLayoutParams(iconMenuParams); - - // Layout params for the background - Rect collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds(); - mBackgroundRelativeLtrLocation.set(collapsedBackgroundBounds); - setOutlineProvider(new ViewOutlineProvider() { - final Rect mRtlAppliedOutlineBounds = new Rect(); - @Override - public void getOutline(View view, Outline outline) { - mRtlAppliedOutlineBounds.set(mBackgroundRelativeLtrLocation); - if (isLayoutRtl()) { - int width = getWidth(); - mRtlAppliedOutlineBounds.left = width - mBackgroundRelativeLtrLocation.right; - mRtlAppliedOutlineBounds.right = width - mBackgroundRelativeLtrLocation.left; - } - outline.setRoundRect( - mRtlAppliedOutlineBounds, mRtlAppliedOutlineBounds.height() / 2f); - } - }); - - // Layout Params for the Icon View - LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); - int iconMarginStartRelativeToParent = mIconViewMarginStart + mBackgroundMarginTopStart; - orientationHandler.setIconAppChipChildrenParams( - iconParams, iconMarginStartRelativeToParent); - - mIconView.setLayoutParams(iconParams); - mIconView.setDrawableSize(mAppIconSize, mAppIconSize); - - // Layout Params for the collapsed Icon Text View - int textMarginStart = - iconMarginStartRelativeToParent + mAppIconSize + mAppNameHorizontalMargin; - LayoutParams iconTextCollapsedParams = - (LayoutParams) mIconTextCollapsedView.getLayoutParams(); - orientationHandler.setIconAppChipChildrenParams(iconTextCollapsedParams, textMarginStart); - int collapsedTextWidth = collapsedBackgroundBounds.width() - mIconViewMarginStart - - mAppIconSize - mArrowSize - mAppNameHorizontalMargin - mArrowMarginEnd; - iconTextCollapsedParams.width = collapsedTextWidth; - mIconTextCollapsedView.setLayoutParams(iconTextCollapsedParams); - mIconTextCollapsedView.setAlpha(1f); - - // Layout Params for the expanded Icon Text View - LayoutParams iconTextExpandedParams = - (LayoutParams) mIconTextExpandedView.getLayoutParams(); - orientationHandler.setIconAppChipChildrenParams(iconTextExpandedParams, textMarginStart); - mIconTextExpandedView.setLayoutParams(iconTextExpandedParams); - mIconTextExpandedView.setAlpha(0f); - mIconTextExpandedView.setRevealClip(true, 0, mAppIconSize / 2f, collapsedTextWidth); - - // Layout Params for the Icon Arrow View - LayoutParams iconArrowParams = (LayoutParams) mIconArrowView.getLayoutParams(); - int arrowMarginStart = collapsedBackgroundBounds.right - mArrowMarginEnd - mArrowSize; - orientationHandler.setIconAppChipChildrenParams(iconArrowParams, arrowMarginStart); - mIconArrowView.setPivotY(iconArrowParams.height / 2f); - mIconArrowView.setLayoutParams(iconArrowParams); - - // This method is called twice sometimes (like when rotating split tasks). It is called - // once before onMeasure and onLayout, and again after onMeasure but before onLayout with - // a new width. This happens because we update widths on rotation and on measure of - // grouped task views. Calling requestLayout() does not guarantee a call to onMeasure if - // it has just measured, so we explicitly call it here. - measure(MeasureSpec.makeMeasureSpec(getLayoutParams().width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getLayoutParams().height, MeasureSpec.EXACTLY)); - } - - @Override - public void setIconColorTint(int color, float amount) { - // RecentsView's COLOR_TINT animates between 0 and 0.5f, we want to hide the app chip menu. - float colorTintAlpha = Utilities.mapToRange(amount, 0f, 0.5f, 1f, 0f, LINEAR); - mMultiValueAlpha.get(INDEX_COLOR_FILTER_ALPHA).setValue(colorTintAlpha); - } - - @Override - public void setContentAlpha(float alpha) { - mMultiValueAlpha.get(INDEX_CONTENT_ALPHA).setValue(alpha); - } - - @Override - public void setModalAlpha(float alpha) { - mMultiValueAlpha.get(INDEX_MODAL_ALPHA).setValue(alpha); - } - - @Override - public int getDrawableWidth() { - return mIconView == null ? 0 : mIconView.getDrawableWidth(); - } - - @Override - public int getDrawableHeight() { - return mIconView == null ? 0 : mIconView.getDrawableHeight(); - } - - protected void revealAnim(boolean isRevealing) { - cancelInProgressAnimations(); - final Rect collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds(); - final Rect expandedBackgroundBounds = getExpandedBackgroundLtrBounds(); - final Rect initialBackground = new Rect(mBackgroundRelativeLtrLocation); - mAnimator = new AnimatorSet(); - - if (isRevealing) { - boolean isRtl = isLayoutRtl(); - bringToFront(); - // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu - Animator expandedTextRevealAnim = ViewAnimationUtils.createCircularReveal( - mIconTextExpandedView, 0, mIconTextExpandedView.getHeight() / 2, - mIconTextCollapsedView.getWidth(), mIconTextExpandedView.getWidth()); - // Animate background clipping - ValueAnimator backgroundAnimator = ValueAnimator.ofObject( - mBackgroundAnimationRectEvaluator, - initialBackground, - expandedBackgroundBounds); - backgroundAnimator.addUpdateListener(valueAnimator -> invalidateOutline()); - - float iconViewScaling = mIconViewDrawableExpandedSize / (float) mAppIconSize; - float arrowTranslationX = - expandedBackgroundBounds.right - collapsedBackgroundBounds.right; - float iconCenterToTextCollapsed = mAppIconSize / 2f + mAppNameHorizontalMargin; - float iconCenterToTextExpanded = - mIconViewDrawableExpandedSize / 2f + mAppNameHorizontalMargin; - float textTranslationX = iconCenterToTextExpanded - iconCenterToTextCollapsed; - - float textTranslationXWithRtl = isRtl ? -textTranslationX : textTranslationX; - float arrowTranslationWithRtl = isRtl ? -arrowTranslationX : arrowTranslationX; - - mAnimator.playTogether( - expandedTextRevealAnim, - backgroundAnimator, - ObjectAnimator.ofFloat(mIconView, SCALE_X, iconViewScaling), - ObjectAnimator.ofFloat(mIconView, SCALE_Y, iconViewScaling), - ObjectAnimator.ofFloat(mIconTextCollapsedView, TRANSLATION_X, - textTranslationXWithRtl), - ObjectAnimator.ofFloat(mIconTextExpandedView, TRANSLATION_X, - textTranslationXWithRtl), - ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 0), - ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 1), - ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, arrowTranslationWithRtl), - ObjectAnimator.ofFloat(mIconArrowView, SCALE_Y, -1)); - mAnimator.setDuration(MENU_BACKGROUND_REVEAL_DURATION); - } else { - // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu - Animator expandedTextClipAnim = ViewAnimationUtils.createCircularReveal( - mIconTextExpandedView, 0, mIconTextExpandedView.getHeight() / 2, - mIconTextExpandedView.getWidth(), mIconTextCollapsedView.getWidth()); - - // Animate background clipping - ValueAnimator backgroundAnimator = ValueAnimator.ofObject( - mBackgroundAnimationRectEvaluator, - initialBackground, - collapsedBackgroundBounds); - backgroundAnimator.addUpdateListener(valueAnimator -> invalidateOutline()); - - mAnimator.playTogether( - expandedTextClipAnim, - backgroundAnimator, - ObjectAnimator.ofFloat(mIconView, SCALE_PROPERTY, 1), - ObjectAnimator.ofFloat(mIconTextCollapsedView, TRANSLATION_X, 0), - ObjectAnimator.ofFloat(mIconTextExpandedView, TRANSLATION_X, 0), - ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 1), - ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 0), - ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, 0), - ObjectAnimator.ofFloat(mIconArrowView, SCALE_Y, 1)); - mAnimator.setDuration(MENU_BACKGROUND_HIDE_DURATION); - } - - mAnimator.setInterpolator(EMPHASIZED); - mAnimator.start(); - } - - private Rect getCollapsedBackgroundLtrBounds() { - Rect bounds = new Rect( - 0, - 0, - Math.min(mMaxWidth, mCollapsedMenuDefaultWidth), - mCollapsedMenuDefaultHeight); - bounds.offset(mBackgroundMarginTopStart, mBackgroundMarginTopStart); - return bounds; - } - - private Rect getExpandedBackgroundLtrBounds() { - return new Rect(0, 0, mExpandedMenuDefaultWidth, mExpandedMenuDefaultHeight); - } - - private void cancelInProgressAnimations() { - // We null the `AnimatorSet` because it holds references to the `Animators` which aren't - // expecting to be mutable and will cause a crash if they are re-used. - if (mAnimator != null && mAnimator.isStarted()) { - mAnimator.cancel(); - mAnimator = null; - } - } - - @Override - public View asView() { - return this; - } -} diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt new file mode 100644 index 00000000000..f4fd12792a1 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt @@ -0,0 +1,464 @@ +/* + * 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 android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Outline +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import android.view.ViewAnimationUtils +import android.view.ViewOutlineProvider +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import com.android.app.animation.Interpolators +import com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY +import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X +import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y +import com.android.launcher3.R +import com.android.launcher3.Utilities +import com.android.launcher3.util.MultiPropertyFactory +import com.android.launcher3.util.MultiPropertyFactory.FloatBiFunction +import com.android.launcher3.util.MultiValueAlpha +import com.android.quickstep.util.RecentsOrientedState +import kotlin.math.max +import kotlin.math.min + +/** An icon app menu view which can be used in place of an IconView in overview TaskViews. */ +class IconAppChipView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0, +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), TaskViewIcon { + + private var iconView: IconView? = null + private var iconArrowView: ImageView? = null + private var menuAnchorView: View? = null + // Two textview so we can ellipsize the collapsed view and crossfade on expand to the full name. + private var iconTextCollapsedView: TextView? = null + private var iconTextExpandedView: TextView? = null + + private val backgroundRelativeLtrLocation = Rect() + private val backgroundAnimationRectEvaluator = RectEvaluator(backgroundRelativeLtrLocation) + + // Menu dimensions + private val collapsedMenuDefaultWidth: Int = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_width) + private val expandedMenuDefaultWidth: Int = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_width) + private val collapsedMenuDefaultHeight = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_height) + private val expandedMenuDefaultHeight = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_height) + private val iconMenuMarginTopStart = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin) + private val menuToChipGap: Int = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_gap) + + // Background dimensions + private val backgroundMarginTopStart: Int = + resources.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_background_margin_top_start + ) + + // Contents dimensions + private val appNameHorizontalMargin = + resources.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_app_name_margin_horizontal_collapsed + ) + private val arrowMarginEnd = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_margin) + private val iconViewMarginStart = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_view_start_margin) + private val appIconSize = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_app_icon_collapsed_size) + private val arrowSize = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_size) + private val iconViewDrawableExpandedSize = + resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_app_icon_expanded_size) + + private var animator: AnimatorSet? = null + + private val multiValueAlpha: MultiValueAlpha = + MultiValueAlpha(this, NUM_ALPHA_CHANNELS).apply { setUpdateVisibility(true) } + + private val viewTranslationX: MultiPropertyFactory = + MultiPropertyFactory(this, VIEW_TRANSLATE_X, INDEX_COUNT_TRANSLATION, SUM_AGGREGATOR) + + private val viewTranslationY: MultiPropertyFactory = + MultiPropertyFactory(this, VIEW_TRANSLATE_Y, INDEX_COUNT_TRANSLATION, SUM_AGGREGATOR) + + var maxWidth = Int.MAX_VALUE + /** + * Sets the maximum width of this Icon Menu. This is usually used when space is limited for + * split screen. + */ + set(value) { + // Width showing only the app icon and arrow. Max width should not be set to less than + // this. + val minMaxWidth = iconViewMarginStart + appIconSize + arrowSize + arrowMarginEnd + field = max(value, minMaxWidth) + } + + var status: AppChipStatus = AppChipStatus.Collapsed + private set + + override fun onFinishInflate() { + super.onFinishInflate() + iconView = findViewById(R.id.icon_view) + iconTextCollapsedView = findViewById(R.id.icon_text_collapsed) + iconTextExpandedView = findViewById(R.id.icon_text_expanded) + iconArrowView = findViewById(R.id.icon_arrow) + menuAnchorView = findViewById(R.id.icon_view_menu_anchor) + } + + override fun setText(text: CharSequence?) { + iconTextCollapsedView?.text = text + iconTextExpandedView?.text = text + } + + override fun getDrawable(): Drawable? = iconView?.drawable + + override fun setDrawable(icon: Drawable?) { + iconView?.drawable = icon + } + + override fun setDrawableSize(iconWidth: Int, iconHeight: Int) { + iconView?.setDrawableSize(iconWidth, iconHeight) + } + + override fun setIconOrientation(orientationState: RecentsOrientedState, isGridTask: Boolean) { + val orientationHandler = orientationState.orientationHandler + // Layout params for anchor view + val anchorLayoutParams = menuAnchorView!!.layoutParams as LayoutParams + anchorLayoutParams.topMargin = expandedMenuDefaultHeight + menuToChipGap + menuAnchorView!!.layoutParams = anchorLayoutParams + + // Layout Params for the Menu View (this) + val iconMenuParams = layoutParams as LayoutParams + iconMenuParams.width = expandedMenuDefaultWidth + iconMenuParams.height = expandedMenuDefaultHeight + orientationHandler.setIconAppChipMenuParams( + this, + iconMenuParams, + iconMenuMarginTopStart, + iconMenuMarginTopStart, + ) + layoutParams = iconMenuParams + + // Layout params for the background + val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds() + backgroundRelativeLtrLocation.set(collapsedBackgroundBounds) + outlineProvider = + object : ViewOutlineProvider() { + val mRtlAppliedOutlineBounds: Rect = Rect() + + override fun getOutline(view: View, outline: Outline) { + mRtlAppliedOutlineBounds.set(backgroundRelativeLtrLocation) + if (isLayoutRtl) { + val width = width + mRtlAppliedOutlineBounds.left = width - backgroundRelativeLtrLocation.right + mRtlAppliedOutlineBounds.right = width - backgroundRelativeLtrLocation.left + } + outline.setRoundRect( + mRtlAppliedOutlineBounds, + mRtlAppliedOutlineBounds.height() / 2f, + ) + } + } + + // Layout Params for the Icon View + val iconParams = iconView!!.layoutParams as LayoutParams + val iconMarginStartRelativeToParent = iconViewMarginStart + backgroundMarginTopStart + orientationHandler.setIconAppChipChildrenParams(iconParams, iconMarginStartRelativeToParent) + + iconView!!.layoutParams = iconParams + iconView!!.setDrawableSize(appIconSize, appIconSize) + + // Layout Params for the collapsed Icon Text View + val textMarginStart = + iconMarginStartRelativeToParent + appIconSize + appNameHorizontalMargin + val iconTextCollapsedParams = iconTextCollapsedView!!.layoutParams as LayoutParams + orientationHandler.setIconAppChipChildrenParams(iconTextCollapsedParams, textMarginStart) + val collapsedTextWidth = + (collapsedBackgroundBounds.width() - + iconViewMarginStart - + appIconSize - + arrowSize - + appNameHorizontalMargin - + arrowMarginEnd) + iconTextCollapsedParams.width = collapsedTextWidth + iconTextCollapsedView!!.layoutParams = iconTextCollapsedParams + iconTextCollapsedView!!.alpha = 1f + + // Layout Params for the expanded Icon Text View + val iconTextExpandedParams = iconTextExpandedView!!.layoutParams as LayoutParams + orientationHandler.setIconAppChipChildrenParams(iconTextExpandedParams, textMarginStart) + iconTextExpandedView!!.layoutParams = iconTextExpandedParams + iconTextExpandedView!!.alpha = 0f + iconTextExpandedView!!.setRevealClip( + true, + 0f, + appIconSize / 2f, + collapsedTextWidth.toFloat(), + ) + + // Layout Params for the Icon Arrow View + val iconArrowParams = iconArrowView!!.layoutParams as LayoutParams + val arrowMarginStart = collapsedBackgroundBounds.right - arrowMarginEnd - arrowSize + orientationHandler.setIconAppChipChildrenParams(iconArrowParams, arrowMarginStart) + iconArrowView!!.pivotY = iconArrowParams.height / 2f + iconArrowView!!.layoutParams = iconArrowParams + + // This method is called twice sometimes (like when rotating split tasks). It is called + // once before onMeasure and onLayout, and again after onMeasure but before onLayout with + // a new width. This happens because we update widths on rotation and on measure of + // grouped task views. Calling requestLayout() does not guarantee a call to onMeasure if + // it has just measured, so we explicitly call it here. + measure( + MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY), + ) + } + + override fun setIconColorTint(color: Int, amount: Float) { + // RecentsView's COLOR_TINT animates between 0 and 0.5f, we want to hide the app chip menu. + val colorTintAlpha = Utilities.mapToRange(amount, 0f, 0.5f, 1f, 0f, Interpolators.LINEAR) + multiValueAlpha[INDEX_COLOR_FILTER_ALPHA].value = colorTintAlpha + } + + override fun setContentAlpha(alpha: Float) { + multiValueAlpha[INDEX_CONTENT_ALPHA].value = alpha + } + + override fun setModalAlpha(alpha: Float) { + multiValueAlpha[INDEX_MODAL_ALPHA].value = alpha + } + + override fun setFlexSplitAlpha(alpha: Float) { + multiValueAlpha[INDEX_MINIMUM_RATIO_ALPHA].value = alpha + } + + override fun getDrawableWidth(): Int = iconView?.drawableWidth ?: 0 + + override fun getDrawableHeight(): Int = iconView?.drawableHeight ?: 0 + + /** Gets the view split x-axis translation */ + fun getSplitTranslationX(): MultiPropertyFactory.MultiProperty = + viewTranslationX.get(INDEX_SPLIT_TRANSLATION) + + /** + * Sets the view split x-axis translation + * + * @param value x-axis translation + */ + fun setSplitTranslationX(value: Float) { + getSplitTranslationX().value = value + } + + /** Gets the view split y-axis translation */ + fun getSplitTranslationY(): MultiPropertyFactory.MultiProperty = + viewTranslationY[INDEX_SPLIT_TRANSLATION] + + /** + * Sets the view split y-axis translation + * + * @param value y-axis translation + */ + fun setSplitTranslationY(value: Float) { + getSplitTranslationY().value = value + } + + /** Gets the menu x-axis translation for split task */ + fun getMenuTranslationX(): MultiPropertyFactory.MultiProperty = + viewTranslationX[INDEX_MENU_TRANSLATION] + + /** Gets the menu y-axis translation for split task */ + fun getMenuTranslationY(): MultiPropertyFactory.MultiProperty = + viewTranslationY[INDEX_MENU_TRANSLATION] + + internal fun revealAnim(isRevealing: Boolean, animated: Boolean = true) { + cancelInProgressAnimations() + val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds() + val expandedBackgroundBounds = getExpandedBackgroundLtrBounds() + val initialBackground = Rect(backgroundRelativeLtrLocation) + animator = AnimatorSet() + + if (isRevealing) { + val isRtl = isLayoutRtl + bringToFront() + // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu + val expandedTextRevealAnim = + ViewAnimationUtils.createCircularReveal( + iconTextExpandedView, + 0, + iconTextExpandedView!!.height / 2, + iconTextCollapsedView!!.width.toFloat(), + iconTextExpandedView!!.width.toFloat(), + ) + // Animate background clipping + val backgroundAnimator = + ValueAnimator.ofObject( + backgroundAnimationRectEvaluator, + initialBackground, + expandedBackgroundBounds, + ) + backgroundAnimator.addUpdateListener { invalidateOutline() } + + val iconViewScaling = iconViewDrawableExpandedSize / appIconSize.toFloat() + val arrowTranslationX = + (expandedBackgroundBounds.right - collapsedBackgroundBounds.right).toFloat() + val iconCenterToTextCollapsed = appIconSize / 2f + appNameHorizontalMargin + val iconCenterToTextExpanded = + iconViewDrawableExpandedSize / 2f + appNameHorizontalMargin + val textTranslationX = iconCenterToTextExpanded - iconCenterToTextCollapsed + + val textTranslationXWithRtl = if (isRtl) -textTranslationX else textTranslationX + val arrowTranslationWithRtl = if (isRtl) -arrowTranslationX else arrowTranslationX + + animator!!.playTogether( + expandedTextRevealAnim, + backgroundAnimator, + ObjectAnimator.ofFloat(iconView, SCALE_X, iconViewScaling), + ObjectAnimator.ofFloat(iconView, SCALE_Y, iconViewScaling), + ObjectAnimator.ofFloat( + iconTextCollapsedView, + TRANSLATION_X, + textTranslationXWithRtl, + ), + ObjectAnimator.ofFloat( + iconTextExpandedView, + TRANSLATION_X, + textTranslationXWithRtl, + ), + ObjectAnimator.ofFloat(iconTextCollapsedView, ALPHA, 0f), + ObjectAnimator.ofFloat(iconTextExpandedView, ALPHA, 1f), + ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, arrowTranslationWithRtl), + ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, -1f), + ) + animator!!.duration = MENU_BACKGROUND_REVEAL_DURATION.toLong() + status = AppChipStatus.Expanded + } else { + // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu + val expandedTextClipAnim = + ViewAnimationUtils.createCircularReveal( + iconTextExpandedView, + 0, + iconTextExpandedView!!.height / 2, + iconTextExpandedView!!.width.toFloat(), + iconTextCollapsedView!!.width.toFloat(), + ) + + // Animate background clipping + val backgroundAnimator = + ValueAnimator.ofObject( + backgroundAnimationRectEvaluator, + initialBackground, + collapsedBackgroundBounds, + ) + backgroundAnimator.addUpdateListener { valueAnimator: ValueAnimator? -> + invalidateOutline() + } + + animator!!.playTogether( + expandedTextClipAnim, + backgroundAnimator, + ObjectAnimator.ofFloat(iconView, SCALE_PROPERTY, 1f), + ObjectAnimator.ofFloat(iconTextCollapsedView, TRANSLATION_X, 0f), + ObjectAnimator.ofFloat(iconTextExpandedView, TRANSLATION_X, 0f), + ObjectAnimator.ofFloat(iconTextCollapsedView, ALPHA, 1f), + ObjectAnimator.ofFloat(iconTextExpandedView, ALPHA, 0f), + ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, 0f), + ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, 1f), + ) + animator!!.duration = MENU_BACKGROUND_HIDE_DURATION.toLong() + status = AppChipStatus.Collapsed + } + + if (!animated) animator!!.duration = 0 + animator!!.interpolator = Interpolators.EMPHASIZED + animator!!.start() + } + + private fun getCollapsedBackgroundLtrBounds(): Rect { + val bounds = + Rect(0, 0, min(maxWidth, collapsedMenuDefaultWidth), collapsedMenuDefaultHeight) + bounds.offset(backgroundMarginTopStart, backgroundMarginTopStart) + return bounds + } + + private fun getExpandedBackgroundLtrBounds() = + Rect(0, 0, expandedMenuDefaultWidth, expandedMenuDefaultHeight) + + private fun cancelInProgressAnimations() { + // We null the `AnimatorSet` because it holds references to the `Animators` which aren't + // expecting to be mutable and will cause a crash if they are re-used. + if (animator != null && animator!!.isStarted) { + animator!!.cancel() + animator = null + } + } + + override fun focusSearch(direction: Int): View? { + if (mParent == null) return null + return when (direction) { + FOCUS_RIGHT, + FOCUS_DOWN -> mParent.focusSearch(this, View.FOCUS_FORWARD) + FOCUS_UP, + FOCUS_LEFT -> mParent.focusSearch(this, View.FOCUS_BACKWARD) + else -> super.focusSearch(direction) + } + } + + fun reset() { + setText(null) + setDrawable(null) + } + + override fun asView(): View = this + + enum class AppChipStatus { + Expanded, + Collapsed, + } + + private companion object { + private val SUM_AGGREGATOR = FloatBiFunction { a: Float, b: Float -> a + b } + + private const val MENU_BACKGROUND_REVEAL_DURATION = 417 + private const val MENU_BACKGROUND_HIDE_DURATION = 333 + + private const val NUM_ALPHA_CHANNELS = 4 + private const val INDEX_CONTENT_ALPHA = 0 + private const val INDEX_COLOR_FILTER_ALPHA = 1 + private const val INDEX_MODAL_ALPHA = 2 + /** Used to hide the app chip for 90:10 flex split. */ + private const val INDEX_MINIMUM_RATIO_ALPHA = 3 + + private const val INDEX_SPLIT_TRANSLATION = 0 + private const val INDEX_MENU_TRANSLATION = 1 + private const val INDEX_COUNT_TRANSLATION = 2 + } +} diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java deleted file mode 100644 index bb4a7ecda68..00000000000 --- a/quickstep/src/com/android/quickstep/views/IconView.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.views; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; - -import androidx.annotation.Nullable; - -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; - -/** - * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout - * 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) { - this(context, null); - } - - public IconView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public IconView(Context context, AttributeSet attrs, int 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); - } - - /** - * Sets a {@link Drawable} to be displayed. - */ - @Override - public void setDrawable(@Nullable Drawable d) { - if (mDrawable != null) { - mDrawable.setCallback(null); - } - mDrawable = d; - if (mDrawable != null) { - mDrawable.setCallback(this); - setDrawableSizeInternal(getWidth(), getHeight()); - } - invalidate(); - } - - /** - * Sets the size of the icon drawable. - */ - @Override - public void setDrawableSize(int iconWidth, int iconHeight) { - mDrawableWidth = iconWidth; - mDrawableHeight = iconHeight; - if (mDrawable != null) { - setDrawableSizeInternal(getWidth(), getHeight()); - } - } - - private void setDrawableSizeInternal(int selfWidth, int selfHeight) { - Rect selfRect = new Rect(0, 0, selfWidth, selfHeight); - Rect drawableRect = new Rect(); - Gravity.apply(Gravity.CENTER, mDrawableWidth, mDrawableHeight, selfRect, drawableRect); - mDrawable.setBounds(drawableRect); - } - - @Override - @Nullable - public Drawable getDrawable() { - return mDrawable; - } - - @Override - public int getDrawableWidth() { - return mDrawableWidth; - } - - @Override - public int getDrawableHeight() { - return mDrawableHeight; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (mDrawable != null) { - setDrawableSizeInternal(w, h); - } - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || who == mDrawable; - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - final Drawable drawable = mDrawable; - if (drawable != null && drawable.isStateful() - && drawable.setState(getDrawableState())) { - invalidateDrawable(drawable); - } - } - - @Override - protected void onDraw(Canvas canvas) { - if (mDrawable != null) { - mDrawable.draw(canvas); - } - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - @Override - public void setContentAlpha(float alpha) { - mMultiValueAlpha.get(INDEX_CONTENT_ALPHA).setValue(alpha); - } - - @Override - public void setModalAlpha(float alpha) { - mMultiValueAlpha.get(INDEX_MODAL_ALPHA).setValue(alpha); - } - - /** - * Set the tint color of the icon, useful for scrimming or dimming. - * - * @param color to blend in. - * @param amount [0,1] 0 no tint, 1 full tint - */ - @Override - public void setIconColorTint(int color, float amount) { - if (mDrawable != null) { - mDrawable.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount)); - } - } - - @Override - public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) { - RecentsPagedOrientationHandler orientationHandler = - orientationState.getOrientationHandler(); - boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; - DeviceProfile deviceProfile = - ActivityContext.lookupContext(getContext()).getDeviceProfile(); - - FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) getLayoutParams(); - - int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; - int taskIconHeight = deviceProfile.overviewTaskIconSizePx; - int taskMargin = deviceProfile.overviewTaskMarginPx; - - orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight, - thumbnailTopMargin, isRtl); - iconParams.width = iconParams.height = taskIconHeight; - setLayoutParams(iconParams); - - setRotation(orientationHandler.getDegreesRotated()); - int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx - : deviceProfile.overviewTaskIconDrawableSizePx; - setDrawableSize(iconDrawableSize, iconDrawableSize); - } - - @Override - public View asView() { - return this; - } -} diff --git a/quickstep/src/com/android/quickstep/views/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt new file mode 100644 index 00000000000..cb69b22de0e --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/IconView.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import androidx.core.view.updateLayoutParams +import com.android.launcher3.DeviceProfile +import com.android.launcher3.Flags +import com.android.launcher3.Utilities +import com.android.launcher3.util.MSDLPlayerWrapper +import com.android.launcher3.util.MultiValueAlpha +import com.android.launcher3.views.ActivityContext +import com.android.quickstep.util.RecentsOrientedState +import com.google.android.msdl.data.model.MSDLToken + +/** + * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout + * when the drawable changes. + */ +class IconView : View, TaskViewIcon { + private val multiValueAlpha: MultiValueAlpha = MultiValueAlpha(this, NUM_ALPHA_CHANNELS) + private var drawable: Drawable? = null + private var drawableWidth = 0 + private var drawableHeight = 0 + private var msdlPlayerWrapper: MSDLPlayerWrapper? = null + + constructor(context: Context) : super(context) { + setUpHaptics() + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + setUpHaptics() + } + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + ) : super(context, attrs, defStyleAttr) { + setUpHaptics() + } + + init { + multiValueAlpha.setUpdateVisibility(true) + } + + private fun setUpHaptics() { + msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context) + // Haptics are handled by the MSDLPlayerWrapper + isHapticFeedbackEnabled = !Flags.msdlFeedback() || msdlPlayerWrapper == null + } + + override fun setOnLongClickListener(l: OnLongClickListener?) { + super.setOnLongClickListener(l?.withFeedback()) + } + + /** Sets a [Drawable] to be displayed. */ + override fun setDrawable(d: Drawable?) { + drawable?.callback = null + + // Copy drawable so that mutations below do not affect other users of the drawable + drawable = d?.constantState?.newDrawable()?.mutate() + drawable?.let { + it.callback = this + setDrawableSizeInternal(width, height) + } + invalidate() + } + + /** Sets the size of the icon drawable. */ + override fun setDrawableSize(iconWidth: Int, iconHeight: Int) { + drawableWidth = iconWidth + drawableHeight = iconHeight + drawable?.let { setDrawableSizeInternal(width, height) } + } + + private fun setDrawableSizeInternal(selfWidth: Int, selfHeight: Int) { + val selfRect = Rect(0, 0, selfWidth, selfHeight) + val drawableRect = Rect() + Gravity.apply(Gravity.CENTER, drawableWidth, drawableHeight, selfRect, drawableRect) + drawable?.bounds = drawableRect + } + + override fun getDrawable(): Drawable? = drawable + + override fun getDrawableWidth(): Int = drawableWidth + + override fun getDrawableHeight(): Int = drawableHeight + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + drawable?.let { setDrawableSizeInternal(w, h) } + } + + override fun verifyDrawable(who: Drawable): Boolean = + super.verifyDrawable(who) || who === drawable + + override fun drawableStateChanged() { + super.drawableStateChanged() + drawable?.let { + if (it.isStateful && it.setState(drawableState)) { + invalidateDrawable(it) + } + } + } + + override fun onDraw(canvas: Canvas) { + drawable?.draw(canvas) + } + + override fun hasOverlappingRendering(): Boolean = false + + override fun setContentAlpha(alpha: Float) { + multiValueAlpha[INDEX_CONTENT_ALPHA].setValue(alpha) + } + + override fun setModalAlpha(alpha: Float) { + multiValueAlpha[INDEX_MODAL_ALPHA].setValue(alpha) + } + + override fun setFlexSplitAlpha(alpha: Float) { + multiValueAlpha[INDEX_FLEX_SPLIT_ALPHA].setValue(alpha) + } + + /** + * Set the tint color of the icon, useful for scrimming or dimming. + * + * @param color to blend in. + * @param amount [0,1] 0 no tint, 1 full tint + */ + override fun setIconColorTint(color: Int, amount: Float) { + drawable?.colorFilter = Utilities.makeColorTintingColorFilter(color, amount) + } + + override fun setIconOrientation(orientationState: RecentsOrientedState, isGridTask: Boolean) { + val orientationHandler = orientationState.orientationHandler + val deviceProfile: DeviceProfile = + (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile() + orientationHandler.setTaskIconParams( + iconParams = getLayoutParams() as FrameLayout.LayoutParams, + taskIconMargin = deviceProfile.overviewTaskMarginPx, + taskIconHeight = deviceProfile.overviewTaskIconSizePx, + thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx, + isRtl = layoutDirection == LAYOUT_DIRECTION_RTL, + ) + updateLayoutParams { + height = deviceProfile.overviewTaskIconSizePx + width = height + } + setRotation(orientationHandler.degreesRotated) + val iconDrawableSize = + if (isGridTask) deviceProfile.overviewTaskIconDrawableSizeGridPx + else deviceProfile.overviewTaskIconDrawableSizePx + setDrawableSize(iconDrawableSize, iconDrawableSize) + } + + override fun asView(): View = this + + private fun OnLongClickListener.withFeedback(): OnLongClickListener { + val delegate = this + return OnLongClickListener { v: View -> + if (Flags.msdlFeedback()) { + msdlPlayerWrapper?.playToken(MSDLToken.LONG_PRESS) + } + delegate.onLongClick(v) + } + } + + companion object { + private const val NUM_ALPHA_CHANNELS = 3 + private const val INDEX_CONTENT_ALPHA = 0 + private const val INDEX_MODAL_ALPHA = 1 + private const val INDEX_FLEX_SPLIT_ALPHA = 2 + } +} diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index b4bca1ced86..a6be3f789ae 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -16,17 +16,16 @@ package com.android.quickstep.views; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY; -import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.Flags.enableGridOnlyOverview; import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON; -import static com.android.launcher3.LauncherState.EDIT_MODE; +import static com.android.launcher3.LauncherState.ADD_DESK_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; -import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME; -import static com.android.window.flags2.Flags.enableDesktopWindowingWallpaperActivity; import android.annotation.TargetApi; import android.content.Context; @@ -40,7 +39,6 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.desktop.DesktopRecentsTransitionController; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.statehandlers.DepthController; @@ -51,12 +49,13 @@ import com.android.launcher3.util.PendingSplitSelectInfo; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource; +import com.android.quickstep.BaseContainerInterface; import com.android.quickstep.GestureState; import com.android.quickstep.LauncherActivityInterface; -import com.android.quickstep.RotationTouchHelper; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.AnimUtils; import com.android.quickstep.util.SplitSelectStateController; -import com.android.systemui.shared.recents.model.Task; +import com.android.wm.shell.shared.GroupedTaskInfo; import kotlin.Unit; @@ -76,7 +75,7 @@ public LauncherRecentsView(Context context, @Nullable AttributeSet attrs) { } public LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE); + super(context, attrs, defStyleAttr); getStateManager().addStateListener(this); } @@ -92,10 +91,12 @@ public void init(OverviewActionsView actionsView, protected void handleStartHome(boolean animated) { StateManager stateManager = getStateManager(); animated &= stateManager.shouldAnimateStateChange(); - stateManager.goToState(NORMAL, animated); - if (FeatureFlags.enableSplitContextually()) { - mSplitSelectStateController.getSplitAnimationController() - .playPlaceholderDismissAnim(mContainer, LAUNCHER_SPLIT_SELECTION_EXIT_HOME); + if (mSplitSelectStateController.isSplitSelectActive()) { + AnimUtils.goToNormalStateWithSplitDismissal(stateManager, mContainer, + LAUNCHER_SPLIT_SELECTION_EXIT_HOME, + mSplitSelectStateController.getSplitAnimationController()); + } else { + stateManager.goToState(NORMAL, animated); } AbstractFloatingView.closeAllOpenViews(mContainer, animated); } @@ -128,9 +129,11 @@ public void onTaskIconChanged(int taskId) { // If Launcher needs to return to split select state, do it now, after the icon has updated. if (mContainer.hasPendingSplitSelectInfo()) { PendingSplitSelectInfo recoveryData = mContainer.getPendingSplitSelectInfo(); - if (recoveryData.getStagedTaskId() == taskId) { + TaskContainer taskContainer; + if (recoveryData != null && recoveryData.getStagedTaskId() == taskId && (taskContainer = + mUtils.getTaskContainerById(taskId)) != null) { initiateSplitSelect( - getTaskViewByTaskId(recoveryData.getStagedTaskId()), + taskContainer, recoveryData.getStagePosition(), recoveryData.getSource() ); mContainer.finishSplitSelectRecovery(); @@ -150,7 +153,14 @@ public void reset() { public void onStateTransitionStart(LauncherState toState) { setOverviewStateEnabled(toState.isRecentsViewVisible); - setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())); + if (enableGridOnlyOverview()) { + if (toState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) { + setOverviewGridEnabled(true); + } + } else { + setOverviewGridEnabled( + toState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())); + } setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1); if (toState == OVERVIEW_MODAL_TASK) { setOverviewSelectEnabled(true); @@ -164,15 +174,18 @@ public void onStateTransitionStart(LauncherState toState) { } setFreezeViewVisibility(true); - if (mContainer.getDesktopVisibilityController() != null) { - mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState); - } } @Override public void onStateTransitionComplete(LauncherState finalState) { - if (finalState == NORMAL || finalState == SPRING_LOADED || finalState == EDIT_MODE - || finalState == ALL_APPS) { + DesktopVisibilityController.INSTANCE.get(mContainer).onLauncherStateChanged(finalState); + if (enableGridOnlyOverview()) { + if (!finalState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) { + setOverviewGridEnabled(false); + } + } + + if (!finalState.isRecentsViewVisible) { // Clean-up logic that occurs when recents is no longer in use/visible. reset(); } @@ -186,10 +199,8 @@ public void onStateTransitionComplete(LauncherState finalState) { if (finalState.isRecentsViewVisible && finalState != OVERVIEW_MODAL_TASK) { setTaskBorderEnabled(true); } - if (isOverlayEnabled) { - runActionOnRemoteHandles( - remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)); + mBlurUtils.setDrawLiveTileBelowRecents(true); } } @@ -200,7 +211,10 @@ public void setOverviewStateEnabled(boolean enabled) { LauncherState state = getStateManager().getState(); boolean hasClearAllButton = (state.getVisibleElements(mContainer) & CLEAR_ALL_BUTTON) != 0; + boolean hasAddDeskButton = (state.getVisibleElements(mContainer) + & ADD_DESK_BUTTON) != 0; setDisallowScrollToClearAll(!hasClearAllButton); + setDisallowScrollToAddDesk(!hasAddDeskButton); } } @@ -226,6 +240,11 @@ public void setModalStateEnabled(int taskId, boolean animate) { } } + @Override + protected BaseContainerInterface getContainerInterface(int displayId) { + return LauncherActivityInterface.INSTANCE; + } + @Override protected void onDismissAnimationEnds() { super.onDismissAnimationEnds(); @@ -238,10 +257,10 @@ protected void onDismissAnimationEnds() { } @Override - public void initiateSplitSelect(TaskView taskView, + public void initiateSplitSelect(TaskContainer taskContainer, @SplitConfigurationOptions.StagePosition int stagePosition, StatsLogManager.EventEnum splitEvent) { - super.initiateSplitSelect(taskView, stagePosition, splitEvent); + super.initiateSplitSelect(taskContainer, stagePosition, splitEvent); getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT); } @@ -252,44 +271,34 @@ public void initiateSplitSelect(SplitSelectSource splitSelectSource) { } @Override - protected boolean canLaunchFullscreenTask() { - if (FeatureFlags.enableSplitContextually()) { - return !mSplitSelectStateController.isSplitSelectActive(); - } else { - return !mContainer.isInState(OVERVIEW_SPLIT_SELECT); - } + public boolean canLaunchFullscreenTask() { + return !mSplitSelectStateController.isSplitSelectActive(); } @Override - public void onGestureAnimationStart(Task[] runningTasks, - RotationTouchHelper rotationTouchHelper) { - super.onGestureAnimationStart(runningTasks, rotationTouchHelper); - DesktopVisibilityController desktopVisibilityController = - mContainer.getDesktopVisibilityController(); - if (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) { + public void onGestureAnimationStart(GroupedTaskInfo groupedTaskInfo) { + super.onGestureAnimationStart(groupedTaskInfo); + if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) { // TODO: b/333533253 - Remove after flag rollout - desktopVisibilityController.setRecentsGestureStart(); + DesktopVisibilityController.INSTANCE.get(mContainer).setRecentsGestureStart(); } } @Override public void onGestureAnimationEnd() { - DesktopVisibilityController desktopVisibilityController = - mContainer.getDesktopVisibilityController(); + final DesktopVisibilityController desktopVisibilityController = + DesktopVisibilityController.INSTANCE.get(mContainer); boolean showDesktopApps = false; - GestureState.GestureEndTarget endTarget = null; - if (desktopVisibilityController != null) { - desktopVisibilityController = mContainer.getDesktopVisibilityController(); - endTarget = mCurrentGestureEndTarget; - if (endTarget == GestureState.GestureEndTarget.LAST_TASK - && 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; - } + GestureState.GestureEndTarget endTarget = mCurrentGestureEndTarget; + if (endTarget == GestureState.GestureEndTarget.LAST_TASK + && desktopVisibilityController.isInDesktopModeAndNotInOverview( + mContainer.getDisplayId())) { + // 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 (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) { + if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) { // TODO: b/333533253 - Remove after flag rollout desktopVisibilityController.setRecentsGestureEnd(endTarget); } diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java index 9c1f0663e3b..ea0c41ce790 100644 --- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java @@ -40,6 +40,8 @@ import com.android.launcher3.util.NavigationMode; import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks; import com.android.quickstep.util.LayoutUtils; +import com.android.wm.shell.shared.TypefaceUtils; +import com.android.wm.shell.shared.TypefaceUtils.FontFamily; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -65,8 +67,7 @@ public class OverviewActionsView extends FrameLayo HIDDEN_DESKTOP }) @Retention(RetentionPolicy.SOURCE) - public @interface ActionsHiddenFlags { - } + public @interface ActionsHiddenFlags { } public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0; public static final int HIDDEN_NO_TASKS = 1 << 1; @@ -79,10 +80,9 @@ public class OverviewActionsView extends FrameLayo @IntDef(flag = true, value = { DISABLED_SCROLLING, DISABLED_ROTATED, - DISABLED_NO_THUMBNAIL }) + DISABLED_NO_THUMBNAIL}) @Retention(RetentionPolicy.SOURCE) - public @interface ActionsDisabledFlags { - } + public @interface ActionsDisabledFlags { } public static final int DISABLED_SCROLLING = 1 << 0; public static final int DISABLED_ROTATED = 1 << 1; @@ -98,14 +98,11 @@ public class OverviewActionsView extends FrameLayo private static final int INDEX_3P_LAUNCHER = 7; private static final int NUM_ALPHAS = 8; - public @interface SplitButtonHiddenFlags { - } - + public @interface SplitButtonHiddenFlags { } public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0; /** - * Holds an AnimatedFloat for each alpha property, used to set or animate alpha - * values in + * 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]; @@ -117,14 +114,11 @@ public class OverviewActionsView extends FrameLayo /** 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. - */ + /** 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 + * 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; @@ -163,18 +157,17 @@ public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int de protected void onFinishInflate() { super.onFinishInflate(); // 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. + // 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. + TypefaceUtils.setTypeface(mSaveAppPairButton, FontFamily.GSF_LABEL_LARGE); + // 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); + 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 + // 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; @@ -185,10 +178,8 @@ protected void onFinishInflate() { }, 1f /* initialValue */); } - // 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 + // 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); @@ -245,15 +236,12 @@ public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean e } /** - * Updates the proper disabled flag to indicate whether OverviewActionsView - * should be enabled. - * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to - * enable/disable + * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled. + * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable * buttons individually, currently done for select button in subclass. * * @param disabledFlags The flag to update. - * @param enable Whether to enable the disable flag: True will cause view - * to be disabled. + * @param enable Whether to enable the disable flag: True will cause view to be disabled. */ public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) { if (enable) { @@ -266,14 +254,11 @@ public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean } /** - * Updates a batch of flags to hide and show actions buttons when a grouped task - * (split screen) + * 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. + * @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 @@ -284,8 +269,7 @@ public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) } /** - * Updates a batch of flags to hide and show actions buttons for tablet/non - * tablet case. + * Updates a batch of flags to hide and show actions buttons for tablet/non tablet case. */ private void updateForIsTablet() { assert mDp != null; @@ -295,7 +279,9 @@ private void updateForIsTablet() { } private void updateActionButtonsVisibility() { - assert mDp != null; + if (mDp == null) { + return; + } boolean showSingleTaskActions = !mIsGroupedTask; boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair; Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = [" @@ -320,17 +306,14 @@ private MultiValueAlpha getGroupActionsAlphas() { } /** - * Updates the proper flags to indicate whether the "Split screen" button should - * be hidden. + * Updates the proper flags to indicate whether the "Split screen" button should be hidden. * * @param flag The flag to update. - * @param enable Whether to enable the hidden flag: True will cause view to be - * hidden. + * @param enable Whether to enable the hidden flag: True will cause view to be hidden. */ void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) { - if (mSplitButton == null) - return; + if (mSplitButton == null) return; if (enable) { mSplitButtonHiddenFlags |= flag; } else { @@ -372,19 +355,14 @@ public boolean areActionsButtonsVisible() { } /** - * Offsets OverviewActionsView horizontal position based on 3 button nav - * container in taskbar. + * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar. */ private void updatePadding() { - // If taskbar is in overview, overview action has dedicated space above nav - // buttons + // If taskbar is in overview, overview action has dedicated space above nav buttons setPadding(mInsets.left, 0, mInsets.right, 0); } - /** - * Updates vertical margins for different navigation mode or configuration - * changes. - */ + /** Updates vertical margins for different navigation mode or configuration changes. */ public void updateVerticalMargin(NavigationMode mode) { updateActionBarPosition(mActionButtons); updateActionBarPosition(mSaveAppPairButton); diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt new file mode 100644 index 00000000000..6acaeae8db5 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.views + +import android.os.VibrationAttributes +import androidx.dynamicanimation.animation.FloatPropertyCompat +import androidx.dynamicanimation.animation.FloatValueHolder +import androidx.dynamicanimation.animation.SpringAnimation +import androidx.dynamicanimation.animation.SpringForce +import com.android.launcher3.Flags.enableGridOnlyOverview +import com.android.launcher3.R +import com.android.launcher3.Utilities.boundToRange +import com.android.launcher3.util.DynamicResource +import com.android.launcher3.util.MSDLPlayerWrapper +import com.android.quickstep.util.TaskGridNavHelper +import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.InteractionProperties +import kotlin.math.abs +import kotlin.math.roundToInt +import kotlin.math.sign + +/** + * Helper class for [RecentsView]. This util class contains refactored and extracted functions from + * RecentsView related to TaskView dismissal. + */ +class RecentsDismissUtils(private val recentsView: RecentsView<*, *>) { + + /** + * Creates the spring animations which run when a dragged task view in overview is released. + * + *

When a task dismiss is cancelled, the task will return to its original position via a + * spring animation. As it passes the threshold of its settling state, its neighbors will spring + * in response to the perceived impact of the settling task. + */ + fun createTaskDismissSettlingSpringAnimation( + draggedTaskView: TaskView?, + velocity: Float, + isDismissing: Boolean, + dismissLength: Int, + onEndRunnable: () -> Unit, + ): SpringAnimation? { + draggedTaskView ?: return null + val taskDismissFloatProperty = + FloatPropertyCompat.createFloatPropertyCompat( + draggedTaskView.secondaryDismissTranslationProperty + ) + val minVelocity = + recentsView.pagedOrientationHandler.getSecondaryDimension(draggedTaskView).toFloat() + val startVelocity = abs(velocity).coerceAtLeast(minVelocity) * velocity.sign + // Animate dragged task towards dismissal or rest state. + val draggedTaskViewSpringAnimation = + SpringAnimation(draggedTaskView, taskDismissFloatProperty) + .setSpring(createExpressiveDismissSpringForce()) + .setStartVelocity(startVelocity) + .addUpdateListener { animation, value, _ -> + if (isDismissing && abs(value) >= abs(dismissLength)) { + animation.cancel() + } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) { + recentsView.runActionOnRemoteHandles { remoteTargetHandle -> + remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value = + taskDismissFloatProperty.getValue(draggedTaskView) + } + recentsView.redrawLiveTile() + } + } + .addEndListener { _, _, _, _ -> + if (isDismissing) { + if (!recentsView.showAsGrid() || enableGridOnlyOverview()) { + runTaskGridReflowSpringAnimation( + draggedTaskView, + getDismissedTaskGapForReflow(draggedTaskView), + onEndRunnable, + ) + } else { + recentsView.dismissTaskView( + draggedTaskView, + /* animateTaskView = */ false, + /* removeTask = */ true, + ) + onEndRunnable() + } + } else { + recentsView.onDismissAnimationEnds() + onEndRunnable() + } + } + if (!isDismissing) { + addNeighborSettlingSpringAnimations( + draggedTaskView, + draggedTaskViewSpringAnimation, + driverProgressThreshold = 0f, + isSpringDirectionVertical = true, + minVelocity = startVelocity, + ) + } + return draggedTaskViewSpringAnimation + } + + private fun addNeighborSettlingSpringAnimations( + draggedTaskView: TaskView, + springAnimationDriver: SpringAnimation, + tasksToExclude: List = emptyList(), + driverProgressThreshold: Float, + isSpringDirectionVertical: Boolean, + minVelocity: Float, + ) { + // Empty spring animation exists for conditional start, and to drive neighboring springs. + val neighborsToSettle = + SpringAnimation(FloatValueHolder()).setSpring(createExpressiveDismissSpringForce()) + + // Add tasks before dragged index, fanning out from the dragged task. + // The order they are added matters, as each spring drives the next. + var previousNeighbor = neighborsToSettle + getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true) + .filter { (taskView, _) -> !tasksToExclude.contains(taskView) } + .forEach { (taskView, offset) -> + previousNeighbor = + createNeighboringTaskViewSpringAnimation( + taskView, + offset * ADDITIONAL_DISMISS_DAMPING_RATIO, + previousNeighbor, + isSpringDirectionVertical, + ) + } + // Add tasks after dragged index, fanning out from the dragged task. + // The order they are added matters, as each spring drives the next. + previousNeighbor = neighborsToSettle + getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false) + .filter { (taskView, _) -> !tasksToExclude.contains(taskView) } + .forEach { (taskView, offset) -> + previousNeighbor = + createNeighboringTaskViewSpringAnimation( + taskView, + offset * ADDITIONAL_DISMISS_DAMPING_RATIO, + previousNeighbor, + isSpringDirectionVertical, + ) + } + + val isCurrentDisplacementAboveOrigin = + recentsView.pagedOrientationHandler.isGoingUp( + draggedTaskView.secondaryDismissTranslationProperty.get(draggedTaskView), + recentsView.isRtl, + ) + addThresholdSpringAnimationTrigger( + springAnimationDriver, + progressThreshold = driverProgressThreshold, + neighborsToSettle, + isCurrentDisplacementAboveOrigin, + minVelocity, + ) + } + + /** As spring passes threshold for the first time, run conditional spring with velocity. */ + private fun addThresholdSpringAnimationTrigger( + springAnimationDriver: SpringAnimation, + progressThreshold: Float, + conditionalSpring: SpringAnimation, + isCurrentDisplacementAboveOrigin: Boolean, + minVelocity: Float, + ) { + val runSettlingAtVelocity = { velocity: Float -> + conditionalSpring.setStartVelocity(velocity).animateToFinalPosition(0f) + playDismissSettlingHaptic(velocity) + } + if (isCurrentDisplacementAboveOrigin) { + var lastPosition = 0f + var startSettling = false + springAnimationDriver.addUpdateListener { _, value, velocity -> + // We do not compare to the threshold directly, as the update listener + // does not necessarily hit every value. Do not check again once it has started + // settling, as a spring can bounce past the end value multiple times. + if (startSettling) return@addUpdateListener + if ( + lastPosition < progressThreshold && value >= progressThreshold || + lastPosition > progressThreshold && value <= progressThreshold + ) { + startSettling = true + } + lastPosition = value + if (startSettling) { + runSettlingAtVelocity(velocity) + } + } + } else { + // Run settling animations immediately when displacement is already below settled state. + runSettlingAtVelocity(minVelocity) + } + } + + /** + * Gets pairs of (TaskView, offset) adjacent the dragged task in visual order. + * + *

Gets tasks either before or after the dragged task along with their offset from it. The + * offset is the distance between indices for carousels, or distance between columns for grids. + */ + private fun getTasksOffsetPairAdjacentToDraggedTask( + draggedTaskView: TaskView, + towardsStart: Boolean, + ): Sequence> { + if (recentsView.showAsGrid()) { + val taskGridNavHelper = + TaskGridNavHelper( + recentsView.mUtils.getTopRowIdArray(), + recentsView.mUtils.getBottomRowIdArray(), + recentsView.mUtils.getLargeTaskViewIds(), + hasAddDesktopButton = false, + ) + return taskGridNavHelper + .gridTaskViewIdOffsetPairInTabOrderSequence( + draggedTaskView.taskViewId, + towardsStart, + ) + .mapNotNull { (taskViewId, columnOffset) -> + recentsView.getTaskViewFromTaskViewId(taskViewId)?.let { taskView -> + Pair(taskView, columnOffset) + } + } + } else { + val taskViewList = recentsView.mUtils.taskViews.toList() + val draggedTaskViewIndex = taskViewList.indexOf(draggedTaskView) + + return if (towardsStart) { + taskViewList + .take(draggedTaskViewIndex) + .reversed() + .mapIndexed { index, taskView -> Pair(taskView, index + 1) } + .asSequence() + } else { + taskViewList + .takeLast(taskViewList.size - draggedTaskViewIndex - 1) + .mapIndexed { index, taskView -> Pair(taskView, index + 1) } + .asSequence() + } + } + } + + /** Creates a neighboring task view spring, driven by the spring of its neighbor. */ + private fun createNeighboringTaskViewSpringAnimation( + taskView: TaskView, + dampingOffsetRatio: Float, + previousNeighborSpringAnimation: SpringAnimation, + springingDirectionVertical: Boolean, + ): SpringAnimation { + val springProperty = + if (springingDirectionVertical) taskView.secondaryDismissTranslationProperty + else taskView.primaryDismissTranslationProperty + val neighboringTaskViewSpringAnimation = + SpringAnimation(taskView, FloatPropertyCompat.createFloatPropertyCompat(springProperty)) + .setSpring(createExpressiveDismissSpringForce(dampingOffsetRatio)) + // Update live tile on spring animation. + if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) { + neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ -> + recentsView.runActionOnRemoteHandles { remoteTargetHandle -> + val taskTranslation = + if (springingDirectionVertical) { + remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation + } else { + remoteTargetHandle.taskViewSimulator.taskPrimaryTranslation + } + taskTranslation.value = springProperty.get(taskView) + } + recentsView.redrawLiveTile() + } + } + // Drive current neighbor's spring with the previous neighbor's. + previousNeighborSpringAnimation.addUpdateListener { _, value, _ -> + neighboringTaskViewSpringAnimation.animateToFinalPosition(value) + } + return neighboringTaskViewSpringAnimation + } + + private fun createExpressiveDismissSpringForce(dampingRatioOffset: Float = 0f): SpringForce { + val resourceProvider = DynamicResource.provider(recentsView.mContainer) + return SpringForce() + .setDampingRatio( + resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio) + + dampingRatioOffset + ) + .setStiffness( + resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_stiffness) + ) + } + + private fun createExpressiveGridReflowSpringForce( + finalPosition: Float = Float.MAX_VALUE + ): SpringForce { + val resourceProvider = DynamicResource.provider(recentsView.mContainer) + return SpringForce(finalPosition) + .setDampingRatio( + resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_damping_ratio) + ) + .setStiffness( + resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_stiffness) + ) + } + + /** + * Plays a haptic as the dragged task view settles back into its rest state. + * + *

Haptic intensity is proportional to velocity. + */ + private fun playDismissSettlingHaptic(velocity: Float) { + val maxDismissSettlingVelocity = + recentsView.pagedOrientationHandler.getSecondaryDimension(recentsView) + MSDLPlayerWrapper.INSTANCE.get(recentsView.context) + ?.playToken( + MSDLToken.CANCEL, + InteractionProperties.DynamicVibrationScale( + boundToRange(abs(velocity) / maxDismissSettlingVelocity, 0f, 1f), + VibrationAttributes.Builder() + .setUsage(VibrationAttributes.USAGE_TOUCH) + .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT) + .build(), + ), + ) + } + + /** Animates RecentsView's scale to the provided value, using spring animations. */ + fun animateRecentsScale(scale: Float): SpringAnimation { + val resourceProvider = DynamicResource.provider(recentsView.mContainer) + val dampingRatio = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio) + val stiffness = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_stiffness) + + // Spring which sets the Recents scale on update. This is needed, as the SpringAnimation + // struggles to animate small values like changing recents scale from 0.9 to 1. So + // we animate over a larger range (e.g. 900 to 1000) and convert back to the required value. + // (This is instead of converting RECENTS_SCALE_PROPERTY to a FloatPropertyCompat and + // animating it directly via springs.) + val initialRecentsScaleSpringValue = + RECENTS_SCALE_SPRING_MULTIPLIER * RECENTS_SCALE_PROPERTY.get(recentsView) + return SpringAnimation(FloatValueHolder(initialRecentsScaleSpringValue)) + .setSpring( + SpringForce(initialRecentsScaleSpringValue) + .setDampingRatio(dampingRatio) + .setStiffness(stiffness) + ) + .addUpdateListener { _, value, _ -> + RECENTS_SCALE_PROPERTY.setValue( + recentsView, + value / RECENTS_SCALE_SPRING_MULTIPLIER, + ) + } + .apply { animateToFinalPosition(RECENTS_SCALE_SPRING_MULTIPLIER * scale) } + } + + /** Animates with springs the TaskViews beyond the dismissed task to fill the gap it left. */ + private fun runTaskGridReflowSpringAnimation( + dismissedTaskView: TaskView, + dismissedTaskGap: Float, + onEndRunnable: () -> Unit, + ) { + // Empty spring animation exists for conditional start, and to drive neighboring springs. + val springAnimationDriver = + SpringAnimation(FloatValueHolder()) + .setSpring(createExpressiveGridReflowSpringForce(finalPosition = dismissedTaskGap)) + val towardsStart = if (recentsView.isRtl) dismissedTaskGap < 0 else dismissedTaskGap > 0 + + var tasksToReflow: List + // Build the chains of Spring Animations + when { + !recentsView.showAsGrid() -> { + tasksToReflow = + getTasksToReflow( + recentsView.mUtils.taskViews.toList(), + dismissedTaskView, + towardsStart, + ) + buildDismissReflowSpringAnimationChain( + tasksToReflow, + dismissedTaskGap, + previousSpring = springAnimationDriver, + ) + } + dismissedTaskView.isLargeTile -> { + tasksToReflow = + getTasksToReflow( + recentsView.mUtils.getLargeTaskViews(), + dismissedTaskView, + towardsStart, + ) + val lastSpringAnimation = + buildDismissReflowSpringAnimationChain( + tasksToReflow, + dismissedTaskGap, + previousSpring = springAnimationDriver, + ) + // Add all top and bottom grid tasks when animating towards the end of the grid. + if (!towardsStart) { + tasksToReflow += recentsView.mUtils.getTopRowTaskViews() + tasksToReflow += recentsView.mUtils.getBottomRowTaskViews() + buildDismissReflowSpringAnimationChain( + recentsView.mUtils.getTopRowTaskViews(), + dismissedTaskGap, + previousSpring = lastSpringAnimation, + ) + buildDismissReflowSpringAnimationChain( + recentsView.mUtils.getBottomRowTaskViews(), + dismissedTaskGap, + previousSpring = lastSpringAnimation, + ) + } + } + recentsView.isOnGridBottomRow(dismissedTaskView) -> { + tasksToReflow = + getTasksToReflow( + recentsView.mUtils.getBottomRowTaskViews(), + dismissedTaskView, + towardsStart, + ) + buildDismissReflowSpringAnimationChain( + tasksToReflow, + dismissedTaskGap, + previousSpring = springAnimationDriver, + ) + } + else -> { + tasksToReflow = + getTasksToReflow( + recentsView.mUtils.getTopRowTaskViews(), + dismissedTaskView, + towardsStart, + ) + buildDismissReflowSpringAnimationChain( + tasksToReflow, + dismissedTaskGap, + previousSpring = springAnimationDriver, + ) + } + } + + if (tasksToReflow.isNotEmpty()) { + addNeighborSettlingSpringAnimations( + dismissedTaskView, + springAnimationDriver, + tasksToExclude = tasksToReflow, + driverProgressThreshold = dismissedTaskGap, + isSpringDirectionVertical = false, + minVelocity = 0f, + ) + } else { + springAnimationDriver.addEndListener { _, _, _, _ -> + // Play the same haptic as when neighbors spring into place. + MSDLPlayerWrapper.INSTANCE.get(recentsView.context)?.playToken(MSDLToken.CANCEL) + } + } + + // Start animations and remove the dismissed task at the end, dismiss immediately if no + // neighboring tasks exist. + val runGridEndAnimationAndRelayout = { + recentsView.expressiveDismissTaskView(dismissedTaskView, onEndRunnable) + } + springAnimationDriver?.apply { + addEndListener { _, _, _, _ -> runGridEndAnimationAndRelayout() } + animateToFinalPosition(dismissedTaskGap) + } ?: runGridEndAnimationAndRelayout() + } + + private fun getDismissedTaskGapForReflow(dismissedTaskView: TaskView): Float { + val screenStart = recentsView.pagedOrientationHandler.getPrimaryScroll(recentsView) + val screenEnd = + screenStart + recentsView.pagedOrientationHandler.getMeasuredSize(recentsView) + val taskStart = + recentsView.pagedOrientationHandler.getChildStart(dismissedTaskView) + + dismissedTaskView.getOffsetAdjustment(recentsView.showAsGrid()) + val taskSize = + recentsView.pagedOrientationHandler.getMeasuredSize(dismissedTaskView) * + dismissedTaskView.getSizeAdjustment(recentsView.showAsFullscreen()) + val taskEnd = taskStart + taskSize + + val isDismissedTaskBeyondEndOfScreen = + if (recentsView.isRtl) taskEnd > screenEnd else taskStart < screenStart + if ( + dismissedTaskView.isLargeTile && + isDismissedTaskBeyondEndOfScreen && + recentsView.mUtils.getLargeTileCount() == 1 + ) { + return with(recentsView) { + pagedOrientationHandler.getPrimaryScroll(this) - + getScrollForPage(indexOfChild(mUtils.getFirstNonDesktopTaskView())) + } + .toFloat() + } + + // If current page is beyond last TaskView's index, use last TaskView to calculate offset. + val lastTaskViewIndex = recentsView.indexOfChild(recentsView.mUtils.getLastTaskView()) + val currentPage = recentsView.currentPage.coerceAtMost(lastTaskViewIndex) + val dismissHorizontalFactor = + when { + dismissedTaskView.isGridTask -> 1f + currentPage == lastTaskViewIndex -> -1f + recentsView.indexOfChild(dismissedTaskView) < currentPage -> -1f + else -> 1f + } * (if (recentsView.isRtl) 1f else -1f) + + return (recentsView.pagedOrientationHandler.getPrimarySize(dismissedTaskView) + + recentsView.pageSpacing) * dismissHorizontalFactor + } + + private fun getTasksToReflow( + taskViews: List, + dismissedTaskView: TaskView, + towardsStart: Boolean, + ): List { + val dismissedTaskViewIndex = taskViews.indexOf(dismissedTaskView) + if (dismissedTaskViewIndex == -1) { + return emptyList() + } + return if (towardsStart) { + taskViews.take(dismissedTaskViewIndex).reversed() + } else { + taskViews.takeLast(taskViews.size - dismissedTaskViewIndex - 1) + } + } + + private fun willTaskBeVisibleAfterDismiss(taskView: TaskView, taskTranslation: Int): Boolean { + val screenStart = recentsView.pagedOrientationHandler.getPrimaryScroll(recentsView) + val screenEnd = + screenStart + recentsView.pagedOrientationHandler.getMeasuredSize(recentsView) + return recentsView.isTaskViewWithinBounds( + taskView, + screenStart, + screenEnd, + /* taskViewTranslation = */ taskTranslation, + ) + } + + /** Builds a chain of spring animations for task reflow after dismissal */ + private fun buildDismissReflowSpringAnimationChain( + taskViews: Iterable, + dismissedTaskGap: Float, + previousSpring: SpringAnimation, + ): SpringAnimation { + var lastTaskViewSpring = previousSpring + taskViews + .filter { taskView -> + willTaskBeVisibleAfterDismiss(taskView, dismissedTaskGap.roundToInt()) + } + .forEach { taskView -> + val taskViewSpringAnimation = + SpringAnimation( + taskView, + FloatPropertyCompat.createFloatPropertyCompat( + taskView.primaryDismissTranslationProperty + ), + ) + .setSpring(createExpressiveGridReflowSpringForce(dismissedTaskGap)) + // Update live tile on spring animation. + if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) { + taskViewSpringAnimation.addUpdateListener { _, _, _ -> + recentsView.runActionOnRemoteHandles { remoteTargetHandle -> + remoteTargetHandle.taskViewSimulator.taskPrimaryTranslation.value = + taskView.primaryDismissTranslationProperty.get(taskView) + } + recentsView.redrawLiveTile() + } + } + lastTaskViewSpring.addUpdateListener { _, value, _ -> + taskViewSpringAnimation.animateToFinalPosition(value) + } + lastTaskViewSpring = taskViewSpringAnimation + } + return lastTaskViewSpring + } + + private companion object { + // The additional damping to apply to tasks further from the dismissed task. + private const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f + private const val RECENTS_SCALE_SPRING_MULTIPLIER = 1000f + } +} diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 163732ff07c..63bb799c97d 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -17,6 +17,8 @@ package com.android.quickstep.views; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.os.Trace.traceBegin; +import static android.os.Trace.traceEnd; import static android.view.Surface.ROTATION_0; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; @@ -25,21 +27,25 @@ import static com.android.app.animation.Interpolators.ACCELERATE_0_75; import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; import static com.android.app.animation.Interpolators.DECELERATE_2; +import static com.android.app.animation.Interpolators.EMPHASIZED; import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN; import static com.android.app.animation.Interpolators.FINAL_FRAME; 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.enableDesktopExplodedView; +import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation; import static com.android.launcher3.Flags.enableGridOnlyOverview; +import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile; +import static com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur; import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; +import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks; import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; +import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; @@ -51,27 +57,26 @@ 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; +import static com.android.launcher3.statehandlers.DesktopVisibilityController.INACTIVE_DESK_ID; import static com.android.launcher3.testing.shared.TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE; import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; 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.MultiPropertyFactory.MULTI_PROPERTY_VALUE; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; +import static com.android.quickstep.BaseContainerInterface.getTaskDimension; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; +import static com.android.quickstep.util.DesksUtils.areMultiDesksFlagsEnabled; import static com.android.quickstep.util.LogUtils.splitFailureMessage; -import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_DOWN; -import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_LEFT; -import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_RIGHT; -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.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; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; -import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SCREEN; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE; +import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS; +import static com.android.quickstep.views.TaskView.SPLIT_ALPHA; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -90,6 +95,7 @@ import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; @@ -100,6 +106,7 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.os.VibrationEffect; import android.text.Layout; @@ -125,12 +132,15 @@ import android.widget.ListView; import android.widget.OverScroller; import android.widget.Toast; +import android.window.DesktopModeFlags; import android.window.PictureInPictureSurfaceTransaction; +import android.window.TransitionInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.core.graphics.ColorUtils; +import androidx.dynamicanimation.animation.SpringAnimation; import com.android.internal.jank.Cuj; import com.android.launcher3.AbstractFloatingView; @@ -138,7 +148,6 @@ 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; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -154,6 +163,7 @@ import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statehandlers.DepthController; +import com.android.launcher3.statehandlers.DesktopVisibilityController; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.statemanager.StatefulContainer; @@ -174,8 +184,11 @@ import com.android.launcher3.util.TranslateEdgeEffect; import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.ViewPool; +import com.android.launcher3.util.coroutines.DispatcherProvider; +import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener; import com.android.quickstep.BaseContainerInterface; import com.android.quickstep.GestureState; +import com.android.quickstep.HighResLoadingState; import com.android.quickstep.OverviewCommandHelper; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; @@ -188,24 +201,31 @@ import com.android.quickstep.SplitSelectionListener; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskOverlayFactory; -import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.ViewUtils; +import com.android.quickstep.fallback.window.RecentsWindowFlags; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.recents.data.TasksRepository; +import com.android.quickstep.recents.data.RecentTasksRepository; +import com.android.quickstep.recents.data.RecentsDeviceProfileRepository; +import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl; +import com.android.quickstep.recents.data.RecentsRotationStateRepository; +import com.android.quickstep.recents.data.RecentsRotationStateRepositoryImpl; +import com.android.quickstep.recents.di.RecentsDependencies; import com.android.quickstep.recents.viewmodel.RecentsViewData; -import com.android.quickstep.util.ActiveGestureErrorDetector; -import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.recents.viewmodel.RecentsViewModel; +import com.android.quickstep.util.ActiveGestureProtoLogProxy; import com.android.quickstep.util.AnimUtils; import com.android.quickstep.util.DesktopTask; import com.android.quickstep.util.GroupTask; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsAtomicAnimationFactory; import com.android.quickstep.util.RecentsOrientedState; +import com.android.quickstep.util.SingleTask; import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps; import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.util.SplitSelectStateController; +import com.android.quickstep.util.SplitTask; import com.android.quickstep.util.SurfaceTransaction; import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TaskGridNavHelper; @@ -213,47 +233,55 @@ import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.VibrationConstants; -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.Task.TaskKey; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 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 com.android.wm.shell.shared.GroupedTaskInfo; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import kotlin.Unit; +import kotlin.collections.CollectionsKt; +import kotlin.jvm.functions.Function0; + +import kotlinx.coroutines.CoroutineScope; 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; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; - /** * A list of recent tasks. + * * @param : the container that should host recents view - * @param : the type of base state that will be used + * @param : the type of base state that will be used */ - -public abstract class RecentsView, STATE_TYPE extends BaseState> extends PagedView implements Insettable, - TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, - TaskVisualsChangeListener { + HighResLoadingState.HighResLoadingStateChangedCallback, + TaskVisualsChangeListener, DesktopVisibilityListener { - private static final String TAG = "RecentsView"; + protected static final String TAG = "RecentsView"; private static final boolean DEBUG = false; - public static final FloatProperty CONTENT_ALPHA = - new FloatProperty("contentAlpha") { + public static final FloatProperty> CONTENT_ALPHA = + new FloatProperty<>("contentAlpha") { @Override public void setValue(RecentsView view, float v) { view.setContentAlpha(v); @@ -265,8 +293,8 @@ public Float get(RecentsView view) { } }; - public static final FloatProperty FULLSCREEN_PROGRESS = - new FloatProperty("fullscreenProgress") { + public static final FloatProperty> FULLSCREEN_PROGRESS = + new FloatProperty<>("fullscreenProgress") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setFullscreenProgress(v); @@ -278,8 +306,8 @@ public Float get(RecentsView recentsView) { } }; - public static final FloatProperty TASK_MODALNESS = - new FloatProperty("taskModalness") { + public static final FloatProperty> TASK_MODALNESS = + new FloatProperty<>("taskModalness") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskModalness(v); @@ -291,8 +319,8 @@ public Float get(RecentsView recentsView) { } }; - public static final FloatProperty ADJACENT_PAGE_HORIZONTAL_OFFSET = - new FloatProperty("adjacentPageHorizontalOffset") { + public static final FloatProperty> ADJACENT_PAGE_HORIZONTAL_OFFSET = + new FloatProperty<>("adjacentPageHorizontalOffset") { @Override public void setValue(RecentsView recentsView, float v) { if (recentsView.mAdjacentPageHorizontalOffset != v) { @@ -307,6 +335,20 @@ public Float get(RecentsView recentsView) { } }; + public static final FloatProperty> RUNNING_TASK_ATTACH_ALPHA = + new FloatProperty<>("runningTaskAttachAlpha") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.mRunningTaskAttachAlpha = v; + recentsView.applyAttachAlpha(); + } + + @Override + public Float get(RecentsView recentsView) { + return recentsView.mRunningTaskAttachAlpha; + } + }; + public static final int SCROLL_VIBRATION_PRIMITIVE = Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1; public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f; @@ -317,10 +359,9 @@ public Float get(RecentsView recentsView) { /** * Can be used to tint the color of the RecentsView to simulate a scrim that can views * excluded from. Really should be a proper scrim. - * TODO(b/187528071): Remove this and replace with a real scrim. */ - private static final FloatProperty COLOR_TINT = - new FloatProperty("colorTint") { + private static final FloatProperty> COLOR_TINT = + new FloatProperty<>("colorTint") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setColorTint(v); @@ -338,8 +379,8 @@ public Float get(RecentsView recentsView) { * more specific, we'd want to create a similar FloatProperty just for a TaskView's * offsetX/Y property */ - public static final FloatProperty TASK_SECONDARY_TRANSLATION = - new FloatProperty("taskSecondaryTranslation") { + public static final FloatProperty> TASK_SECONDARY_TRANSLATION = + new FloatProperty<>("taskSecondaryTranslation") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskViewsResistanceTranslation(v); @@ -357,8 +398,8 @@ public Float get(RecentsView recentsView) { * more specific, we'd want to create a similar FloatProperty just for a TaskView's * offsetX/Y property */ - public static final FloatProperty TASK_PRIMARY_SPLIT_TRANSLATION = - new FloatProperty("taskPrimarySplitTranslation") { + public static final FloatProperty> TASK_PRIMARY_SPLIT_TRANSLATION = + new FloatProperty<>("taskPrimarySplitTranslation") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskViewsPrimarySplitTranslation(v); @@ -370,8 +411,8 @@ public Float get(RecentsView recentsView) { } }; - public static final FloatProperty TASK_SECONDARY_SPLIT_TRANSLATION = - new FloatProperty("taskSecondarySplitTranslation") { + public static final FloatProperty> TASK_SECONDARY_SPLIT_TRANSLATION = + new FloatProperty<>("taskSecondarySplitTranslation") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskViewsSecondarySplitTranslation(v); @@ -384,15 +425,12 @@ public Float get(RecentsView recentsView) { }; /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ - public static final FloatProperty RECENTS_SCALE_PROPERTY = - new FloatProperty("recentsScale") { + public static final FloatProperty> RECENTS_SCALE_PROPERTY = + new FloatProperty<>("recentsScale") { @Override 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() { @@ -417,8 +455,8 @@ public Float get(RecentsView view) { * Progress of Recents view from carousel layout to grid layout. If Recents is not shown as a * grid, then the value remains 0. */ - public static final FloatProperty RECENTS_GRID_PROGRESS = - new FloatProperty("recentsGrid") { + public static final FloatProperty> RECENTS_GRID_PROGRESS = + new FloatProperty<>("recentsGrid") { @Override public void setValue(RecentsView view, float gridProgress) { view.setGridProgress(gridProgress); @@ -430,12 +468,27 @@ public Float get(RecentsView view) { } }; + public static final FloatProperty> DESKTOP_CAROUSEL_DETACH_PROGRESS = + new FloatProperty<>("desktopCarouselDetachProgress") { + @Override + public void setValue(RecentsView view, float offset) { + view.mDesktopCarouselDetachProgress = offset; + view.applyAttachAlpha(); + view.updatePageOffsets(); + } + + @Override + public Float get(RecentsView view) { + return view.mDesktopCarouselDetachProgress; + } + }; + /** * Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and * being in any other state has a value of 0. */ - public static final FloatProperty TASK_THUMBNAIL_SPLASH_ALPHA = - new FloatProperty("taskThumbnailSplashAlpha") { + public static final FloatProperty> TASK_THUMBNAIL_SPLASH_ALPHA = + new FloatProperty<>("taskThumbnailSplashAlpha") { @Override public void setValue(RecentsView view, float taskThumbnailSplashAlpha) { view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); @@ -463,11 +516,8 @@ public Float get(RecentsView view) { 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 BaseContainerInterface mSizeStrategy; + protected final BaseContainerInterface mSizeStrategy; @Nullable protected RecentsAnimationController mRecentsAnimationController; @Nullable @@ -483,11 +533,9 @@ public Float get(RecentsView view) { @Nullable protected RemoteTargetHandle[] mRemoteTargetHandles; - protected final Rect mLastComputedCarouselTaskSize = new Rect(); protected final Rect mLastComputedTaskSize = new Rect(); protected final Rect mLastComputedGridSize = new Rect(); protected final Rect mLastComputedGridTaskSize = new Rect(); - private TaskView mSelectedTask = null; // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot. @Nullable protected Float mLastComputedTaskStartPushOutDistance = null; @@ -511,19 +559,25 @@ public Float get(RecentsView view) { private final int mSplitPlaceholderSize; private final int mSplitPlaceholderInset; private final ClearAllButton mClearAllButton; + @Nullable + private AddDesktopButton mAddDesktopButton = null; private final Rect mClearAllButtonDeadZoneRect = new Rect(); private final Rect mTaskViewDeadZoneRect = new Rect(); + private final Rect mTopRowDeadZoneRect = new Rect(); + private final Rect mBottomRowDeadZoneRect = new Rect(); + + @Nullable + private DesktopVisibilityController mDesktopVisibilityController = null; + /** - * Reflects if Recents is currently in the middle of a gesture, and if so, which tasks are - * running. If a gesture is not in progress, this will be null. + * Reflects if Recents is currently in the middle of a gesture, and if so, which related + * [GroupedTaskInfo] is running. If a gesture is not in progress, this will be null. */ - private @Nullable Task[] mActiveGestureRunningTasks; + private @Nullable GroupedTaskInfo mActiveGestureGroupedTaskInfo; // Keeps track of the previously known visible tasks for purposes of loading/unloading task data private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray(); - private final InvariantDeviceProfile mIdp; - /** * Getting views should be done via {@link #getTaskViewFromPool(int)} */ @@ -531,9 +585,11 @@ public Float get(RecentsView view) { private final ViewPool mGroupedTaskViewPool; private final ViewPool mDesktopTaskViewPool; - private final TaskOverlayFactory mTaskOverlayFactory; + protected final TaskOverlayFactory mTaskOverlayFactory; protected boolean mDisallowScrollToClearAll; + // True if it is not allowed to scroll to [AddDesktopButton]. + protected boolean mDisallowScrollToAddDesk; private boolean mOverlayEnabled; protected boolean mFreezeViewVisibility; private boolean mOverviewGridEnabled; @@ -544,21 +600,22 @@ public Float get(RecentsView view) { private int mClampedScrollOffsetBound; private float mAdjacentPageHorizontalOffset = 0; + private float mDesktopCarouselDetachProgress = 0; protected float mTaskViewsSecondaryTranslation = 0; protected float mTaskViewsPrimarySplitTranslation = 0; protected float mTaskViewsSecondarySplitTranslation = 0; // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid. private float mGridProgress = 0; private float mTaskThumbnailSplashAlpha = 0; + private boolean mBorderEnabled = false; private boolean mShowAsGridLastOnLayout = false; - private final IntSet mTopRowIdSet = new IntSet(); + protected final IntSet mTopRowIdSet = new IntSet(); private int mClearAllShortTotalWidthTranslation = 0; // The GestureEndTarget that is still in progress. @Nullable protected GestureState.GestureEndTarget mCurrentGestureEndTarget; - // TODO(b/187528071): Remove these and replace with a real scrim. private float mColorTint; private final int mTintingColor; @Nullable @@ -570,6 +627,9 @@ public Float get(RecentsView view) { private int mKeyboardTaskFocusSnapAnimationDuration; private int mKeyboardTaskFocusIndex = INVALID_PAGE; + private Map mTaskViewsDismissPrimaryTranslations = + new HashMap(); + /** * TODO: Call reloadIdNeeded in onTaskStackChanged. */ @@ -604,25 +664,28 @@ public void onActivityUnpinned() { @Override public void onTaskRemoved(int taskId) { if (!mHandleTaskStackChanges) { + Log.d(TAG, "onTaskRemoved: " + taskId + ", not handling task stack changes"); return; } - TaskView taskView = getTaskViewByTaskId(taskId); - if (taskView == null) { + TaskContainer taskContainer = mUtils.getTaskContainerById(taskId); + if (taskContainer == null) { + Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task"); return; } - Task.TaskKey taskKey = taskView.getFirstTask().key; + Log.d(TAG, "onTaskRemoved: " + taskId); + TaskKey taskKey = taskContainer.getTask().key; UI_HELPER_EXECUTOR.execute(new CancellableTask<>( () -> PackageManagerWrapper.getInstance() .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null, MAIN_EXECUTOR, apkRemoved -> { if (apkRemoved) { - dismissTask(taskId); + dismissTask(taskId, /*animate=*/true, /*removeTask=*/false); } else { mModel.isTaskRemoved(taskKey.id, taskRemoved -> { if (taskRemoved) { - dismissTask(taskId); + dismissTask(taskId, /*animate=*/true, /*removeTask=*/false); } }, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter())); } @@ -647,12 +710,11 @@ public void onTaskRemoved(int taskId) { protected int mRunningTaskViewId = -1; private int mTaskViewIdCount; protected boolean mRunningTaskTileHidden; - @Nullable - private Task[] mTmpRunningTasks; - protected int mFocusedTaskViewId = -1; + protected int mFocusedTaskViewId = INVALID_TASK_ID; - private boolean mTaskIconScaledDown = false; + private boolean mTaskIconVisible = true; private boolean mRunningTaskShowScreenshot = false; + private float mRunningTaskAttachAlpha; private boolean mOverviewStateEnabled; private boolean mHandleTaskStackChanges; @@ -721,10 +783,12 @@ public void onTaskRemoved(int taskId) { private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() { @Override - public void onSplitSelectionConfirmed() { } + public void onSplitSelectionConfirmed() { + } @Override - public void onSplitSelectionActive() { } + public void onSplitSelectionActive() { + } @Override public void onSplitSelectionExit(boolean launchedSplit) { @@ -762,12 +826,6 @@ public void onSplitSelectionExit(boolean launchedSplit) { @Nullable private DesktopRecentsTransitionController mDesktopRecentsTransitionController; - /** - * Keeps track of the desktop task. Optional and only present when the feature flag is enabled. - */ - @Nullable - private DesktopTaskView mDesktopTaskView; - private MultiWindowModeChangedListener mMultiWindowModeChangedListener = new MultiWindowModeChangedListener() { @Override @@ -775,7 +833,7 @@ public void onMultiWindowModeChanged(boolean inMultiWindowMode) { mOrientationState.setMultiWindowMode(inMultiWindowMode); setLayoutRotation(mOrientationState.getTouchRotation(), mOrientationState.getDisplayRotation()); - updateChildTaskOrientations(); + mUtils.updateChildTaskOrientations(); if (!inMultiWindowMode && mOverviewStateEnabled) { // TODO: Re-enable layout transitions for addition of the unpinned task reloadIfNeeded(); @@ -796,40 +854,93 @@ public void onMultiWindowModeChanged(boolean inMultiWindowMode) { private int mOffsetMidpointIndexOverride = INVALID_PAGE; - public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, - BaseContainerInterface sizeStrategy) { + /** + * Whether or not any task has been dismissed i.e. swiped away by the user, in the lifetime of + * RecentsView being open and displayed to the user. It is reset in the {@link #reset()} method + * i.e. when RecentsView closes. + */ + private boolean mAnyTaskHasBeenDismissed; + + protected final RecentsViewModel mRecentsViewModel; + private final RecentsViewModelHelper mHelper; + protected final RecentsViewUtils mUtils = new RecentsViewUtils(this); + protected final RecentsDismissUtils mDismissUtils = new RecentsDismissUtils(this); + + private final Matrix mTmpMatrix = new Matrix(); + + private int mTaskViewCount = 0; + + protected final BlurUtils mBlurUtils = new BlurUtils(this); + + @Nullable + public TaskView getFirstTaskView() { + return mUtils.getFirstTaskView(); + } + + public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setEnableFreeScroll(true); - mSizeStrategy = sizeStrategy; + mContainer = RecentsViewContainer.containerFromContext(context); + mSizeStrategy = getContainerInterface(mContainer.getDisplayId()); + mOrientationState = new RecentsOrientedState( context, mSizeStrategy, this::animateRecentsRotationInPlace); final int rotation; rotation = Utilities.ATLEAST_R ? mContainer.getDisplay().getRotation() : WindowConfiguration.ROTATION_UNDEFINED; mOrientationState.setRecentsRotation(rotation); + // Start Recents Dependency graph + if (enableRefactorTaskThumbnail()) { + RecentsDependencies recentsDependencies = RecentsDependencies.Companion.maybeInitialize( + context); + String scopeId = recentsDependencies.createRecentsViewScope(context); + mRecentsViewModel = new RecentsViewModel( + recentsDependencies.inject(RecentTasksRepository.class, scopeId), + recentsDependencies.inject(RecentsViewData.class, scopeId) + ); + mHelper = new RecentsViewModelHelper( + mRecentsViewModel, + recentsDependencies.inject(CoroutineScope.class, scopeId), + recentsDependencies.inject(DispatcherProvider.class, scopeId) + ); + + recentsDependencies.provide(RecentsRotationStateRepository.class, scopeId, + () -> new RecentsRotationStateRepositoryImpl(mOrientationState)); + + recentsDependencies.provide(RecentsDeviceProfileRepository.class, scopeId, + () -> new RecentsDeviceProfileRepositoryImpl(mContainer)); + } else { + mRecentsViewModel = null; + mHelper = null; + } + mScrollHapticMinGapMillis = getResources() .getInteger(R.integer.recentsScrollHapticMinGapMillis); mFastFlingVelocity = getResources() .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); mClearAllButton.setOnClickListener(this::dismissAllTasks); + + if (DesktopModeStatus.enableMultipleDesktops(context)) { + mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate( + R.layout.overview_add_desktop_button, this, false); + mAddDesktopButton.setOnClickListener(this::createDesk); + + mDesktopVisibilityController = DesktopVisibilityController.INSTANCE.get(context); + } + mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 10 /* initial size */); + int groupedViewPoolInitialSize = enableRefactorTaskThumbnail() ? 2 : 10; mGroupedTaskViewPool = new ViewPool<>(context, this, - R.layout.task_grouped, 20 /* max size */, 10 /* initial size */); + R.layout.task_grouped, 20 /* max size */, groupedViewPoolInitialSize); + int desktopViewPoolInitialSize = DesktopModeStatus.canEnterDesktopMode(context) ? 1 : 0; mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop, - 5 /* max size */, 1 /* initial size */); + 5 /* max size */, desktopViewPoolInitialSize); setOrientationHandler(mOrientationState.getOrientationHandler()); mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources()); @@ -849,8 +960,11 @@ public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAt mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary)); mEmptyMessagePaint.setTextSize(getResources() .getDimension(R.dimen.recents_empty_message_text_size)); - mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context), - Typeface.NORMAL)); + Typeface typeface = Typeface.create( + Typeface.create(Themes.getDefaultBodyFont(context), Typeface.NORMAL), + getFontWeight(), + false); + mEmptyMessagePaint.setTypeface(typeface); mEmptyMessagePaint.setAntiAlias(true); mEmptyMessagePadding = getResources() .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); @@ -1019,15 +1133,20 @@ public int getOverScrollShift() { @Override @Nullable public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { + if (enableRefactorTaskThumbnail()) { + return null; + } if (mHandleTaskStackChanges) { - TaskView taskView = getTaskViewByTaskId(taskId); - if (taskView != null) { - for (TaskContainer container : taskView.getTaskContainers()) { - if (container == null || taskId != container.getTask().key.id) { - continue; + if (!enableRefactorTaskThumbnail()) { + TaskView taskView = getTaskViewByTaskId(taskId); + if (taskView != null) { + for (TaskContainer container : taskView.getTaskContainers()) { + if (taskId != container.getTask().key.id) { + continue; + } + container.getThumbnailViewDeprecated().setThumbnail(container.getTask(), + thumbnailData); } - container.getThumbnailViewDeprecated().setThumbnail(container.getTask(), - thumbnailData); } } } @@ -1035,15 +1154,15 @@ public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { } @Override - public void onTaskIconChanged(String pkg, UserHandle user) { - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView tv = requireTaskViewAt(i); - Task task = tv.getFirstTask(); - if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) { - task.icon = null; - if (tv.getTaskContainers().stream().anyMatch( + public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) { + for (TaskView taskView : getTaskViews()) { + Task firstTask = taskView.getFirstTask(); + if (firstTask != null && pkg.equals(firstTask.key.getPackageName()) + && firstTask.key.userId == user.getIdentifier()) { + firstTask.icon = null; + if (taskView.getTaskContainers().stream().anyMatch( container -> container.getIconView().getDrawable() != null)) { - tv.onTaskListVisibilityChanged(true /* visible */); + taskView.onTaskListVisibilityChanged(true /* visible */); } } } @@ -1051,42 +1170,36 @@ public void onTaskIconChanged(String pkg, UserHandle user) { @Override public void onTaskIconChanged(int taskId) { + if (enableRefactorTaskThumbnail()) { + return; + } TaskView taskView = getTaskViewByTaskId(taskId); if (taskView != null) { taskView.refreshTaskThumbnailSplash(); } } - /** - * Update the thumbnail(s) of the relevant TaskView. - * @param refreshNow Refresh immediately if it's true. - */ - @Nullable - public TaskView updateThumbnail( - HashMap thumbnailData, boolean refreshNow) { - TaskView updatedTaskView = null; - for (Map.Entry entry : thumbnailData.entrySet()) { - Integer id = entry.getKey(); - ThumbnailData thumbnail = entry.getValue(); - TaskView taskView = getTaskViewByTaskId(id); - if (taskView == null) { - continue; - } - // taskView could be a GroupedTaskView, so select the relevant task by ID - TaskContainer taskAttributes = taskView.getTaskContainerById(id); - if (taskAttributes == null) { - continue; + /** Updates the thumbnail(s) of the relevant TaskView. */ + public void updateThumbnail(Map thumbnailData) { + if (!enableRefactorTaskThumbnail()) { + for (Map.Entry entry : thumbnailData.entrySet()) { + Integer id = entry.getKey(); + ThumbnailData thumbnail = entry.getValue(); + TaskView taskView = getTaskViewByTaskId(id); + if (taskView == null) { + continue; + } + // taskView could be a GroupedTaskView, so select the relevant task by ID + TaskContainer taskContainer = taskView.getTaskContainerById(id); + if (taskContainer == null) { + continue; + } + Task task = taskContainer.getTask(); + TaskThumbnailViewDeprecated taskThumbnailViewDeprecated = + taskContainer.getThumbnailViewDeprecated(); + taskThumbnailViewDeprecated.setThumbnail(task, thumbnail, /*refreshNow=*/false); } - Task task = taskAttributes.getTask(); - 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; } - - return updatedTaskView; } @Override @@ -1098,7 +1211,7 @@ protected void onWindowVisibilityChanged(int visibility) { public void init(OverviewActionsView actionsView, SplitSelectStateController splitController, @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) { mActionsView = actionsView; - mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); + mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews()); // Update flags for 1p/3p launchers mActionsView.updateFor3pLauncher(!supportsAppPairs()); mSplitSelectStateController = splitController; @@ -1116,9 +1229,10 @@ public boolean isSplitSelectionActive() { /** * See overridden implementations + * * @return {@code true} if child TaskViews can be launched when user taps on them */ - protected boolean canLaunchFullscreenTask() { + public boolean canLaunchFullscreenTask() { return true; } @@ -1132,20 +1246,22 @@ protected void onAttachedToWindow() { mSyncTransactionApplier = new SurfaceTransactionApplier(this); runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams() .setSyncTransactionApplier(mSyncTransactionApplier)); - RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); + RecentsModel.INSTANCE.get(mContext).addThumbnailChangeListener(this); mIPipAnimationListener.setActivityAndRecentsView(mContainer, this); - SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener( + SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener( mIPipAnimationListener); mOrientationState.initListeners(); mTaskOverlayFactory.initListeners(); - if (FeatureFlags.enableSplitContextually()) { - mSplitSelectStateController.registerSplitListener(mSplitSelectionListener); + mSplitSelectStateController.registerSplitListener(mSplitSelectionListener); + if (mDesktopVisibilityController != null) { + mDesktopVisibilityController.registerDesktopVisibilityListener(this); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + updateTaskStackListenerState(); mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this); mContainer.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); @@ -1154,51 +1270,81 @@ protected void onDetachedFromWindow() { runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams() .setSyncTransactionApplier(null)); executeSideTaskLaunchCallback(); - RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); - SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(null); + RecentsModel.INSTANCE.get(mContext).removeThumbnailChangeListener(this); + SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(null); mIPipAnimationListener.setActivityAndRecentsView(null, null); mOrientationState.destroyListeners(); mTaskOverlayFactory.removeListeners(); - if (FeatureFlags.enableSplitContextually()) { - mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener); + mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener); + if (mDesktopVisibilityController != null) { + mDesktopVisibilityController.unregisterDesktopVisibilityListener(this); } reset(); } + /** + * Execute clean-up logic needed when the view is destroyed. + */ + public void destroy() { + Log.d(TAG, "destroy"); + if (enableRefactorTaskThumbnail()) { + try { + mTaskViewPool.killOngoingInitializations(); + mGroupedTaskViewPool.killOngoingInitializations(); + mDesktopTaskViewPool.killOngoingInitializations(); + } catch (InterruptedException e) { + Log.e(TAG, "Ongoing initializations could not be killed", e); + } + mHelper.onDestroy(); + RecentsDependencies.destroy(getContext()); + } + } + @Override public void onViewRemoved(View child) { + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewRemoved"); super.onViewRemoved(child); - // Clear the task data for the removed child if it was visible unless: // - It's the initial taskview for entering split screen, we only pretend to dismiss the // task // - It's the focused task to be moved to the front, we immediately re-add the task - if (child instanceof TaskView && child != mSplitHiddenTaskView - && child != mMovingTaskView) { - TaskView taskView = (TaskView) child; - for (int i : taskView.getTaskIds()) { - mHasVisibleTaskData.delete(i); - } - if (child instanceof GroupedTaskView) { - mGroupedTaskViewPool.recycle((GroupedTaskView) taskView); - } else if (child instanceof DesktopTaskView) { - mDesktopTaskViewPool.recycle((DesktopTaskView) taskView); - } else { - mTaskViewPool.recycle(taskView); + if (child instanceof TaskView) { + mTaskViewCount = Math.max(0, --mTaskViewCount); + if (child != mSplitHiddenTaskView && child != mMovingTaskView) { + clearAndRecycleTaskView((TaskView) child); } - mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); } + traceEnd(Trace.TRACE_TAG_APP); + } + + private void clearAndRecycleTaskView(TaskView taskView) { + for (int i : taskView.getTaskIds()) { + mHasVisibleTaskData.delete(i); + } + if (taskView instanceof GroupedTaskView) { + mGroupedTaskViewPool.recycle((GroupedTaskView) taskView); + } else if (taskView instanceof DesktopTaskView) { + mDesktopTaskViewPool.recycle((DesktopTaskView) taskView); + } else { + mTaskViewPool.recycle(taskView); + } + mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews()); } @Override public void onViewAdded(View child) { + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewAdded"); super.onViewAdded(child); + if (child instanceof TaskView) { + mTaskViewCount++; + } child.setAlpha(mContentAlpha); // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the // child direction back to match system settings. child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL); mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false); updateEmptyMessage(); + traceEnd(Trace.TRACE_TAG_APP); } @Override @@ -1277,12 +1423,13 @@ public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) { RemoteAnimationTargets targets = params.getTargetSet(); if (targets != null && targets.findTask(taskId) != null) { launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers, - targets.nonApps); + targets.nonApps, /* transitionInfo= */ null); } } public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps) { + RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps, + @Nullable TransitionInfo transitionInfo) { AnimatorSet anim = new AnimatorSet(); TaskView taskView = getTaskViewByTaskId(taskId); if (taskView == null || !isTaskViewVisible(taskView)) { @@ -1328,22 +1475,35 @@ public void onAnimationStart(Animator animation) { anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - finishRecentsAnimation(false /* toRecents */, null); + finishRecentsAnimation(false /* toRecents */, true /*shouldPip*/, + allAppsAreTranslucent(apps), null); } }); } else { TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps, true /* launcherClosing */, getStateManager(), this, - getDepthController()); + getDepthController(), transitionInfo); } anim.start(); } + private boolean allAppsAreTranslucent(RemoteAnimationTarget[] apps) { + if (apps == null) { + return false; + } + for (int i = apps.length - 1; i >= 0; --i) { + if (!apps[i].isTranslucent) { + return false; + } + } + return true; + } + public boolean isTaskViewVisible(TaskView tv) { if (showAsGrid()) { int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this); - return isTaskViewWithinBounds(tv, screenStart, screenEnd); + return isTaskViewWithinBounds(tv, screenStart, screenEnd, /*taskViewTranslation=*/ 0); } else { // For now, just check if it's the active task or an adjacent task return Math.abs(indexOfChild(tv) - getNextPage()) <= 1; @@ -1363,7 +1523,7 @@ public boolean isTaskViewFullyVisible(TaskView tv) { @Nullable private TaskView getLastGridTaskView() { - return getLastGridTaskView(getTopRowIdArray(), getBottomRowIdArray()); + return getLastGridTaskView(mUtils.getTopRowIdArray(), mUtils.getBottomRowIdArray()); } @Nullable @@ -1390,14 +1550,41 @@ private int getLastTaskScroll(int clearAllScroll, int clearAllWidth) { return clearAllScroll + (mIsRtl ? distance : -distance); } - private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) { - int taskStart = getPagedOrientationHandler().getChildStart(tv) - + (int) tv.getOffsetAdjustment(showAsGrid()); - int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(tv) - * tv.getSizeAdjustment(showAsFullscreen())); + /** + * Launch running task view if it is instance of DesktopTaskView. + * @return provides runnable list to attach runnable at end of Desktop Mode launch + */ + @Nullable + public RunnableList launchRunningDesktopTaskView() { + TaskView taskView = getRunningTaskView(); + if (taskView instanceof DesktopTaskView) { + return taskView.launchWithAnimation(); + } + return null; + } + + /* + * Returns if TaskView is within screen bounds defined in [screenStart, screenEnd]. + * + * @param taskViewTranslation taskView is considered within bounds if either translated or + * original position of taskView is within screen bounds. + */ + protected boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd, + int taskViewTranslation) { + int taskStart = getPagedOrientationHandler().getChildStart(taskView) + + (int) taskView.getOffsetAdjustment(showAsGrid()); + int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(taskView) + * taskView.getSizeAdjustment(showAsFullscreen())); int taskEnd = taskStart + taskSize; - return (taskStart >= start && taskStart <= end) || (taskEnd >= start - && taskEnd <= end); + + int translatedTaskStart = taskStart + taskViewTranslation; + int translatedTaskEnd = taskEnd + taskViewTranslation; + + taskStart = Math.min(taskStart, translatedTaskStart); + taskEnd = Math.max(taskEnd, translatedTaskEnd); + + return (taskStart >= screenStart && taskStart <= screenEnd) || (taskEnd >= screenStart + && taskEnd <= screenEnd); } private boolean isTaskViewFullyWithinBounds(TaskView tv, int start, int end) { @@ -1410,17 +1597,19 @@ private boolean isTaskViewFullyWithinBounds(TaskView tv, int start, int end) { } /** - * Returns true if the task is in expected scroll position. - * - * @param taskIndex the index of the task + * Returns true if the given TaskView is in expected scroll position. */ - public boolean isTaskInExpectedScrollPosition(int taskIndex) { - return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this); + public boolean isTaskInExpectedScrollPosition(@NonNull TaskView taskView) { + return getScrollForPage(indexOfChild(taskView)) + == getPagedOrientationHandler().getPrimaryScroll(this); } - private boolean isFocusedTaskInExpectedScrollPosition() { + /** + * Returns true if the focused TaskView is in expected scroll position. + */ + public boolean isFocusedTaskInExpectedScrollPosition() { TaskView focusedTask = getFocusedTaskView(); - return focusedTask != null && isTaskInExpectedScrollPosition(indexOfChild(focusedTask)); + return focusedTask != null && isTaskInExpectedScrollPosition(focusedTask); } /** @@ -1432,8 +1621,7 @@ public TaskView getTaskViewByTaskId(int taskId) { return null; } - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView taskView = requireTaskViewAt(i); + for (TaskView taskView : getTaskViews()) { if (taskView.containsTaskId(taskId)) { return taskView; } @@ -1454,8 +1642,7 @@ public TaskView getTaskViewByTaskIds(int[] taskIds) { int[] taskIdsCopy = Arrays.copyOf(taskIds, taskIds.length); Arrays.sort(taskIdsCopy); - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView taskView = requireTaskViewAt(i); + for (TaskView taskView : getTaskViews()) { int[] taskViewIdsCopy = taskView.getTaskIds(); Arrays.sort(taskViewIdsCopy); if (Arrays.equals(taskIdsCopy, taskViewIdsCopy)) { @@ -1477,9 +1664,6 @@ public void setOverviewStateEnabled(boolean enabled) { updateTaskStackListenerState(); mOrientationState.setRotationWatcherEnabled(enabled); if (!enabled) { - // Reset the running task when leaving overview since it can still have a reference to - // its thumbnail - mTmpRunningTasks = null; mSplitBoundsConfig = null; mTaskOverlayFactory.clearAllActiveState(); } @@ -1490,12 +1674,14 @@ public void setOverviewStateEnabled(boolean enabled) { * Enable or disable showing border on hover and focus change on task views */ public void setTaskBorderEnabled(boolean enabled) { - int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); + mBorderEnabled = enabled; + for (TaskView taskView : getTaskViews()) { taskView.setBorderEnabled(enabled); } mClearAllButton.setBorderEnabled(enabled); + if (mAddDesktopButton != null) { + mAddDesktopButton.setBorderEnabled(enabled); + } } /** @@ -1523,8 +1709,7 @@ protected void onPageBeginTransition() { @Override protected void onPageEndTransition() { super.onPageEndTransition(); - ActiveGestureLog.INSTANCE.addLog( - "onPageEndTransition: current page index updated", getNextPage()); + ActiveGestureProtoLogProxy.logOnPageEndTransition(getNextPage()); if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) { mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); } @@ -1559,9 +1744,7 @@ public boolean onTouchEvent(MotionEvent ev) { super.onTouchEvent(ev); if (showAsGrid()) { - int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); + for (TaskView taskView : getTaskViews()) { if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) { // Keep consuming events to pass to delegate return true; @@ -1607,8 +1790,11 @@ public boolean onTouchEvent(MotionEvent ev) { mClearAllButton.getAlpha() == 1 && mClearAllButtonDeadZoneRect.contains(x, y); final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; + int adjustedX = x + getScrollX(); if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar - && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) { + && !mTaskViewDeadZoneRect.contains(adjustedX, y) + && !mTopRowDeadZoneRect.contains(adjustedX, y) + && !mBottomRowDeadZoneRect.contains(adjustedX, y)) { mTouchDownToStartHome = true; } } @@ -1643,11 +1829,11 @@ protected void onNotSnappingToPageInFreeScroll() { return; } TaskView taskView = getTaskViewAt(mNextPage); - // Snap to fully visible focused task and clear all button. - boolean shouldSnapToFocusedTask = taskView != null && taskView.isFocusedTask() - && isTaskViewFullyVisible(taskView); + boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile() + && !mUtils.isAnySmallTaskFullyVisible(); boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton); - if (!shouldSnapToFocusedTask && !shouldSnapToClearAll) { + // Snap to large tile when grid tasks aren't fully visible or the clear all button. + if (!shouldSnapToLargeTask && !shouldSnapToClearAll) { return; } } @@ -1690,24 +1876,17 @@ protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { } /** - * Moves the running task to the front of the carousel in tablets, to minimize animation - * required to move the running task in grid. + * Moves the running task to the expected position in the carousel. In tablets, this minimize + * animation required to move the running task into focused task position. */ - public void moveRunningTaskToFront() { - if (!mContainer.getDeviceProfile().isTablet) { - return; - } - + public void moveRunningTaskToExpectedPosition() { TaskView runningTaskView = getRunningTaskView(); - if (runningTaskView == null) { - return; - } - - if (indexOfChild(runningTaskView) != mCurrentPage) { + if (runningTaskView == null || mCurrentPage != indexOfChild(runningTaskView)) { return; } - if (mCurrentPage == 0) { + int runningTaskExpectedIndex = mUtils.getRunningTaskExpectedIndex(runningTaskView); + if (mCurrentPage == runningTaskExpectedIndex) { return; } @@ -1719,16 +1898,16 @@ public void moveRunningTaskToFront() { removeView(runningTaskView); mMovingTaskView = null; runningTaskView.resetPersistentViewTransforms(); - addView(runningTaskView, 0); - setCurrentPage(0); + + addView(runningTaskView, runningTaskExpectedIndex); + setCurrentPage(runningTaskExpectedIndex); updateTaskSize(); } @Override protected void onScrollerAnimationAborted() { - ActiveGestureLog.INSTANCE.addLog("scroller animation aborted", - ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED); + ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted(); } @Override @@ -1738,7 +1917,8 @@ protected boolean isPageScrollsInitialized() { protected void applyLoadPlan(List taskGroups) { if (mPendingAnimation != null) { - mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups)); + final List finalTaskGroups = taskGroups; + mPendingAnimation.addEndListener(success -> applyLoadPlan(finalTaskGroups)); return; } @@ -1746,11 +1926,11 @@ protected void applyLoadPlan(List taskGroups) { Log.d(TAG, "applyLoadPlan - taskGroups is null"); } else { Log.d(TAG, "applyLoadPlan - taskGroups: " + taskGroups.stream().map( - GroupTask::toString).collect(Collectors.toList())); + GroupTask::toString).toList()); } mLoadPlanEverApplied = true; if (taskGroups == null || taskGroups.isEmpty()) { - removeTasksViewsAndClearAllButton(); + removeAllTaskViews(); onTaskStackUpdated(); // With all tasks removed, touch handling in PagedView is disabled and we need to reset // touch state or otherwise values will be obsolete. @@ -1761,12 +1941,19 @@ protected void applyLoadPlan(List taskGroups) { return; } - int[] currentTaskIds; + // Start here to avoid early returns and empty cases which have special logic + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan"); + TaskView currentTaskView = getTaskViewAt(mCurrentPage); - if (currentTaskView != null) { + int[] currentTaskIds = null; + // Track the current DesktopTaskView through [deskId] as a desk can be empty without any + // tasks. + int currentTaskViewDeskId = INACTIVE_DESK_ID; + if (areMultiDesksFlagsEnabled() + && currentTaskView instanceof DesktopTaskView desktopTaskView) { + currentTaskViewDeskId = desktopTaskView.getDeskId(); + } else if (currentTaskView != null) { currentTaskIds = currentTaskView.getTaskIds(); - } else { - currentTaskIds = new int[0]; } // Unload existing visible task data @@ -1778,19 +1965,33 @@ protected void applyLoadPlan(List taskGroups) { // 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[] runningTaskIds = getTaskIdsForTaskViewId(mRunningTaskViewId); - int[] focusedTaskIds = getTaskIdsForTaskViewId(mFocusedTaskViewId); + TaskView runningTaskView = getRunningTaskView(); + int[] runningTaskIds = null; + + // Track the running TaskView through [deskId] as a desk can be empty without any tasks. + int runningTaskViewDeskId = INACTIVE_DESK_ID; + if (areMultiDesksFlagsEnabled() + && runningTaskView instanceof DesktopTaskView desktopTaskView) { + runningTaskViewDeskId = desktopTaskView.getDeskId(); + } else if (runningTaskView != null) { + runningTaskIds = runningTaskView.getTaskIds(); + } + 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. - mFocusedTaskViewId = INVALID_TASK_ID; + setFocusedTaskViewId(INVALID_TASK_ID); // Removing views sets the currentPage to 0, so we save this and restore it after // the new set of views are added int previousCurrentPage = mCurrentPage; int previousFocusedPage = indexOfChild(getFocusedChild()); + // TaskIds will no longer be valid after remove and re-add, clearing mTopRowIdSet. + mAnyTaskHasBeenDismissed = false; + mTopRowIdSet.clear(); + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.removeAllViews"); removeAllViews(); - + traceEnd(Trace.TRACE_TAG_APP); // 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 stagedTaskIdToBeRemoved; @@ -1804,81 +2005,115 @@ protected void applyLoadPlan(List taskGroups) { mFilterState.updateInstanceCountMap(taskGroups); // Clear out desktop view if it is set - mDesktopTaskView = null; + + // Move Desktop Tasks to the end of the list + if (enableLargeDesktopWindowingTile()) { + taskGroups = mUtils.sortDesktopTasksToFront(taskGroups); + } + if (enableSeparateExternalDisplayTasks()) { + taskGroups = mUtils.sortExternalDisplayTasksToFront(taskGroups); + } + + if (mAddDesktopButton != null) { + // Add `mAddDesktopButton` as the first child. + addView(mAddDesktopButton); + } + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop"); // 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 = stagedTaskIdToBeRemoved != INVALID_TASK_ID + boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID && groupTask.containsTask(stagedTaskIdToBeRemoved); + boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask; - if (isRemovalNeeded && !groupTask.hasMultipleTasks()) { - // If the task we need to remove is not part of a pair, avoiding creating the - // TaskView. + if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP) + || shouldSkipGroupTask) { + // To avoid these tasks from being chosen as the app pair, the creation of a + // TaskView is bypassed. The staged task is already selected for the app pair, + // and the Desktop task should be hidden when selecting a pair. continue; } // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE // to be a temporary container for the remaining task. + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.createTaskView"); 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, - mTaskOverlayFactory, groupTask.mSplitBounds); - } else if (taskView instanceof DesktopTaskView) { - ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks, - mOrientationState, mTaskOverlayFactory); - mDesktopTaskView = (DesktopTaskView) taskView; - } else { - Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2 - : groupTask.task1; + containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType); + traceEnd(Trace.TRACE_TAG_APP); + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.bind"); + if (taskView instanceof GroupedTaskView groupedTaskView) { + var splitTask = (SplitTask) groupTask; + groupedTaskView.bind(splitTask.getTopLeftTask(), + splitTask.getBottomRightTask(), mOrientationState, + mTaskOverlayFactory, splitTask.getSplitBounds()); + } else if (taskView instanceof DesktopTaskView desktopTaskView) { + desktopTaskView.bind((DesktopTask) groupTask, mOrientationState, + mTaskOverlayFactory); + } else if (groupTask instanceof SplitTask splitTask) { + Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved + ? splitTask.getBottomRightTask() + : splitTask.getTopLeftTask(); taskView.bind(task, mOrientationState, mTaskOverlayFactory); + } else { + taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState, + mTaskOverlayFactory); } + traceEnd(Trace.TRACE_TAG_APP); + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.addTaskView"); addView(taskView); + traceEnd(Trace.TRACE_TAG_APP); // enables instance filtering if the feature flag for it is on if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) { taskView.setUpShowAllInstancesListener(); } } + // For loop end trace + traceEnd(Trace.TRACE_TAG_APP); - if (!taskGroups.isEmpty()) { - addView(mClearAllButton); - } + addView(mClearAllButton); // Keep same previous focused task - TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds); - // If the list changed, maybe the focused task doesn't exist anymore - if (newFocusedTaskView == null && getTaskViewCount() > 0) { - newFocusedTaskView = getTaskViewAt(0); + TaskView newFocusedTaskView = null; + if (!enableGridOnlyOverview()) { + newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds); + if (enableLargeDesktopWindowingTile() + && newFocusedTaskView instanceof DesktopTaskView) { + newFocusedTaskView = null; + } + // If the list changed, maybe the focused task doesn't exist anymore. + if (newFocusedTaskView == null) { + newFocusedTaskView = mUtils.getFirstNonDesktopTaskView(); + } } - mFocusedTaskViewId = newFocusedTaskView != null && !enableGridOnlyOverview() - ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID; + setFocusedTaskViewId( + newFocusedTaskView != null ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID); + + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.layouts"); updateTaskSize(); - updateChildTaskOrientations(); + mUtils.updateChildTaskOrientations(); + traceEnd(Trace.TRACE_TAG_APP); - TaskView newRunningTaskView = null; - if (hasAllValidTaskIds(runningTaskIds)) { + TaskView newRunningTaskView = mUtils.getDesktopTaskViewForDeskId(runningTaskViewDeskId); + if (newRunningTaskView == null) { // Update mRunningTaskViewId to be the new TaskView that was assigned by binding // the full list of tasks to taskViews newRunningTaskView = getTaskViewByTaskIds(runningTaskIds); - if (newRunningTaskView != null) { - setRunningTaskViewId(newRunningTaskView.getTaskViewId()); + } + if (newRunningTaskView != null) { + setRunningTaskViewId(newRunningTaskView.getTaskViewId()); + } else { + if (mActiveGestureGroupedTaskInfo != null) { + // This will update mRunningTaskViewId and create a stub view if necessary. + // We try to avoid this because it can cause a scroll jump, but it is needed + // for cases where the running task isn't included in this load plan (e.g. if + // the current running task is excludedFromRecents.) + showCurrentTask(mActiveGestureGroupedTaskInfo, "applyLoadPlan"); + newRunningTaskView = getRunningTaskView(); } else { - if (mActiveGestureRunningTasks != null) { - // This will update mRunningTaskViewId and create a stub view if necessary. - // We try to avoid this because it can cause a scroll jump, but it is needed - // for cases where the running task isn't included in this load plan (e.g. if - // the current running task is excludedFromRecents.) - showCurrentTask(mActiveGestureRunningTasks); - } else { - setRunningTaskViewId(INVALID_TASK_ID); - } + setRunningTaskViewId(INVALID_TASK_ID); } } @@ -1886,21 +2121,18 @@ protected void applyLoadPlan(List taskGroups) { if (mNextPage != INVALID_PAGE) { // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll. mCurrentPage = previousCurrentPage; - if (hasAllValidTaskIds(currentTaskIds)) { + currentTaskView = mUtils.getDesktopTaskViewForDeskId(currentTaskViewDeskId); + if (currentTaskView == null) { currentTaskView = getTaskViewByTaskIds(currentTaskIds); - if (currentTaskView != null) { - targetPage = indexOfChild(currentTaskView); - } + } + if (currentTaskView != null) { + targetPage = indexOfChild(currentTaskView); } } else if (previousFocusedPage != INVALID_PAGE) { targetPage = previousFocusedPage; } else { - // Set the current page to the running task, but not if settling on new task. - if (hasAllValidTaskIds(runningTaskIds)) { - targetPage = indexOfChild(newRunningTaskView); - } else if (getTaskViewCount() > 0) { - targetPage = indexOfChild(requireTaskViewAt(0)); - } + targetPage = indexOfChild( + mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView)); } if (targetPage != -1 && mCurrentPage != targetPage) { int finalTargetPage = targetPage; @@ -1917,6 +2149,7 @@ protected void applyLoadPlan(List taskGroups) { }); } + traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.cleanupStates"); if (mIgnoreResetTaskId != INVALID_TASK_ID && getTaskViewByTaskId(mIgnoreResetTaskId) != ignoreResetTaskView) { // If the taskView mapping is changing, do not preserve the visuals. Since we are @@ -1924,12 +2157,17 @@ protected void applyLoadPlan(List taskGroups) { // generally map to the same task. mIgnoreResetTaskId = INVALID_TASK_ID; } + resetTaskVisuals(); onTaskStackUpdated(); updateEnabledOverlays(); if (isPageScrollsInitialized()) { onPageScrollsInitialized(); } + traceEnd(Trace.TRACE_TAG_APP); + + // applyLoadPlan end trace + traceEnd(Trace.TRACE_TAG_APP); } private boolean isModal() { @@ -1940,38 +2178,30 @@ public boolean isLoadingTasks() { return mModel.isLoadingTasksInBackground(); } - private void removeTasksViewsAndClearAllButton() { - // This handles an edge case where applyLoadPlan happens during a gesture when the - // only Task is one with excludeFromRecents, in which case we should not remove it. - final int stubRunningTaskIndex = isGestureActive() ? getRunningTaskIndex() : -1; - - for (int i = getTaskViewCount() - 1; i >= 0; i--) { - if (i == stubRunningTaskIndex) { - continue; - } - removeView(requireTaskViewAt(i)); - } - if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) { + private void removeAllTaskViews() { + // This handles an edge case where applyLoadPlan happens during a gesture when the only + // Task is one with excludeFromRecents, in which case we should not remove it. + CollectionsKt + .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask()) + .forEach(this::removeView); + if (!hasTaskViews()) { + removeView(mAddDesktopButton); removeView(mClearAllButton); } } + /** Returns true if there are at least one TaskView has been added to the RecentsView. */ + public boolean hasTaskViews() { + return mUtils.hasTaskViews(); + } + public int getTaskViewCount() { - int taskViewCount = getChildCount(); - if (indexOfChild(mClearAllButton) != -1) { - taskViewCount--; - } - return taskViewCount; + return mTaskViewCount; } - public int getGroupedTaskViewCount() { - int groupViewCount = 0; - for (int i = 0; i < getChildCount(); i++) { - if (getChildAt(i) instanceof GroupedTaskView) { - groupViewCount++; - } - } - return groupViewCount; + /** Counts {@link TaskView}s that are not {@link DesktopTaskView} instances. */ + public int getNonDesktopTaskViewCount() { + return mUtils.getNonDesktopTaskViewCount(); } /** @@ -1994,16 +2224,16 @@ protected void onTaskStackUpdated() { } public void resetTaskVisuals() { - for (int i = getTaskViewCount() - 1; i >= 0; i--) { - TaskView taskView = requireTaskViewAt(i); + for (TaskView taskView : getTaskViews()) { if (Arrays.stream(taskView.getTaskIds()).noneMatch( taskId -> taskId == mIgnoreResetTaskId)) { taskView.resetViewTransforms(); - taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1); + taskView.setIconVisibleForGesture(mTaskIconVisible); taskView.setStableAlpha(mContentAlpha); taskView.setFullscreenProgress(mFullscreenProgress); taskView.setModalness(mTaskModalness); taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha); + taskView.setBorderEnabled(mBorderEnabled); } } // resetTaskVisuals is called at the end of dismiss animation which could update @@ -2016,14 +2246,10 @@ public void resetTaskVisuals() { simulator.fullScreenProgress.value = 0; simulator.recentsViewScale.value = 1; }); - // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is - // null. - if (!mRunningTaskShowScreenshot) { - setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot); - } - if (mRunningTaskTileHidden) { - setRunningTaskHidden(mRunningTaskTileHidden); - } + // Reapply runningTask related attributes as they might have been reset by + // resetViewTransforms(). + setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot); + applyAttachAlpha(); updateCurveProperties(); // Update the set of visible task's data @@ -2034,12 +2260,8 @@ public void resetTaskVisuals() { 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); + for (TaskView taskView : getTaskViews()) { + taskView.setFullscreenProgress(mFullscreenProgress); } mClearAllButton.setFullscreenProgress(fullscreenProgress); @@ -2052,6 +2274,7 @@ private void updateTaskStackListenerState() { boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow() && getWindowVisibility() == VISIBLE; if (handleTaskStackChanges != mHandleTaskStackChanges) { + Log.d(TAG, "updateTaskStackListenerState: " + handleTaskStackChanges); mHandleTaskStackChanges = handleTaskStackChanges; if (handleTaskStackChanges) { reloadIfNeeded(); @@ -2122,7 +2345,7 @@ private void updateOrientationHandler(boolean forceRecreateDragLayerControllers) updateSizeAndPadding(); // Update TaskView's DeviceProfile dependent layout. - updateChildTaskOrientations(); + mUtils.updateChildTaskOrientations(); requestLayout(); // Reapply the current page to update page scrolls. @@ -2153,11 +2376,6 @@ private void updateSizeAndPadding() { mSizeStrategy.calculateGridTaskSize(mContainer, dp, mLastComputedGridTaskSize, getPagedOrientationHandler()); - if (enableGridOnlyOverview()) { - mSizeStrategy.calculateCarouselTaskSize(mContainer, dp, mLastComputedCarouselTaskSize, - getPagedOrientationHandler()); - } - mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top; mTopBottomRowHeightDiff = mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx @@ -2172,33 +2390,14 @@ private void updateSizeAndPadding() { * Updates TaskView scaling and translation required to support variable width. */ private void updateTaskSize() { - updateTaskSize(false); - } - - /** - * Updates TaskView scaling and translation required to support variable width. - * - * @param isTaskDismissal indicates if update was called due to task dismissal - */ - private void updateTaskSize(boolean isTaskDismissal) { - final int taskCount = getTaskViewCount(); - if (taskCount == 0) { + if (!hasTaskViews()) { return; } float accumulatedTranslationX = 0; - float translateXToMiddle = 0; - 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(mLastComputedTaskSize, mLastComputedGridTaskSize, - mLastComputedCarouselTaskSize); + for (TaskView taskView : getTaskViews()) { + taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize); taskView.setNonGridTranslationX(accumulatedTranslationX); - taskView.setNonGridPivotTranslationX(translateXToMiddle); // Compensate space caused by TaskView scaling. float widthDiff = taskView.getLayoutParams().width * (1 - taskView.getNonGridScale()); @@ -2207,7 +2406,13 @@ private void updateTaskSize(boolean isTaskDismissal) { mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX); - updateGridProperties(isTaskDismissal); + float taskAlignmentTranslationY = getTaskAlignmentTranslationY(); + mClearAllButton.setTaskAlignmentTranslationY(taskAlignmentTranslationY); + if (mAddDesktopButton != null) { + mAddDesktopButton.setTranslationY(taskAlignmentTranslationY); + } + + updateGridProperties(); } public void getTaskSize(Rect outRect) { @@ -2216,28 +2421,50 @@ public void getTaskSize(Rect outRect) { } /** - * Sets the last TaskView selected. + * Returns the currently selected TaskView in Select mode. + */ + @Nullable + public TaskView getSelectedTaskView() { + return mUtils.getSelectedTaskView(); + } + + /** + * Sets the selected TaskView in Select mode. */ public void setSelectedTask(int lastSelectedTaskId) { - mSelectedTask = getTaskViewByTaskId(lastSelectedTaskId); + mUtils.setSelectedTaskView(getTaskViewByTaskId(lastSelectedTaskId)); } /** * Returns the bounds of the task selected to enter modal state. */ public Rect getSelectedTaskBounds() { - if (mSelectedTask == null) { + if (getSelectedTaskView() == null) { return mLastComputedTaskSize; } - return getTaskBounds(mSelectedTask); + return getTaskBounds(getSelectedTaskView()); + } + + /** + * Get the Y translation that should be applied to the non-TaskView item inside the RecentsView + * (ClearAllButton and AddDesktopButton) in the original layout position, before scrolling. This + * is done to make sure the button is aligned to the middle of Task thumbnail in y coordinate. + */ + private float getTaskAlignmentTranslationY() { + DeviceProfile deviceProfile = mContainer.getDeviceProfile(); + if (deviceProfile.isTablet) { + return deviceProfile.overviewRowSpacing; + } + return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f; } - private Rect getTaskBounds(TaskView taskView) { + protected Rect getTaskBounds(TaskView taskView) { int selectedPage = indexOfChild(taskView); int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); int selectedPageScroll = getScrollForPage(selectedPage); - boolean isTopRow = taskView != null && mTopRowIdSet.contains(taskView.getTaskViewId()); - Rect outRect = new Rect(mLastComputedTaskSize); + boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); + Rect outRect = new Rect( + taskView.isGridTask() ? mLastComputedGridTaskSize : mLastComputedTaskSize); outRect.offset( -(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))), (int) (showAsGrid() && enableGridOnlyOverview() && !isTopRow @@ -2254,10 +2481,6 @@ public Rect getLastComputedGridTaskSize() { return mLastComputedGridTaskSize; } - public Rect getLastComputedCarouselTaskSize() { - return mLastComputedCarouselTaskSize; - } - /** Gets the task size for modal state. */ public void getModalTaskSize(Rect outRect) { mSizeStrategy.calculateModalTaskSize(mContainer, mContainer.getDeviceProfile(), outRect, @@ -2341,6 +2564,11 @@ protected int getDestinationPage(int scaledScroll) { int minDistanceFromScreenStart = Integer.MAX_VALUE; int minDistanceFromScreenStartIndex = INVALID_PAGE; for (int i = 0; i < getChildCount(); ++i) { + // Do not set the destination page to the AddDesktopButton, which has the same page + // scrolls as the first [TaskView] and shouldn't be scrolled to. + if (getChildAt(i) instanceof AddDesktopButton) { + continue; + } int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll); if (distanceFromScreenStart < minDistanceFromScreenStart) { minDistanceFromScreenStart = distanceFromScreenStart; @@ -2362,41 +2590,39 @@ public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { return; } - int lower = 0; - int upper = 0; - int visibleStart = 0; - int visibleEnd = 0; + int lowerIndex, upperIndex, visibleStart, visibleEnd; if (showAsGrid()) { int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); int pageOrientedSize = getPagedOrientationHandler().getMeasuredSize(this); // For GRID_ONLY_OVERVIEW, use +/- 1 task column as visible area for preloading // adjacent thumbnails, otherwise use +/-50% screen width - int extraWidth = enableGridOnlyOverview() ? getLastComputedTaskSize().width() - + getPageSpacing() : pageOrientedSize / 2; + int extraWidth = + enableGridOnlyOverview() ? getLastComputedTaskSize().width() + getPageSpacing() + : pageOrientedSize / 2; + lowerIndex = upperIndex = 0; visibleStart = screenStart - extraWidth; visibleEnd = screenStart + pageOrientedSize + extraWidth; } else { int centerPageIndex = getPageNearestToCenterOfScreen(); int numChildren = getChildCount(); - lower = Math.max(0, centerPageIndex - 2); - upper = Math.min(centerPageIndex + 2, numChildren - 1); + lowerIndex = Math.max(0, centerPageIndex - 2); + upperIndex = Math.min(centerPageIndex + 2, numChildren - 1); + visibleStart = visibleEnd = 0; } List visibleTaskIds = new ArrayList<>(); - // Update the task data for the in/visible children - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView taskView = requireTaskViewAt(i); + getTaskViews().forEachWithIndexInParent((index, taskView) -> { List containers = taskView.getTaskContainers(); if (containers.isEmpty()) { - continue; + return; } - int index = indexOfChild(taskView); boolean visible; if (showAsGrid()) { - visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd); + visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd, + mTaskViewsDismissPrimaryTranslations.getOrDefault(taskView, 0)); } else { - visible = lower <= index && index <= upper; + visible = index >= lowerIndex && index <= upperIndex; } if (visible) { // Default update all non-null tasks, then remove running ones @@ -2405,18 +2631,12 @@ public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { .collect(Collectors.toCollection(ArrayList::new)); if (enableRefactorTaskThumbnail()) { visibleTaskIds.addAll( - tasksToUpdate.stream().map((task) -> task.key.id).collect(Collectors.toList())); - } - if (mTmpRunningTasks != null) { - for (Task t : mTmpRunningTasks) { - // Skip loading if this is the task that we are animating into - // TODO(b/280812109) change this equality check to use A.equals(B) - tasksToUpdate.removeIf(task -> task == t); - } + tasksToUpdate.stream().map((task) -> task.key.id).toList()); } if (tasksToUpdate.isEmpty()) { - continue; + return; } + int visibilityChanges = 0; for (Task task : tasksToUpdate) { if (!mHasVisibleTaskData.get(task.key.id)) { // Ignore thumbnail update if it's current running task during the gesture @@ -2425,25 +2645,32 @@ public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { if (taskView == getRunningTaskView() && isGestureActive()) { changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL; } - taskView.onTaskListVisibilityChanged(true /* visible */, changes); + visibilityChanges |= changes; } - mHasVisibleTaskData.put(task.key.id, visible); + mHasVisibleTaskData.put(task.key.id, true); + } + if (visibilityChanges != 0) { + taskView.onTaskListVisibilityChanged(true /* visible */, visibilityChanges); } } else { + int visibilityChanges = 0; for (TaskContainer container : containers) { if (container == null) { continue; } if (mHasVisibleTaskData.get(container.getTask().key.id)) { - taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges); + visibilityChanges = dataChanges; } mHasVisibleTaskData.delete(container.getTask().key.id); } + if (visibilityChanges != 0) { + taskView.onTaskListVisibilityChanged(false /* visible */, visibilityChanges); + } } - } + }); if (enableRefactorTaskThumbnail()) { - mTasksRepository.setVisibleTasks(visibleTaskIds); + mRecentsViewModel.updateVisibleTasks(visibleTaskIds); } } @@ -2470,6 +2697,10 @@ public void onHighResLoadingStateChanged(boolean enabled) { mModel.preloadCacheIfNeeded(); } + if (enableRefactorTaskThumbnail()) { + return; + } + // Whenever the high res loading state changes, poke each of the visible tasks to see if // they want to updated their thumbnail state for (int i = 0; i < mHasVisibleTaskData.size(); i++) { @@ -2507,7 +2738,14 @@ public void reset() { mCurrentPageScrollDiff = 0; mIgnoreResetTaskId = -1; mTaskListChangeId = -1; - mFocusedTaskViewId = -1; + setFocusedTaskViewId(INVALID_TASK_ID); + mAnyTaskHasBeenDismissed = false; + + if (enableRefactorTaskThumbnail()) { + // TODO(b/353917593): RecentsView is never destroyed, so its dependencies need to + // be cleaned up during the reset, but re-created when RecentsView is "resumed". + // RecentsDependencies.Companion.destroy(); + } Log.d(TAG, "reset - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile + ", mRecentsAnimationController: " + mRecentsAnimationController); @@ -2522,22 +2760,22 @@ public void reset() { } setEnableDrawingLiveTile(false); } - runActionOnRemoteHandles(remoteTargetHandle -> - remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false)); - if (!FeatureFlags.enableSplitContextually()) { - resetFromSplitSelectionState(); - } - + mBlurUtils.setDrawLiveTileBelowRecents(false); // These are relatively expensive and don't need to be done this frame (RecentsView isn't // visible anyway), so defer by a frame to get off the critical path, e.g. app to home. - post(() -> { - unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); - setCurrentPage(0); - LayoutUtils.setViewEnabled(mActionsView, true); - if (mOrientationState.setGestureActive(false)) { - updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); - } - }); + post(this::onReset); + } + + private void onReset() { + unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); + setCurrentPage(0); + LayoutUtils.setViewEnabled(mActionsView, true); + if (mOrientationState.setGestureActive(false)) { + updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); + } + if (enableRefactorTaskThumbnail()) { + mRecentsViewModel.onReset(); + } } public int getRunningTaskViewId() { @@ -2567,13 +2805,12 @@ private int[] getTaskIdsForTaskViewId(int taskViewId) { } @Nullable - private TaskView getTaskViewFromTaskViewId(int taskViewId) { + TaskView getTaskViewFromTaskViewId(int taskViewId) { if (taskViewId == -1) { return null; } - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView taskView = requireTaskViewAt(i); + for (TaskView taskView : getTaskViews()) { if (taskView.getTaskViewId() == taskViewId) { return taskView; } @@ -2594,16 +2831,16 @@ public int getRunningTaskIndex() { * Handle the edge case where Recents could increment task count very high over long * period of device usage. Probably will never happen, but meh. */ - private TaskView getTaskViewFromPool(@TaskView.Type int type) { + protected TaskView getTaskViewFromPool(TaskViewType type) { TaskView taskView; switch (type) { - case TaskView.Type.GROUPED: + case GROUPED: taskView = mGroupedTaskViewPool.getView(); break; - case TaskView.Type.DESKTOP: + case DESKTOP: taskView = mDesktopTaskViewPool.getView(); break; - case TaskView.Type.SINGLE: + case SINGLE: default: taskView = mTaskViewPool.getView(); } @@ -2619,6 +2856,7 @@ private TaskView getTaskViewFromPool(@TaskView.Type int type) { /** * Get the index of the task view whose id matches {@param taskId}. + * * @return -1 if there is no task view for the task id, else the index of the task view. */ public int getTaskIndexForId(int taskId) { @@ -2633,37 +2871,48 @@ public void reloadIfNeeded() { if (!mModel.isTaskListValid(mTaskListChangeId)) { mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState .getFilter(mFilterState.getPackageNameToFilter())); + Log.d(TAG, "reloadIfNeeded - getTasks: " + mTaskListChangeId); if (enableRefactorTaskThumbnail()) { - mTasksRepository.getAllTaskData(/* forceRefresh = */ true); + mRecentsViewModel.refreshAllTaskData(); } + } else { + Log.d(TAG, "reloadIfNeeded - task list still valid: " + mTaskListChangeId); } } /** * Called when a gesture from an app is starting. */ - public void onGestureAnimationStart( - Task[] runningTasks, RotationTouchHelper rotationTouchHelper) { - Log.d(TAG, "onGestureAnimationStart - runningTasks: " + Arrays.toString(runningTasks)); - mActiveGestureRunningTasks = runningTasks; + // TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity` from being + // considered in Overview. + public void onGestureAnimationStart(GroupedTaskInfo groupedTaskInfo) { + Log.d(TAG, "onGestureAnimationStart - groupedTaskInfo: " + groupedTaskInfo); + mActiveGestureGroupedTaskInfo = groupedTaskInfo; + // This needs to be called before the other states are set since it can create the task view if (mOrientationState.setGestureActive(true)) { - setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), - rotationTouchHelper.getDisplayRotation()); + reapplyActiveRotation(); // Force update to ensure the initial task size is computed even if the orientation has // not changed. updateSizeAndPadding(); } - showCurrentTask(mActiveGestureRunningTasks); + showCurrentTask(groupedTaskInfo, "onGestureAnimationStart"); setEnableFreeScroll(false); setEnableDrawingLiveTile(false); setRunningTaskHidden(true); - setTaskIconScaledDown(true); + setTaskIconVisible(false); + } + + /** + * Returns whether the running task's attach alpha should be updated during the attach animation + */ + public boolean shouldUpdateRunningTaskAlpha() { + return enableDesktopTaskAlphaAnimation() && getRunningTaskView() instanceof DesktopTaskView; } private boolean isGestureActive() { - return mActiveGestureRunningTasks != null; + return mActiveGestureGroupedTaskInfo != null; } /** @@ -2671,7 +2920,7 @@ private boolean isGestureActive() { * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. */ public void onSwipeUpAnimationSuccess() { - animateUpTaskIconScale(); + startIconFadeInOnGestureComplete(); setSwipeDownShouldLaunchApp(true); } @@ -2710,28 +2959,12 @@ public AnimatorSet setRecentsChangedOrientation(boolean fadeOut) { return as; } - private void updateChildTaskOrientations() { - for (int i = 0; i < getTaskViewCount(); i++) { - requireTaskViewAt(i).setOrientationState(mOrientationState); - } - boolean shouldRotateMenuForFakeRotation = - !mOrientationState.isRecentsActivityRotationAllowed(); - if (!shouldRotateMenuForFakeRotation) { - return; - } - TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mContainer, TYPE_TASK_MENU); - if (tv != null) { - // Rotation is supported on phone (details at b/254198019#comment4) - tv.onRotationChanged(); - } - } - /** * Called when a gesture from an app has finished, and an end target has been determined. */ public void onPrepareGestureEndAnimation( @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget, - TaskViewSimulator[] taskViewSimulators) { + RemoteTargetHandle[] remoteTargetHandles) { Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: " + endTarget); mCurrentGestureEndTarget = endTarget; boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS; @@ -2740,31 +2973,59 @@ public void onPrepareGestureEndAnimation( } BaseState endState = mSizeStrategy.stateFromGestureEndTarget(endTarget); + // Starting the desk exploded animation when the gesture from an app is released. + if (enableDesktopExplodedView()) { + if (animatorSet == null) { + mUtils.setDeskExplodeProgress(endState.showExplodedDesktopView() ? 1f : 0f); + } else { + animatorSet.play( + ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, + endState.showExplodedDesktopView() ? 1f : 0f)); + } + + for (TaskView taskView : getTaskViews()) { + if (taskView instanceof DesktopTaskView desktopTaskView) { + desktopTaskView.setRemoteTargetHandles(remoteTargetHandles); + } + } + } + if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) { TaskView runningTaskView = getRunningTaskView(); - float runningTaskPrimaryGridTranslation = 0; - float runningTaskSecondaryGridTranslation = 0; + float runningTaskGridTranslationX = 0; + float runningTaskGridTranslationY = 0; if (runningTaskView != null) { // Apply the grid translation to running task unless it's being snapped to // and removes the current translation applied to the running task. - runningTaskPrimaryGridTranslation = runningTaskView.getGridTranslationX() + runningTaskGridTranslationX = runningTaskView.getGridTranslationX() - runningTaskView.getNonGridTranslationX(); - runningTaskSecondaryGridTranslation = runningTaskView.getGridTranslationY(); + runningTaskGridTranslationY = runningTaskView.getGridTranslationY(); } - for (TaskViewSimulator tvs : taskViewSimulators) { + for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) { + TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator(); if (animatorSet == null) { setGridProgress(1); - tvs.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation; - tvs.taskSecondaryTranslation.value = runningTaskSecondaryGridTranslation; + if (enableGridOnlyOverview()) { + tvs.taskGridTranslationX.value = runningTaskGridTranslationX; + tvs.taskGridTranslationY.value = runningTaskGridTranslationY; + } else { + tvs.taskPrimaryTranslation.value = runningTaskGridTranslationX; + tvs.taskSecondaryTranslation.value = runningTaskGridTranslationY; + } } else { animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1)); - animatorSet.play(tvs.carouselScale.animateToValue(1)); - animatorSet.play(tvs.carouselPrimaryTranslation.animateToValue(0)); - animatorSet.play(tvs.carouselSecondaryTranslation.animateToValue(0)); - animatorSet.play(tvs.taskPrimaryTranslation.animateToValue( - runningTaskPrimaryGridTranslation)); - animatorSet.play(tvs.taskSecondaryTranslation.animateToValue( - runningTaskSecondaryGridTranslation)); + if (enableGridOnlyOverview()) { + animatorSet.play(tvs.carouselScale.animateToValue(1)); + animatorSet.play(tvs.taskGridTranslationX.animateToValue( + runningTaskGridTranslationX)); + animatorSet.play(tvs.taskGridTranslationY.animateToValue( + runningTaskGridTranslationY)); + } else { + animatorSet.play(tvs.taskPrimaryTranslation.animateToValue( + runningTaskGridTranslationX)); + animatorSet.play(tvs.taskSecondaryTranslation.animateToValue( + runningTaskGridTranslationY)); + } } } } @@ -2775,13 +3036,21 @@ public void onPrepareGestureEndAnimation( animatorSet.play( ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, splashAlpha)); } + if (enableLargeDesktopWindowingTile()) { + if (animatorSet != null) { + animatorSet.play( + ObjectAnimator.ofFloat(this, DESKTOP_CAROUSEL_DETACH_PROGRESS, 0f)); + } else { + DESKTOP_CAROUSEL_DETACH_PROGRESS.set(this, 0f); + } + } } /** * Called when a gesture from an app has finished, and the animation to the target has ended. */ public void onGestureAnimationEnd() { - mActiveGestureRunningTasks = null; + mActiveGestureGroupedTaskInfo = null; if (mOrientationState.setGestureActive(false)) { updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); } @@ -2790,20 +3059,48 @@ public void onGestureAnimationEnd() { setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS); Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile); setRunningTaskHidden(false); - animateUpTaskIconScale(); + startIconFadeInOnGestureComplete(); animateActionsViewIn(); + if (mEnableDrawingLiveTile) { + if (enableDesktopExplodedView()) { + for (TaskView taskView : getTaskViews()) { + if (taskView instanceof DesktopTaskView desktopTaskView) { + desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles); + } + } + } + TaskView runningTaskView = getRunningTaskView(); + if (showAsGrid() && enableGridOnlyOverview() && runningTaskView != null) { + runActionOnRemoteHandles(remoteTargetHandle -> { + TaskViewSimulator taskViewSimulator = remoteTargetHandle.getTaskViewSimulator(); + // After settling in Overview, recentsScroll will be used to adjust horizontally + // location and taskGridTranslationX doesn't needs to be applied. + taskViewSimulator.taskGridTranslationX.value = 0; + taskViewSimulator.taskGridTranslationY.value = + runningTaskView.getGridTranslationY(); + }); + } + } + mCurrentGestureEndTarget = null; } /** * Returns true if we should add a stub taskView for the running task id */ - protected boolean shouldAddStubTaskView(Task[] runningTasks) { - int[] runningTaskIds = Arrays.stream(runningTasks).mapToInt(task -> task.key.id).toArray(); + protected boolean shouldAddStubTaskView(GroupedTaskInfo groupedTaskInfo) { + int[] runningTaskIds; + if (groupedTaskInfo != null) { + runningTaskIds = groupedTaskInfo.getTaskInfoList().stream().mapToInt( + taskInfo -> taskInfo.taskId).toArray(); + } else { + runningTaskIds = new int[0]; + } TaskView matchingTaskView = null; - if (hasDesktopTask(runningTasks) && runningTaskIds.length == 1) { - // TODO(b/249371338): Unsure if it's expected, desktop runningTasks only have a single + if (groupedTaskInfo != null && groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK) + && runningTaskIds.length == 1) { + // TODO(b/342635213): 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) { @@ -2816,44 +3113,41 @@ protected boolean shouldAddStubTaskView(Task[] runningTasks) { } /** - * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}. + * Creates a task view (if necessary) to represent the tasks with the {@param groupedTaskInfo}. * * All subsequent calls to reload will keep the task as the first item until {@link #reset()} * 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) { + private void showCurrentTask(GroupedTaskInfo groupedTaskInfo, String caller) { + Log.d(TAG, "showCurrentTask(" + caller + ") - groupedTaskInfo: " + groupedTaskInfo); + if (groupedTaskInfo == null) { return; } + int runningTaskViewId = -1; - boolean needGroupTaskView = runningTasks.length > 1; - boolean needDesktopTask = hasDesktopTask(runningTasks); - if (shouldAddStubTaskView(runningTasks)) { + if (shouldAddStubTaskView(groupedTaskInfo)) { boolean wasEmpty = getChildCount() == 0; // Add an empty view for now until the task plan is loaded and applied final TaskView taskView; - if (needDesktopTask) { - taskView = getTaskViewFromPool(TaskView.Type.DESKTOP); - mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length); - ((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks), - mOrientationState, mTaskOverlayFactory); - } else if (needGroupTaskView) { - taskView = getTaskViewFromPool(TaskView.Type.GROUPED); - mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]}; + if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK)) { + taskView = mUtils.createDesktopTaskViewForActiveDesk(groupedTaskInfo); + } else if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_SPLIT)) { + taskView = getTaskViewFromPool(TaskViewType.GROUPED); // 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, mTaskOverlayFactory, mSplitBoundsConfig); + ((GroupedTaskView) taskView).bind(Task.from(groupedTaskInfo.getTaskInfo1()), + Task.from(groupedTaskInfo.getTaskInfo2()), mOrientationState, + mTaskOverlayFactory, mSplitBoundsConfig); } else { - taskView = getTaskViewFromPool(TaskView.Type.SINGLE); - // 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, mTaskOverlayFactory); + taskView = getTaskViewFromPool(TaskViewType.SINGLE); + taskView.bind(Task.from(groupedTaskInfo.getTaskInfo1()), mOrientationState, + mTaskOverlayFactory); } - addView(taskView, 0); + if (mAddDesktopButton != null && wasEmpty) { + addView(mAddDesktopButton); + } + addView(taskView, mUtils.getRunningTaskExpectedIndex(taskView)); runningTaskViewId = taskView.getTaskViewId(); if (wasEmpty) { addView(mClearAllButton); @@ -2864,41 +3158,40 @@ private void showCurrentTask(Task[] runningTasks) { measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), makeMeasureSpec(getMeasuredHeight(), EXACTLY)); layout(getLeft(), getTop(), getRight(), getBottom()); - } else if (getTaskViewByTaskId(runningTasks[0].key.id) != null) { - runningTaskViewId = getTaskViewByTaskId(runningTasks[0].key.id).getTaskViewId(); + } else { + var runningTaskView = getTaskViewByTaskId(groupedTaskInfo.getTaskInfo1().taskId); + if (runningTaskView != null) { + runningTaskViewId = runningTaskView.getTaskViewId(); + } } boolean runningTaskTileHidden = mRunningTaskTileHidden; setCurrentTask(runningTaskViewId); - mFocusedTaskViewId = enableGridOnlyOverview() ? INVALID_TASK_ID : runningTaskViewId; + + int focusedTaskViewId; + if (enableGridOnlyOverview()) { + focusedTaskViewId = INVALID_TASK_ID; + } else if (enableLargeDesktopWindowingTile() + && getRunningTaskView() instanceof DesktopTaskView) { + TaskView focusedTaskView = mUtils.getFirstNonDesktopTaskView(); + focusedTaskViewId = + focusedTaskView != null ? focusedTaskView.getTaskViewId() : INVALID_TASK_ID; + } else { + focusedTaskViewId = runningTaskViewId; + } + setFocusedTaskViewId(focusedTaskViewId); + runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex())); setRunningTaskViewShowScreenshot(false); setRunningTaskHidden(runningTaskTileHidden); // Update task size after setting current task. updateTaskSize(); - updateChildTaskOrientations(); + mUtils.updateChildTaskOrientations(); // Reload the task list reloadIfNeeded(); } - private boolean hasDesktopTask(Task[] runningTasks) { - try { - if (!DesktopModeStatus.canEnterDesktopMode(getContext())) { - return false; - } - } catch (NoClassDefFoundError e) { - // Desktop mode is not supported on this device - return false; - } - for (Task task : runningTasks) { - if (task.key.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) { - return true; - } - } - return false; - } - /** * Sets the running task id, cleaning up the old running task if necessary. */ @@ -2909,7 +3202,7 @@ public void setCurrentTask(int runningTaskViewId) { if (mRunningTaskViewId != -1) { // Reset the state on the old running task view - setTaskIconScaledDown(false); + setTaskIconVisible(true); setRunningTaskViewShowScreenshot(true); setRunningTaskHidden(false); } @@ -2917,21 +3210,20 @@ public void setCurrentTask(int 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(); - } + if (enableRefactorTaskThumbnail()) { + TaskView runningTaskView = getTaskViewFromTaskViewId(runningTaskViewId); + mRecentsViewModel.updateRunningTask( + runningTaskView != null ? runningTaskView.getTaskIdSet() + : Collections.emptySet()); } } + private void setFocusedTaskViewId(int viewId) { + mFocusedTaskViewId = viewId; + } + private int getTaskViewIdFromTaskId(int taskId) { TaskView taskView = getTaskViewByTaskId(taskId); return taskView != null ? taskView.getTaskViewId() : -1; @@ -2942,68 +3234,79 @@ private int getTaskViewIdFromTaskId(int taskId) { */ public void setRunningTaskHidden(boolean isHidden) { mRunningTaskTileHidden = isHidden; + // mRunningTaskAttachAlpha can be changed by RUNNING_TASK_ATTACH_ALPHA animation without + // changing mRunningTaskTileHidden. + mRunningTaskAttachAlpha = isHidden ? 0f : 1f; TaskView runningTask = getRunningTaskView(); - if (runningTask != null) { - runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha); - if (!isHidden) { - AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask, - AccessibilityEvent.TYPE_VIEW_FOCUSED, null); - } + if (runningTask == null) { + return; + } + applyAttachAlpha(); + if (!isHidden) { + AccessibilityManagerCompat.sendCustomAccessibilityEvent( + runningTask, AccessibilityEvent.TYPE_VIEW_FOCUSED, null); } } + private void applyAttachAlpha() { + // Only hide non running task carousel when it's fully off screen, otherwise it needs to + // be visible to move to on screen. + mUtils.applyAttachAlpha( + /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress == 1f); + } + private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { + setRunningTaskViewShowScreenshot(showScreenshot, /*updatedThumbnails=*/null); + } + + private void setRunningTaskViewShowScreenshot(boolean showScreenshot, + @Nullable Map updatedThumbnails) { mRunningTaskShowScreenshot = showScreenshot; TaskView runningTaskView = getRunningTaskView(); if (runningTaskView != null) { - runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot); + runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot, updatedThumbnails); + } + if (enableRefactorTaskThumbnail()) { + mRecentsViewModel.setRunningTaskShowScreenshot(showScreenshot); } } - public void setTaskIconScaledDown(boolean isScaledDown) { - if (mTaskIconScaledDown != isScaledDown) { - mTaskIconScaledDown = isScaledDown; - int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - requireTaskViewAt(i).setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1); + /** + * Updates icon visibility when going in or out of overview. + */ + public void setTaskIconVisible(boolean isVisible) { + if (mTaskIconVisible != isVisible) { + mTaskIconVisible = isVisible; + for (TaskView taskView : getTaskViews()) { + taskView.setIconVisibleForGesture(mTaskIconVisible); } } } private void animateActionsViewIn() { if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) { - animateActionsViewAlpha(1, TaskView.SCALE_ICON_DURATION); - } - } - - public void animateUpTaskIconScale() { - mTaskIconScaledDown = false; - int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); - taskView.animateIconScaleAndDimIntoView(); + animateActionsViewAlpha(1, TaskView.FADE_IN_ICON_DURATION); } } /** - * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid - * layout. - * This method is used when no task dismissal has occurred. + * Updates icon visibility when gesture is settled. */ - private void updateGridProperties() { - updateGridProperties(false, Integer.MAX_VALUE); + public void startIconFadeInOnGestureComplete() { + mTaskIconVisible = true; + for (TaskView taskView : getTaskViews()) { + taskView.startIconFadeInOnGestureComplete(); + } } /** * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid * layout. * - * This method is used when task dismissal has occurred, but rebalance is not needed. - * - * @param isTaskDismissal indicates if update was called due to task dismissal + * Skips rebalance. */ - private void updateGridProperties(boolean isTaskDismissal) { - updateGridProperties(isTaskDismissal, Integer.MAX_VALUE); + private void updateGridProperties() { + updateGridProperties(null); } /** @@ -3013,13 +3316,11 @@ private void updateGridProperties(boolean isTaskDismissal) { * This method only calculates the potential position and depends on {@link #setGridProgress} to * apply the actual scaling and translation. * - * @param isTaskDismissal indicates if update was called due to task dismissal - * @param startRebalanceAfter which view index to start rebalancing from. Use Integer.MAX_VALUE - * to skip rebalance + * @param lastVisibleTaskViewDuringDismiss which TaskView to start rebalancing from. Use + * `null` to skip rebalance. */ - private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAfter) { - int taskCount = getTaskViewCount(); - if (taskCount == 0) { + private void updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss) { + if (!hasTaskViews()) { return; } @@ -3028,68 +3329,97 @@ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAft int topRowWidth = 0; int bottomRowWidth = 0; + int largeTileRowWidth = 0; float topAccumulatedTranslationX = 0; float bottomAccumulatedTranslationX = 0; - // Contains whether the child index is in top or bottom of grid (for non-focused task) - // Different from mTopRowIdSet, which contains the taskViewId of what task is in top row - IntSet topSet = new IntSet(); - IntSet bottomSet = new IntSet(); - - // Horizontal grid translation for each task - float[] gridTranslations = new float[taskCount]; + // Horizontal grid translation for each task. + Map gridTranslations = new HashMap<>(); - int focusedTaskIndex = Integer.MAX_VALUE; - int focusedTaskShift = 0; - int focusedTaskWidthAndSpacing = 0; + TaskView lastLargeTaskView = mUtils.getLastLargeTaskView(); + int focusedTaskViewShift = 0; + int largeTaskWidthAndSpacing = 0; int snappedTaskRowWidth = 0; + int expectedCurrentTaskRowWidth = 0; int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage(); TaskView snappedTaskView = getTaskViewAt(snappedPage); TaskView homeTaskView = getHomeTaskView(); + TaskView expectedCurrentTaskView = mUtils.getExpectedCurrentTask(getRunningTaskView(), + getFocusedTaskView()); TaskView nextFocusedTaskView = null; - if (!isTaskDismissal) { + // Don't clear the top row, if the user has dismissed a task, to maintain the task order. + if (!mAnyTaskHasBeenDismissed) { mTopRowIdSet.clear(); } - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); + + // Consecutive task views in the top row or bottom row, which means another one set will + // be cleared up while starting to add TaskViews to one of them. Also means only one of + // them can be non-empty at most. + Set lastTopTaskViews = new HashSet<>(); + Set lastBottomTaskViews = new HashSet<>(); + + int largeTasksCount = 0; + // True if the last large TaskView has been visited during the TaskViews iteration. + boolean encounteredLastLargeTaskView = false; + // True if the highest index visible TaskView has been visited during the TaskViews + // iteration. + boolean encounteredLastVisibleTaskView = false; + for (TaskView taskView : getTaskViews()) { + if (taskView == lastLargeTaskView) { + encounteredLastLargeTaskView = true; + } + if (taskView == lastVisibleTaskViewDuringDismiss) { + encounteredLastVisibleTaskView = true; + } + float gridTranslation = 0f; int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing; // Evenly distribute tasks between rows unless rearranging due to task dismissal, in // which case keep tasks in their respective rows. For the running task, don't join // the grid. - if (taskView.isFocusedTask()) { - topRowWidth += taskWidthAndSpacing; - bottomRowWidth += taskWidthAndSpacing; - - focusedTaskIndex = i; - focusedTaskWidthAndSpacing = taskWidthAndSpacing; - gridTranslations[i] += focusedTaskShift; - gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; + if (taskView.isLargeTile()) { + largeTasksCount++; + // DesktopTaskView`s are hidden during split select state, so we shouldn't count + // them when calculating row width. + if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) { + topRowWidth += taskWidthAndSpacing; + bottomRowWidth += taskWidthAndSpacing; + largeTileRowWidth += taskWidthAndSpacing; + } + gridTranslation += focusedTaskViewShift; + gridTranslation += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; // Center view vertically in case it's from different orientation. taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin - taskView.getLayoutParams().height) / 2f); + largeTaskWidthAndSpacing = taskWidthAndSpacing; + if (taskView == snappedTaskView) { - // If focused task is snapped, the row width is just task width and spacing. - snappedTaskRowWidth = taskWidthAndSpacing; + snappedTaskRowWidth = largeTileRowWidth; + } + if (taskView == expectedCurrentTaskView) { + expectedCurrentTaskRowWidth = largeTileRowWidth; } } else { - if (i > focusedTaskIndex) { - // For tasks after the focused task, shift by focused task's width and spacing. - gridTranslations[i] += - mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing; + if (encounteredLastLargeTaskView) { + // For tasks after the last large task, shift by large task's width and spacing. + gridTranslation += + mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing; } else { - // For task before the focused task, accumulate the width and spacing to - // calculate the distance focused task need to shift. - focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; + // For TaskViews before the new focused TaskView, accumulate the width and + // spacing to calculate the distance the new focused TaskView needs to shift. + // This could happen for example after multiple times of dismissing the + // focused TaskView, the triggered rebalance might set a non-first TaskView + // inside `mChildren` as the new focused TaskView. + focusedTaskViewShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; } int taskViewId = taskView.getTaskViewId(); - // Rebalance the grid starting after a certain index boolean isTopRow; - if (isTaskDismissal) { - if (i > startRebalanceAfter) { + if (mAnyTaskHasBeenDismissed) { + // Rebalance the grid starting after a certain index. + if (encounteredLastVisibleTaskView) { mTopRowIdSet.remove(taskViewId); isTopRow = topRowWidth <= bottomRowWidth; } else { @@ -3106,47 +3436,47 @@ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAft } else { topRowWidth += taskWidthAndSpacing; } - topSet.add(i); mTopRowIdSet.add(taskViewId); - taskView.setGridTranslationY(mTaskGridVerticalDiff); // Move horizontally into empty space. float widthOffset = 0; - for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) { - if (j == focusedTaskIndex) { - continue; - } - widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing; + for (TaskView bottomTaskView : lastBottomTaskViews) { + widthOffset += bottomTaskView.getLayoutParams().width + mPageSpacing; } float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; - gridTranslations[i] += topAccumulatedTranslationX + currentTaskTranslationX; + gridTranslation += topAccumulatedTranslationX + currentTaskTranslationX; topAccumulatedTranslationX += currentTaskTranslationX; + lastTopTaskViews.add(taskView); + lastBottomTaskViews.clear(); } else { bottomRowWidth += taskWidthAndSpacing; - bottomSet.add(i); // Move into bottom row. taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff); // Move horizontally into empty space. float widthOffset = 0; - for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) { - if (j == focusedTaskIndex) { - continue; - } - widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing; + for (TaskView topTaskView : lastTopTaskViews) { + widthOffset += topTaskView.getLayoutParams().width + mPageSpacing; } float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; - gridTranslations[i] += bottomAccumulatedTranslationX + currentTaskTranslationX; + gridTranslation += bottomAccumulatedTranslationX + currentTaskTranslationX; bottomAccumulatedTranslationX += currentTaskTranslationX; + lastBottomTaskViews.add(taskView); + lastTopTaskViews.clear(); } + int taskViewRowWidth = isTopRow ? topRowWidth : bottomRowWidth; if (taskView == snappedTaskView) { - snappedTaskRowWidth = isTopRow ? topRowWidth : bottomRowWidth; + snappedTaskRowWidth = taskViewRowWidth; + } + if (taskView == expectedCurrentTaskView) { + expectedCurrentTaskRowWidth = taskViewRowWidth; } } + gridTranslations.put(taskView, gridTranslation); } // We need to maintain snapped task's page scroll invariant between quick switch and @@ -3157,22 +3487,22 @@ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAft if (snappedTaskView != null) { snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment( /*gridEnabled=*/false); - snappedTaskGridTranslationX = gridTranslations[snappedPage]; + snappedTaskGridTranslationX = gridTranslations.getOrDefault(snappedTaskView, 0f); } // Use the accumulated translation of the row containing the last task. - float clearAllAccumulatedTranslation = topSet.contains(taskCount - 1) + float clearAllAccumulatedTranslation = !lastTopTaskViews.isEmpty() ? topAccumulatedTranslationX : bottomAccumulatedTranslationX; // If the last task is on the shorter row, ClearAllButton will embed into the shorter row // which is not what we want. Compensate the width difference of the 2 rows in that case. float shorterRowCompensation = 0; if (topRowWidth <= bottomRowWidth) { - if (topSet.contains(taskCount - 1)) { + if (!lastTopTaskViews.isEmpty()) { shorterRowCompensation = bottomRowWidth - topRowWidth; } } else { - if (bottomSet.contains(taskCount - 1)) { + if (!lastBottomTaskViews.isEmpty()) { shorterRowCompensation = topRowWidth - bottomRowWidth; } } @@ -3183,12 +3513,20 @@ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAft // accordingly. Update longRowWidth if ClearAllButton has been moved. float clearAllShortTotalWidthTranslation = 0; int longRowWidth = Math.max(topRowWidth, bottomRowWidth); - if (longRowWidth < mLastComputedGridSize.width()) { - mClearAllShortTotalWidthTranslation = - (mIsRtl - ? mLastComputedTaskSize.right - : deviceProfile.widthPx - mLastComputedTaskSize.left) - - longRowWidth - deviceProfile.overviewGridSideMargin; + + // If first task is not in the expected position (mLastComputedTaskSize) and being too close + // to ClearAllButton, then apply extra translation to ClearAllButton. + int rowWidthAfterExpectedCurrentTask = longRowWidth - expectedCurrentTaskRowWidth; + int expectedCurrentTaskWidthAndSpacing = + (expectedCurrentTaskView != null + ? expectedCurrentTaskView.getLayoutParams().width + : 0 + ) + mPageSpacing; + int firstTaskStart = mLastComputedGridSize.left + rowWidthAfterExpectedCurrentTask + + expectedCurrentTaskWidthAndSpacing; + int expectedFirstTaskStart = mLastComputedTaskSize.right; + if (firstTaskStart < expectedFirstTaskStart) { + mClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart; clearAllShortTotalWidthTranslation = mIsRtl ? -mClearAllShortTotalWidthTranslation : mClearAllShortTotalWidthTranslation; if (snappedTaskRowWidth == longRowWidth) { @@ -3203,10 +3541,10 @@ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAft float clearAllTotalTranslationX = clearAllAccumulatedTranslation + clearAllShorterRowCompensation + clearAllShortTotalWidthTranslation + snappedTaskNonGridScrollAdjustment; - if (focusedTaskIndex < taskCount) { + if (largeTasksCount > 0) { // Shift by focused task's width and spacing if a task is focused. clearAllTotalTranslationX += - mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing; + mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing; } // Make sure there are enough space between snapped page and ClearAllButton, for the case @@ -3218,27 +3556,33 @@ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAft (mIsRtl ? mLastComputedTaskSize.left : deviceProfile.widthPx - mLastComputedTaskSize.right) - - deviceProfile.overviewGridSideMargin - mPageSpacing - + (mTaskWidth - snappedTaskView.getLayoutParams().width) - - mClearAllShortTotalWidthTranslation; + - deviceProfile.overviewGridSideMargin - mPageSpacing + + (mTaskWidth - snappedTaskView.getLayoutParams().width) + - mClearAllShortTotalWidthTranslation; if (distanceFromClearAll < minimumDistance) { int distanceDifference = minimumDistance - distanceFromClearAll; snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference; } } - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); - taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX - + snappedTaskNonGridScrollAdjustment); + for (TaskView taskView : getTaskViews()) { + taskView.setGridTranslationX( + gridTranslations.getOrDefault(taskView, 0f) - snappedTaskGridTranslationX + + snappedTaskNonGridScrollAdjustment); } - final TaskView runningTask = getRunningTaskView(); - if (showAsGrid() && enableGridOnlyOverview() && runningTask != null) { - runActionOnRemoteHandles( - remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() - .taskSecondaryTranslation.value = runningTask.getGridTranslationY() - ); + if (mAddDesktopButton != null) { + TaskView firstTaskView = getFirstTaskView(); + float translationX = 0f; + if (firstTaskView != null) { + translationX += firstTaskView.getGridTranslationX(); + } + if (focusedTaskViewShift != 0) { + // If the focused task is inserted between `firstTaskView` and + // `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate. + translationX += largeTaskWidthAndSpacing; + } + mAddDesktopButton.setGridTranslationX(translationX); } mClearAllButton.setGridTranslationPrimary( @@ -3246,19 +3590,18 @@ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAft mClearAllButton.setGridScrollOffset( mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left : mLastComputedTaskSize.right - mLastComputedGridSize.right); - setGridProgress(mGridProgress); } - private boolean isSameGridRow(TaskView taskView1, TaskView taskView2) { + protected boolean isSameGridRow(TaskView taskView1, TaskView taskView2) { if (taskView1 == null || taskView2 == null) { return false; } - int taskViewId1 = taskView1.getTaskViewId(); - int taskViewId2 = taskView2.getTaskViewId(); - if (taskViewId1 == mFocusedTaskViewId || taskViewId2 == mFocusedTaskViewId) { + if (taskView1.isLargeTile() || taskView2.isLargeTile()) { return false; } + int taskViewId1 = taskView1.getTaskViewId(); + int taskViewId2 = taskView2.getTaskViewId(); return (mTopRowIdSet.contains(taskViewId1) && mTopRowIdSet.contains(taskViewId2)) || ( !mTopRowIdSet.contains(taskViewId1) && !mTopRowIdSet.contains(taskViewId2)); } @@ -3271,22 +3614,16 @@ private boolean isSameGridRow(TaskView taskView1, TaskView taskView2) { private void setGridProgress(float gridProgress) { mGridProgress = gridProgress; - int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - requireTaskViewAt(i).setGridProgress(gridProgress); + for (TaskView taskView : getTaskViews()) { + taskView.setGridProgress(gridProgress); } mClearAllButton.setGridProgress(gridProgress); } private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) { - int taskCount = getTaskViewCount(); - if (taskCount == 0) { - return; - } - mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha; - for (int i = 0; i < taskCount; i++) { - requireTaskViewAt(i).setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); + for (TaskView taskView : getTaskViews()) { + taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); } } @@ -3300,12 +3637,12 @@ private void enableLayoutTransitions() { mLayoutTransition.addTransitionListener(new TransitionListener() { @Override public void startTransition(LayoutTransition transition, ViewGroup viewGroup, - View view, int i) { + View view, int i) { } @Override public void endTransition(LayoutTransition transition, ViewGroup viewGroup, - View view, int i) { + View view, int i) { // When the unpinned task is added, snap to first page and disable transitions if (view instanceof TaskView) { snapToPage(0); @@ -3358,12 +3695,9 @@ private void addDismissedTaskAnimations(TaskView taskView, long duration, if (taskView.isRunningTask()) { anim.addOnFrameCallback(() -> { if (!mEnableDrawingLiveTile) return; - runActionOnRemoteHandles( - remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() - .taskSecondaryTranslation.value = getPagedOrientationHandler() - .getSecondaryValue(taskView.getTranslationX(), - taskView.getTranslationY() - )); + runActionOnRemoteHandles(remoteTargetHandle -> + remoteTargetHandle.getTaskViewSimulator().taskSecondaryTranslation.value = + taskView.getSecondaryDismissTranslationProperty().get(taskView)); redrawLiveTile(); }); } @@ -3408,6 +3742,7 @@ private void createInitialSplitSelectAnimation(PendingAnimation anim) { mSplitSelectStateController.getSplitAnimationController(). playAnimPlaceholderToFullscreen(mContainer, view, Optional.of(() -> resetFromSplitSelectionState()))); + firstFloatingTaskView.setContentDescription(splitAnimInitProps.getContentDescription()); // SplitInstructionsView: animate in safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); @@ -3443,11 +3778,7 @@ public void onAnimationStart(Animator animation) { InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER); } else { // If transition to split select was interrupted, clean up to prevent glitches - if (FeatureFlags.enableSplitContextually()) { - mSplitSelectStateController.resetState(); - } else { - resetFromSplitSelectionState(); - } + mSplitSelectStateController.resetState(); InteractionJankMonitorWrapper.cancel(Cuj.CUJ_SPLIT_SCREEN_ENTER); } @@ -3457,17 +3788,22 @@ public void onAnimationStart(Animator animation) { /** * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}. - * @param dismissedTaskView the {@link TaskView} to be dismissed - * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated - * @param shouldRemoveTask whether the associated {@link Task} should be removed from - * ActivityManager after dismissal - * @param duration duration of the animation + * + * @param dismissedTaskView the {@link TaskView} to be dismissed + * @param animateTaskView whether the {@link TaskView} to be dismissed should be + * animated + * @param shouldRemoveTask whether the associated {@link Task} should be removed from + * ActivityManager after dismissal + * @param duration duration of the animation * @param dismissingForSplitSelection task dismiss animation is used for entering split * selection state from app icon + * @param isExpressiveDismiss runs expressive animations controlled via + * {@link RecentsDismissUtils} */ - public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissedTaskView, + public void createTaskDismissAnimation(PendingAnimation anim, + @Nullable TaskView dismissedTaskView, boolean animateTaskView, boolean shouldRemoveTask, long duration, - boolean dismissingForSplitSelection) { + boolean dismissingForSplitSelection, boolean isExpressiveDismiss) { if (mPendingAnimation != null) { mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd(); } @@ -3480,35 +3816,44 @@ public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissed boolean showAsGrid = showAsGrid(); int taskCount = getTaskViewCount(); int dismissedIndex = indexOfChild(dismissedTaskView); - int dismissedTaskViewId = dismissedTaskView.getTaskViewId(); + int dismissedTaskViewId = + dismissedTaskView != null ? dismissedTaskView.getTaskViewId() : INVALID_TASK_ID; // Grid specific properties. boolean isFocusedTaskDismissed = false; boolean isStagingFocusedTask = false; + boolean isSlidingTasks = false; TaskView nextFocusedTaskView = null; boolean nextFocusedTaskFromTop = false; float dismissedTaskWidth = 0; float nextFocusedTaskWidth = 0; - // Non-grid specific properties. int[] oldScroll = new int[count]; int[] newScroll = new int[count]; int scrollDiffPerPage = 0; + // Non-grid specific properties. boolean needsCurveUpdates = false; + boolean areAllDesktopTasksDismissed = false; if (showAsGrid) { - dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing; - isFocusedTaskDismissed = dismissedTaskViewId == mFocusedTaskViewId; + if (dismissedTaskView != null) { + dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing; + } + isFocusedTaskDismissed = dismissedTaskViewId != INVALID_TASK_ID + && dismissedTaskViewId == mFocusedTaskViewId; + if (dismissingForSplitSelection && getTaskViewAt( + mCurrentPage) instanceof DesktopTaskView) { + areAllDesktopTasksDismissed = true; + } if (isFocusedTaskDismissed) { if (isSplitSelectionActive()) { isStagingFocusedTask = true; } else { nextFocusedTaskFromTop = - mTopRowIdSet.size() > 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f; + !mTopRowIdSet.isEmpty() && mTopRowIdSet.size() >= (taskCount - 1) / 2f; // Pick the next focused task from the preferred row. - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); - if (taskView == dismissedTaskView) { + for (TaskView taskView : getTaskViews()) { + if (taskView == dismissedTaskView || taskView.isLargeTile()) { continue; } boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); @@ -3524,15 +3869,16 @@ public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissed } } } - } else { - getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); - getPageScrolls(newScroll, false, - v -> v.getVisibility() != GONE && v != dismissedTaskView); - if (count > 1) { - scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); - } } + getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); + getPageScrolls(newScroll, false, + v -> v.getVisibility() != GONE && v != dismissedTaskView); + if (count > 1) { + scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); + } + + isSlidingTasks = isStagingFocusedTask || areAllDesktopTasksDismissed; float dismissTranslationInterpolationEnd = 1; boolean closeGapBetweenClearAll = false; boolean isClearAllHidden = isClearAllHidden(); @@ -3543,31 +3889,46 @@ public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissed int currentPageScroll = getScrollForPage(mCurrentPage); int lastGridTaskScroll = getScrollForPage(indexOfChild(lastGridTaskView)); boolean currentPageSnapsToEndOfGrid = currentPageScroll == lastGridTaskScroll; - if (lastGridTaskView != null && lastGridTaskView.isVisibleToUser()) { + + int topGridRowSize = mTopRowIdSet.size(); + int numLargeTiles = mUtils.getLargeTileCount(); + int bottomGridRowSize = taskCount - mTopRowIdSet.size() - numLargeTiles; + boolean topRowLonger = topGridRowSize > bottomGridRowSize; + boolean bottomRowLonger = bottomGridRowSize > topGridRowSize; + boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId); + boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed; + if (dismissedTaskFromTop || (isFocusedTaskDismissed && nextFocusedTaskFromTop)) { + topGridRowSize--; + } + if (dismissedTaskFromBottom || (isFocusedTaskDismissed && !nextFocusedTaskFromTop)) { + bottomGridRowSize--; + } + int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize) + * (mLastComputedGridTaskSize.width() + mPageSpacing); + if (!enableGridOnlyOverview() && !isStagingFocusedTask) { + longRowWidth += mLastComputedTaskSize.width() + mPageSpacing; + } + // Compensate the removed gap if we don't already have shortTotalCompensation, + // and adjust accordingly to the new shortTotalCompensation after dismiss. + int newClearAllShortTotalWidthTranslation = 0; + if (mClearAllShortTotalWidthTranslation == 0) { + // If first task is not in the expected position (mLastComputedTaskSize) and being too + // close to ClearAllButton, then apply extra translation to ClearAllButton. + int firstTaskStart = mLastComputedGridSize.left + longRowWidth; + int expectedFirstTaskStart = mLastComputedTaskSize.right; + if (firstTaskStart < expectedFirstTaskStart) { + newClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart; + } + } + if (lastGridTaskView != null && ( + (!isExpressiveDismiss && lastGridTaskView.isVisibleToUser()) || (isExpressiveDismiss + && (isTaskViewVisible(lastGridTaskView) + || lastGridTaskView == dismissedTaskView)))) { // After dismissal, animate translation of the remaining tasks to fill any gap left // between the end of the grid and the clear all button. Only animate if the clear // all button is visible or would become visible after dismissal. float longGridRowWidthDiff = 0; - int topGridRowSize = mTopRowIdSet.size(); - int bottomGridRowSize = taskCount - mTopRowIdSet.size() - - (enableGridOnlyOverview() ? 0 : 1); - boolean topRowLonger = topGridRowSize > bottomGridRowSize; - boolean bottomRowLonger = bottomGridRowSize > topGridRowSize; - boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId); - boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed; - if (dismissedTaskFromTop || (isFocusedTaskDismissed && nextFocusedTaskFromTop)) { - topGridRowSize--; - } - if (dismissedTaskFromBottom || (isFocusedTaskDismissed && !nextFocusedTaskFromTop)) { - bottomGridRowSize--; - } - int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize) - * (mLastComputedGridTaskSize.width() + mPageSpacing); - if (!enableGridOnlyOverview() && !isStagingFocusedTask) { - longRowWidth += mLastComputedTaskSize.width() + mPageSpacing; - } - float gapWidth = 0; if ((topRowLonger && dismissedTaskFromTop) || (bottomRowLonger && dismissedTaskFromBottom)) { @@ -3579,17 +3940,6 @@ public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissed } if (gapWidth > 0) { if (mClearAllShortTotalWidthTranslation == 0) { - // Compensate the removed gap if we don't already have shortTotalCompensation, - // and adjust accordingly to the new shortTotalCompensation after dismiss. - int newClearAllShortTotalWidthTranslation = 0; - if (longRowWidth < mLastComputedGridSize.width()) { - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - newClearAllShortTotalWidthTranslation = - (mIsRtl - ? mLastComputedTaskSize.right - : deviceProfile.widthPx - mLastComputedTaskSize.left) - - longRowWidth - deviceProfile.overviewGridSideMargin; - } float gapCompensation = gapWidth - newClearAllShortTotalWidthTranslation; longGridRowWidthDiff += mIsRtl ? -gapCompensation : gapCompensation; } @@ -3618,6 +3968,22 @@ public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissed // the only invariant point in landscape split screen. snapToLastTask = true; } + if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) { + TaskView lastLargeTile = mUtils.getLastLargeTaskView(); + if (lastLargeTile != null) { + // Calculate the distance to put last large tile back to middle of the screen. + int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); + int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile)); + longGridRowWidthDiff = primaryScroll - lastLargeTileScroll; + + if (!isClearAllHidden) { + // If ClearAllButton is visible, reduce the distance by scroll difference + // between ClearAllButton and the last task. + longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0, + getPagedOrientationHandler().getPrimarySize(mClearAllButton)); + } + } + } // If we need to animate the grid to compensate the clear all gap, we split the second // half of the dismiss pending animation (in which the non-dismissed tasks slide into @@ -3635,8 +4001,7 @@ public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissed END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + (taskCount - 1) * halfAdditionalDismissTranslationOffset, END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1); - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); + for (TaskView taskView : getTaskViews()) { anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff, clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1)); dismissTranslationInterpolationEnd = Utilities.boundToRange( @@ -3672,140 +4037,99 @@ public void onAnimationEnd(Animator animation) { SplitAnimationTimings splitTimings = AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet); - int distanceFromDismissedTask = 0; + int distanceFromDismissedTask = 1; + int slidingTranslation = 0; + if (isSlidingTasks) { + int nextSnappedPage = indexOfChild(isStagingFocusedTask + ? mUtils.getFirstSmallTaskView() + : mUtils.getFirstNonDesktopTaskView()); + slidingTranslation = getPagedOrientationHandler().getPrimaryScroll(this) + - getScrollForPage(nextSnappedPage); + slidingTranslation += mIsRtl ? newClearAllShortTotalWidthTranslation + : -newClearAllShortTotalWidthTranslation; + } + mTaskViewsDismissPrimaryTranslations.clear(); + int lastTaskViewIndex = indexOfChild(mUtils.getLastTaskView()); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child == dismissedTaskView) { - if (animateTaskView) { - if (dismissingForSplitSelection) { - createInitialSplitSelectAnimation(anim); - } else { - addDismissedTaskAnimations(dismissedTaskView, duration, anim); - } + if (animateTaskView && !dismissingForSplitSelection) { + addDismissedTaskAnimations(dismissedTaskView, duration, anim); } - } else if (!showAsGrid) { - // Compute scroll offsets from task dismissal for animation. - // If we just take newScroll - oldScroll, everything to the right of dragged task - // translates to the left. We need to offset this in some cases: - // - In RTL, add page offset to all pages, since we want pages to move to the right - // Additionally, add a page offset if: - // - Current page is rightmost page (leftmost for RTL) - // - Dragging an adjacent page on the left side (right side for RTL) - int offset = mIsRtl ? scrollDiffPerPage : 0; - if (mCurrentPage == dismissedIndex) { - int lastPage = taskCount - 1; - if (mCurrentPage == lastPage) { - offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; - } - } else { - // Dismissing an adjacent page. - int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR) - if (dismissedIndex == negativeAdjacent) { - offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; - } - } - + } else if (!showAsGrid || (enableLargeDesktopWindowingTile() + && dismissedTaskView != null && dismissedTaskView.isLargeTile() + && nextFocusedTaskView == null && !dismissingForSplitSelection)) { + int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, + lastTaskViewIndex); int scrollDiff = newScroll[i] - oldScroll[i] + offset; if (scrollDiff != 0) { - FloatProperty translationProperty = child instanceof TaskView - ? ((TaskView) child).getPrimaryDismissTranslationProperty() - : getPagedOrientationHandler().getPrimaryViewTranslate(); - - float additionalDismissDuration = - ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs( - i - dismissedIndex); - - // We are in non-grid layout. - // If dismissing for split select, use split timings. - // If not, use dismiss timings. - float animationStartProgress = isSplitSelectionActive() - ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f) - : Utilities.boundToRange( - INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - + additionalDismissDuration, 0f, 1f); - - float animationEndProgress = isSplitSelectionActive() - ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset() - + splitTimings.getGridSlideDurationOffset(), 0f, 1f) - : 1f; - - // Slide tiles in horizontally to fill dismissed area - anim.setFloat(child, translationProperty, scrollDiff, - clampToProgress( - splitTimings.getGridSlidePrimaryInterpolator(), - animationStartProgress, - animationEndProgress - ) - ); - - if (mEnableDrawingLiveTile && child instanceof TaskView - && ((TaskView) child).isRunningTask()) { - anim.addOnFrameCallback(() -> { - runActionOnRemoteHandles( - remoteTargetHandle -> - remoteTargetHandle.getTaskViewSimulator() - .taskPrimaryTranslation.value = - getPagedOrientationHandler().getPrimaryValue( - child.getTranslationX(), - child.getTranslationY() - )); - redrawLiveTile(); - }); - } - needsCurveUpdates = true; - } - } else if (child instanceof TaskView) { - TaskView taskView = (TaskView) child; - if (isFocusedTaskDismissed) { - if (nextFocusedTaskView != null && - !isSameGridRow(taskView, nextFocusedTaskView)) { - continue; + if (!isExpressiveDismiss) { + translateTaskWhenDismissed( + child, + Math.abs(i - dismissedIndex), + scrollDiff, + anim, + splitTimings); } - } else { - if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) { - continue; + if (child instanceof TaskView taskView) { + mTaskViewsDismissPrimaryTranslations.put(taskView, scrollDiffPerPage); } + needsCurveUpdates = true; } + } else if (child instanceof TaskView taskView) { // Animate task with index >= dismissed index and in the same row as the // dismissed index or next focused index. Offset successive task dismissal // durations for a staggered effect. - distanceFromDismissedTask++; - int staggerColumn = isStagingFocusedTask + int staggerColumn = isSlidingTasks ? (int) Math.ceil(distanceFromDismissedTask / 2f) : distanceFromDismissedTask; // Set timings based on if user is initiating splitscreen on the focused task, // or splitting/dismissing some other task. - float animationStartProgress = isStagingFocusedTask - ? Utilities.boundToRange( - splitTimings.getGridSlideStartOffset() - + (splitTimings.getGridSlideStaggerOffset() - * staggerColumn), - 0f, - dismissTranslationInterpolationEnd) - : Utilities.boundToRange( - INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - * staggerColumn, 0f, dismissTranslationInterpolationEnd); - float animationEndProgress = isStagingFocusedTask - ? Utilities.boundToRange( - splitTimings.getGridSlideStartOffset() - + (splitTimings.getGridSlideStaggerOffset() * staggerColumn) - + splitTimings.getGridSlideDurationOffset(), - 0f, - dismissTranslationInterpolationEnd) - : dismissTranslationInterpolationEnd; - Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR; + final float animationStartProgress; + if (isSlidingTasks) { + float slidingStartOffset = splitTimings.getGridSlideStartOffset() + + (splitTimings.getGridSlideStaggerOffset() * staggerColumn); + if (areAllDesktopTasksDismissed) { + animationStartProgress = Utilities.boundToRange( + slidingStartOffset + + splitTimings.getDesktopFadeSplitAnimationEndOffset(), + 0f, + dismissTranslationInterpolationEnd); + } else { + animationStartProgress = Utilities.boundToRange( + slidingStartOffset, + 0f, + dismissTranslationInterpolationEnd); + } + } else { + animationStartProgress = Utilities.boundToRange( + INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + * staggerColumn, 0f, dismissTranslationInterpolationEnd); + } + + final float animationEndProgress; + if (isSlidingTasks && taskView != nextFocusedTaskView) { + animationEndProgress = Utilities.boundToRange( + splitTimings.getGridSlideStartOffset() + + (splitTimings.getGridSlideStaggerOffset() * staggerColumn) + + splitTimings.getGridSlideDurationOffset(), + 0f, + dismissTranslationInterpolationEnd); + } else { + animationEndProgress = dismissTranslationInterpolationEnd; + } + Interpolator dismissInterpolator = isSlidingTasks ? EMPHASIZED : LINEAR; + + float primaryTranslation = 0; 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.DISMISS_SCALE, scale, clampToProgress(LINEAR, animationStartProgress, dismissTranslationInterpolationEnd)); - anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(), - mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth, - clampToProgress(LINEAR, animationStartProgress, - dismissTranslationInterpolationEnd)); + primaryTranslation += dismissedTaskWidth; float secondaryTranslation = -mTaskGridVerticalDiff; if (!nextFocusedTaskFromTop) { secondaryTranslation -= mTopBottomRowHeightDiff; @@ -3813,27 +4137,45 @@ public void onAnimationEnd(Animator animation) { anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(), secondaryTranslation, clampToProgress(LINEAR, animationStartProgress, dismissTranslationInterpolationEnd)); - anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(), + anim.add(taskView.getDismissIconFadeOutAnimator(), clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT)); - } else { - float primaryTranslation = + } else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow( + taskView, nextFocusedTaskView)) + || (!isFocusedTaskDismissed && i >= dismissedIndex && isSameGridRow( + taskView, dismissedTaskView))) { + primaryTranslation += nextFocusedTaskView != null ? nextFocusedTaskWidth : dismissedTaskWidth; - if (isStagingFocusedTask) { - // Moves less if focused task is not in scroll position. - int focusedTaskScroll = getScrollForPage(dismissedIndex); - int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); - int focusedTaskScrollDiff = primaryScroll - focusedTaskScroll; - primaryTranslation += - mIsRtl ? focusedTaskScrollDiff : -focusedTaskScrollDiff; - } + } + if (!(taskView instanceof DesktopTaskView)) { + primaryTranslation += mIsRtl ? slidingTranslation : -slidingTranslation; + } - anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(), - mIsRtl ? primaryTranslation : -primaryTranslation, - clampToProgress(dismissInterpolator, animationStartProgress, - animationEndProgress)); + if (primaryTranslation != 0) { + float finalTranslation = mIsRtl ? primaryTranslation : -primaryTranslation; + float startTranslation = 0; + if (!(taskView instanceof DesktopTaskView) && slidingTranslation != 0) { + startTranslation = isTaskViewVisible(taskView) ? 0 + : finalTranslation + (mIsRtl ? -mLastComputedTaskSize.right + : mLastComputedTaskSize.right); + } + // Expressive dismiss will animate the translations of taskViews itself. + if (!isExpressiveDismiss) { + Animator dismissAnimator = ObjectAnimator.ofFloat(taskView, + taskView.getPrimaryDismissTranslationProperty(), + startTranslation, finalTranslation); + dismissAnimator.setInterpolator( + clampToProgress(dismissInterpolator, animationStartProgress, + animationEndProgress)); + anim.add(dismissAnimator); + } + mTaskViewsDismissPrimaryTranslations.put(taskView, (int) finalTranslation); + distanceFromDismissedTask++; } } } + if (dismissingForSplitSelection) { + createInitialSplitSelectAnimation(anim); + } if (needsCurveUpdates) { anim.addOnFrameCallback(this::updateCurveProperties); @@ -3842,21 +4184,26 @@ secondaryTranslation, clampToProgress(LINEAR, animationStartProgress, // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we // want the dragged task to stay above all other views. - if (animateTaskView) { + if (animateTaskView && dismissedTaskView != null) { dismissedTaskView.setTranslationZ(0.1f); } - + loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); + if (!dismissingForSplitSelection) { + anim.addStartListener(() -> InteractionJankMonitorWrapper.begin(this, + Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS)); + } mPendingAnimation = anim; final TaskView finalNextFocusedTaskView = nextFocusedTaskView; final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll; final boolean finalSnapToLastTask = snapToLastTask; final boolean finalIsFocusedTaskDismissed = isFocusedTaskDismissed; - mPendingAnimation.addEndListener(new Consumer() { + mPendingAnimation.addEndListener(new Consumer<>() { @Override public void accept(Boolean success) { - if (mEnableDrawingLiveTile && dismissedTaskView.isRunningTask() && success) { + if (mEnableDrawingLiveTile && dismissedTaskView != null + && dismissedTaskView.isRunningTask() && success) { finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, - () -> onEnd(success)); + () -> onEnd(true)); } else { onEnd(success); } @@ -3869,17 +4216,16 @@ private void onEnd(boolean success) { resetTaskVisuals(); if (success) { - if (shouldRemoveTask) { + mAnyTaskHasBeenDismissed = true; + if (shouldRemoveTask && dismissedTaskView != null) { if (dismissedTaskView.isRunningTask()) { finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, - () -> removeTaskInternal(dismissedTaskViewId)); + () -> removeTaskInternal(dismissedTaskView)); } else { - removeTaskInternal(dismissedTaskViewId); + removeTaskInternal(dismissedTaskView); } - announceForAccessibility( - getResources().getString(R.string.task_view_closed)); mContainer.getStatsLogManager().logger() - .withItemInfo(dismissedTaskView.getFirstItemInfo()) + .withItemInfo(dismissedTaskView.getItemInfo()) .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); } @@ -3895,7 +4241,7 @@ private void onEnd(boolean success) { pageToSnapTo = indexOfChild(mClearAllButton); } else if (isClearAllHidden) { // Snap to focused task if clear all is hidden. - pageToSnapTo = 0; + pageToSnapTo = indexOfChild(getFirstTaskView()); } } else { // Get the id of the task view we will snap to based on the current @@ -3913,7 +4259,7 @@ private void onEnd(boolean success) { } else { // Won't focus next task in split select, so snap to the // first task. - pageToSnapTo = 0; + pageToSnapTo = indexOfChild(getFirstTaskView()); calculateScrollDiff = false; } } else { @@ -3921,8 +4267,8 @@ private void onEnd(boolean success) { boolean isSnappedTaskInTopRow = mTopRowIdSet.contains( snappedTaskViewId); IntArray taskViewIdArray = - isSnappedTaskInTopRow ? getTopRowIdArray() - : getBottomRowIdArray(); + isSnappedTaskInTopRow ? mUtils.getTopRowIdArray() + : mUtils.getBottomRowIdArray(); int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId); taskViewIdArray.removeValue(dismissedTaskViewId); if (finalNextFocusedTaskView != null) { @@ -3937,8 +4283,8 @@ private void onEnd(boolean success) { // dismissed row, // snap to the same column in the other grid row IntArray inverseRowTaskViewIdArray = - isSnappedTaskInTopRow ? getBottomRowIdArray() - : getTopRowIdArray(); + isSnappedTaskInTopRow ? mUtils.getBottomRowIdArray() + : mUtils.getTopRowIdArray(); if (snappedIndex < inverseRowTaskViewIdArray.size()) { taskViewIdToSnapTo = inverseRowTaskViewIdArray.get( snappedIndex); @@ -3954,7 +4300,7 @@ private void onEnd(boolean success) { mCurrentPageScrollDiff = primaryScroll - currentPageScroll; } } - } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) { + } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == lastTaskViewIndex) { pageToSnapTo--; } boolean isHomeTaskDismissed = dismissedTaskView == getHomeTaskView(); @@ -3963,6 +4309,7 @@ private void onEnd(boolean success) { if (taskCount == 1) { removeViewInLayout(mClearAllButton); + removeViewInLayout(mAddDesktopButton); if (isHomeTaskDismissed) { updateEmptyMessage(); } else if (!mSplitSelectStateController.isSplitSelectActive()) { @@ -3971,28 +4318,28 @@ private void onEnd(boolean success) { } else { // Update focus task and its size. if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) { - mFocusedTaskViewId = enableGridOnlyOverview() + setFocusedTaskViewId(enableGridOnlyOverview() ? INVALID_TASK_ID - : finalNextFocusedTaskView.getTaskViewId(); + : finalNextFocusedTaskView.getTaskViewId()); mTopRowIdSet.remove(mFocusedTaskViewId); - finalNextFocusedTaskView.animateIconScaleAndDimIntoView(); + finalNextFocusedTaskView.getDismissIconFadeInAnimator().start(); } - updateTaskSize(/*isTaskDismissal=*/ true); - updateChildTaskOrientations(); + updateTaskSize(); + mUtils.updateChildTaskOrientations(); // Update scroll and snap to page. updateScrollSynchronously(); if (showAsGrid) { // Rebalance tasks in the grid - int highestVisibleTaskIndex = getHighestVisibleTaskIndex(); - if (highestVisibleTaskIndex < Integer.MAX_VALUE) { - TaskView taskView = requireTaskViewAt(highestVisibleTaskIndex); - + TaskView highestVisibleTaskView = getHighestVisibleTaskView(); + if (highestVisibleTaskView != null) { boolean shouldRebalance; int screenStart = getPagedOrientationHandler().getPrimaryScroll( RecentsView.this); - int taskStart = getPagedOrientationHandler().getChildStart(taskView) - + (int) taskView.getOffsetAdjustment(/*gridEnabled=*/ true); + int taskStart = getPagedOrientationHandler().getChildStart( + highestVisibleTaskView) + + (int) highestVisibleTaskView.getOffsetAdjustment( + /*gridEnabled=*/true); // Rebalance only if there is a maximum gap between the task and the // screen's edge; this ensures that rebalanced tasks are outside the @@ -4005,26 +4352,33 @@ private void onEnd(boolean success) { RecentsView.this); int taskSize = (int) ( getPagedOrientationHandler().getMeasuredSize( - taskView) * taskView - .getSizeAdjustment(/*fullscreenEnabled=*/false)); + highestVisibleTaskView) * highestVisibleTaskView + .getSizeAdjustment(/*fullscreenEnabled=*/ + false)); int taskEnd = taskStart + taskSize; shouldRebalance = taskEnd >= screenEnd - mPageSpacing; } if (shouldRebalance) { - updateGridProperties(/*isTaskDismissal=*/ true, - highestVisibleTaskIndex); + updateGridProperties(highestVisibleTaskView); updateScrollSynchronously(); } } - IntArray topRowIdArray = getTopRowIdArray(); - IntArray bottomRowIdArray = getBottomRowIdArray(); + IntArray topRowIdArray = mUtils.getTopRowIdArray(); + IntArray bottomRowIdArray = mUtils.getBottomRowIdArray(); if (finalSnapToLastTask) { // If snapping to last task, find the last task after dismissal. pageToSnapTo = indexOfChild( getLastGridTaskView(topRowIdArray, bottomRowIdArray)); + + if (pageToSnapTo == INVALID_PAGE) { + // Snap to latest large tile page after dismissing the + // last grid task. This will prevent snapping to page 0 when + // desktop task is visible as large tile. + pageToSnapTo = indexOfChild(mUtils.getLastLargeTaskView()); + } } else if (taskViewIdToSnapTo != -1) { // If snapping to another page due to indices rearranging, find // the new index after dismissal & rearrange using the task view id. @@ -4053,10 +4407,105 @@ private void onEnd(boolean success) { updateCurrentTaskActionsVisibility(); onDismissAnimationEnds(); mPendingAnimation = null; + mTaskViewsDismissPrimaryTranslations.clear(); + + if (!dismissingForSplitSelection && success) { + InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS); + } else if (!dismissingForSplitSelection) { + InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS); + } } }); } + /** + * Compute scroll offsets from task dismissal for animation. + * If we just take newScroll - oldScroll, everything to the right of dragged task + * translates to the left. We need to offset this in some cases: + * - In RTL, add page offset to all pages, since we want pages to move to the right + * Additionally, add a page offset if: + * - Current page is rightmost page (leftmost for RTL) + * - Dragging an adjacent page on the left side (right side for RTL) + */ + private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, + int lastTaskViewIndex) { + // If `mCurrentPage` is beyond `lastTaskViewIndex`, use the last TaskView instead to + // calculate offset. + int currentPage = Math.min(mCurrentPage, lastTaskViewIndex); + int offset = mIsRtl ? scrollDiffPerPage : 0; + if (currentPage == dismissedIndex) { + if (currentPage == lastTaskViewIndex) { + offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; + } + } else { + // Dismissing an adjacent page. + int negativeAdjacent = currentPage - 1; // (Right in RTL, left in LTR) + if (dismissedIndex == negativeAdjacent) { + offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; + } + } + return offset; + } + + private void translateTaskWhenDismissed( + View view, + int indexDiff, + int scrollDiffPerPage, + PendingAnimation pendingAnimation, + SplitAnimationTimings splitTimings) { + // No need to translate the AddDesktopButton on dismissing a TaskView, which should be + // always at the right most position, even when dismissing the last TaskView. + if (view instanceof AddDesktopButton) { + return; + } + FloatProperty translationProperty = view instanceof TaskView + ? ((TaskView) view).getPrimaryDismissTranslationProperty() + : getPagedOrientationHandler().getPrimaryViewTranslate(); + + float additionalDismissDuration = + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * indexDiff; + + // We are in non-grid layout. + // If dismissing for split select, use split timings. + // If not, use dismiss timings. + float animationStartProgress = isSplitSelectionActive() + ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f) + : Utilities.boundToRange( + INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + + additionalDismissDuration, 0f, 1f); + + float animationEndProgress = isSplitSelectionActive() + ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset() + + splitTimings.getGridSlideDurationOffset(), 0f, 1f) + : 1f; + + // Slide tiles in horizontally to fill dismissed area + pendingAnimation.setFloat( + view, + translationProperty, + scrollDiffPerPage, + clampToProgress( + splitTimings.getGridSlidePrimaryInterpolator(), + animationStartProgress, + animationEndProgress + ) + ); + if (mEnableDrawingLiveTile && view instanceof TaskView + && ((TaskView) view).isRunningTask()) { + pendingAnimation.addOnFrameCallback(() -> { + runActionOnRemoteHandles( + remoteTargetHandle -> + remoteTargetHandle.getTaskViewSimulator() + .taskPrimaryTranslation.value = + getPagedOrientationHandler().getPrimaryValue( + view.getTranslationX(), + view.getTranslationY() + )); + redrawLiveTile(); + }); + } + } + /** * Hides all overview actions if user is halfway through split selection, shows otherwise. * We only show split option if: @@ -4068,9 +4517,6 @@ private void updateCurrentTaskActionsVisibility() { 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. @@ -4087,55 +4533,18 @@ public boolean supportsAppPairs() { return true; } - /** - * Returns all the tasks in the top row, without the focused task - */ - private IntArray getTopRowIdArray() { - if (mTopRowIdSet.isEmpty()) { - return new IntArray(0); - } - IntArray topArray = new IntArray(mTopRowIdSet.size()); - int taskViewCount = getTaskViewCount(); - for (int i = 0; i < taskViewCount; i++) { - int taskViewId = requireTaskViewAt(i).getTaskViewId(); - if (mTopRowIdSet.contains(taskViewId)) { - topArray.add(taskViewId); - } - } - return topArray; - } - - /** - * Returns all the tasks in the bottom row, without the focused task - */ - private IntArray getBottomRowIdArray() { - int bottomRowIdArraySize = getBottomRowTaskCountForTablet(); - if (bottomRowIdArraySize <= 0) { - return new IntArray(0); - } - IntArray bottomArray = new IntArray(bottomRowIdArraySize); - int taskViewCount = getTaskViewCount(); - for (int i = 0; i < taskViewCount; i++) { - int taskViewId = requireTaskViewAt(i).getTaskViewId(); - if (!mTopRowIdSet.contains(taskViewId) && taskViewId != mFocusedTaskViewId) { - bottomArray.add(taskViewId); - } - } - return bottomArray; - } - /** * Iterate the grid by columns instead of by TaskView index, starting after the focused task and * up to the last balanced column. * - * @return the highest visible TaskView index between both rows + * @return the highest visible TaskView between both rows */ - private int getHighestVisibleTaskIndex() { - if (mTopRowIdSet.isEmpty()) return Integer.MAX_VALUE; // return earlier + private TaskView getHighestVisibleTaskView() { + if (mTopRowIdSet.isEmpty()) return null; // return earlier - int lastVisibleIndex = Integer.MAX_VALUE; - IntArray topRowIdArray = getTopRowIdArray(); - IntArray bottomRowIdArray = getBottomRowIdArray(); + TaskView lastVisibleTaskView = null; + IntArray topRowIdArray = mUtils.getTopRowIdArray(); + IntArray bottomRowIdArray = mUtils.getBottomRowIdArray(); int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size()); for (int i = 0; i < balancedColumns; i++) { @@ -4143,25 +4552,42 @@ private int getHighestVisibleTaskIndex() { if (isTaskViewVisible(topTask)) { TaskView bottomTask = getTaskViewFromTaskViewId(bottomRowIdArray.get(i)); - lastVisibleIndex = Math.max(indexOfChild(topTask), indexOfChild(bottomTask)); - } else if (lastVisibleIndex < Integer.MAX_VALUE) { + lastVisibleTaskView = + indexOfChild(topTask) > indexOfChild(bottomTask) ? topTask : bottomTask; + } else if (lastVisibleTaskView != null) { break; } } - return lastVisibleIndex; + return lastVisibleTaskView; } - private void removeTaskInternal(int dismissedTaskViewId) { - int[] taskIds = getTaskIdsForTaskViewId(dismissedTaskViewId); - UI_HELPER_EXECUTOR.getHandler().post( - () -> { - for (int taskId : taskIds) { - if (taskId != -1) { - ActivityManagerWrapper.getInstance().removeTask(taskId); - } - } - }); + private void removeTaskInternal(@NonNull TaskView dismissedTaskView) { + UI_HELPER_EXECUTOR + .getHandler() + .post( + () -> { + if (dismissedTaskView instanceof DesktopTaskView desktopTaskView) { + removeDesktopTaskView(desktopTaskView); + } else { + for (int taskId : dismissedTaskView.getTaskIds()) { + ActivityManagerWrapper.getInstance().removeTask(taskId); + } + } + }); + } + + private void removeDesktopTaskView(DesktopTaskView desktopTaskView) { + if (areMultiDesksFlagsEnabled()) { + SystemUiProxy.INSTANCE + .get(getContext()) + .removeDesk(desktopTaskView.getDeskId()); + } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { + SystemUiProxy.INSTANCE + .get(getContext()) + .removeDefaultDeskInDisplay( + mContainer.getDisplay().getDisplayId()); + } } protected void onDismissAnimationEnds() { @@ -4175,19 +4601,24 @@ public PendingAnimation createAllTasksDismissAnimation(long duration) { } PendingAnimation anim = new PendingAnimation(duration); - int count = getTaskViewCount(); - for (int i = 0; i < count; i++) { - addDismissedTaskAnimations(requireTaskViewAt(i), duration, anim); + for (TaskView taskView : getTaskViews()) { + addDismissedTaskAnimations(taskView, duration, anim); } mPendingAnimation = anim; mPendingAnimation.addEndListener(isSuccess -> { if (isSuccess) { + // Remove desktops first, since desks can be empty (so they have no recent tasks), + // and closing all tasks on a desk doesn't always necessarily mean that the desk + // will be removed. So, there are no guarantees that the below call to + // `ActivityManagerWrapper::removeAllRecentTasks()` will be enough. + SystemUiProxy.INSTANCE.get(getContext()).removeAllDesks(); + // Remove all the task views now finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> { UI_HELPER_EXECUTOR.getHandler().post( ActivityManagerWrapper.getInstance()::removeAllRecentTasks); - removeTasksViewsAndClearAllButton(); + removeAllTaskViews(); startHome(); }); } @@ -4197,7 +4628,7 @@ public PendingAnimation createAllTasksDismissAnimation(long duration) { } private boolean snapToPageRelative(int delta, boolean cycle, - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction) { + TaskGridNavHelper.TaskNavDirection direction) { // Set next page if scroll animation is still running, otherwise cannot snap to the // next page on successive key presses. Setting the current page aborts the scroll. if (!mScroller.isFinished()) { @@ -4216,32 +4647,41 @@ private boolean snapToPageRelative(int delta, boolean cycle, return true; } - private int getNextPageInternal(int delta, @TaskGridNavHelper.TASK_NAV_DIRECTION int direction, + private int getNextPageInternal(int delta, TaskGridNavHelper.TaskNavDirection direction, boolean cycle) { if (!showAsGrid()) { return getNextPage() + delta; } // Init task grid nav helper with top/bottom id arrays. - TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(), - getBottomRowIdArray(), mFocusedTaskViewId); + TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(mUtils.getTopRowIdArray(), + mUtils.getBottomRowIdArray(), mUtils.getLargeTaskViewIds(), + mAddDesktopButton != null); // Get current page's task view ID. TaskView currentPageTaskView = getCurrentPageTaskView(); int currentPageTaskViewId; + final int clearAllButtonIndex = indexOfChild(mClearAllButton); + final int addDesktopButtonIndex = indexOfChild(mAddDesktopButton); if (currentPageTaskView != null) { currentPageTaskViewId = currentPageTaskView.getTaskViewId(); - } else if (mCurrentPage == indexOfChild(mClearAllButton)) { + } else if (mCurrentPage == clearAllButtonIndex) { currentPageTaskViewId = TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID; + } else if (mCurrentPage == addDesktopButtonIndex) { + currentPageTaskViewId = TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID; } else { return INVALID_PAGE; } - int nextGridPage = + final int nextGridPage = taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - return nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID - ? indexOfChild(mClearAllButton) - : indexOfChild(getTaskViewFromTaskViewId(nextGridPage)); + if (nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID) { + return clearAllButtonIndex; + } + if (nextGridPage == TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID) { + return addDesktopButtonIndex; + } + return indexOfChild(getTaskViewFromTaskViewId(nextGridPage)); } private void runDismissAnimation(PendingAnimation pendingAnim) { @@ -4252,18 +4692,39 @@ private void runDismissAnimation(PendingAnimation pendingAnim) { } @UiThread - private void dismissTask(int taskId) { + public void dismissTask(int taskId, boolean animate, boolean removeTask) { TaskView taskView = getTaskViewByTaskId(taskId); if (taskView == null) { + Log.d(TAG, "dismissTask: " + taskId + ", no associated TaskView"); return; } - dismissTask(taskView, true /* animate */, false /* removeTask */); + Log.d(TAG, "dismissTask: " + taskId); + + if (enableDesktopExplodedView() && taskView instanceof DesktopTaskView desktopTaskView) { + desktopTaskView.removeTaskFromExplodedView(taskId, animate); + + if (removeTask) { + ActivityManagerWrapper.getInstance().removeTask(taskId); + } + } else { + dismissTaskView(taskView, animate, removeTask); + } } - public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) { + /** Dismisses the entire [taskView]. */ + public void dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask) { PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION); createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION, - false /* dismissingForSplitSelection*/); + false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); + runDismissAnimation(pa); + } + + protected void expressiveDismissTaskView(TaskView taskView, Function0 onEndRunnable) { + PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION); + createTaskDismissAnimation(pa, taskView, false /* animateTaskView */, true /* removeTask */, + DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/, + true /* isExpressiveDismiss */); + pa.addEndListener((success) -> onEndRunnable.invoke()); runDismissAnimation(pa); } @@ -4276,27 +4737,42 @@ private void dismissAllTasks(View view) { private void dismissCurrentTask() { TaskView taskView = getNextPageTaskView(); if (taskView != null) { - dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/); + dismissTaskView(taskView, true /*animateTaskView*/, true /*removeTask*/); } } + private void createDesk(View view) { + SystemUiProxy.INSTANCE + .get(getContext()) + .createDesk(mContainer.getDisplay().getDisplayId()); + } + @Override public boolean dispatchKeyEvent(KeyEvent event) { if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) { return super.dispatchKeyEvent(event); } + + if (mUtils.shouldInterceptKeyEvent(event)) { + return super.dispatchKeyEvent(event); + } + switch (event.getKeyCode()) { case KeyEvent.KEYCODE_TAB: return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */, - DIRECTION_TAB); + TaskGridNavHelper.TaskNavDirection.TAB); case KeyEvent.KEYCODE_DPAD_RIGHT: - return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, DIRECTION_RIGHT); + return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, + TaskGridNavHelper.TaskNavDirection.RIGHT); case KeyEvent.KEYCODE_DPAD_LEFT: - return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, DIRECTION_LEFT); + return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, + TaskGridNavHelper.TaskNavDirection.LEFT); case KeyEvent.KEYCODE_DPAD_UP: - return snapToPageRelative(1, false /* cycle */, DIRECTION_UP); + return snapToPageRelative(1, false /* cycle */, + TaskGridNavHelper.TaskNavDirection.UP); case KeyEvent.KEYCODE_DPAD_DOWN: - return snapToPageRelative(1, false /* cycle */, DIRECTION_DOWN); + return snapToPageRelative(1, false /* cycle */, + TaskGridNavHelper.TaskNavDirection.DOWN); case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_FORWARD_DEL: dismissCurrentTask(); @@ -4340,15 +4816,14 @@ public void setContentAlpha(float alpha) { alpha = Utilities.boundToRange(alpha, 0, 1); mContentAlpha = alpha; - TaskView runningTaskView = getRunningTaskView(); - for (int i = getTaskViewCount() - 1; i >= 0; i--) { - TaskView child = requireTaskViewAt(i); - if (runningTaskView != null && mRunningTaskTileHidden && child == runningTaskView) { - continue; - } - child.setStableAlpha(alpha); + for (TaskView taskView : getTaskViews()) { + taskView.setStableAlpha(alpha); } mClearAllButton.setContentAlpha(mContentAlpha); + + if (mAddDesktopButton != null) { + mAddDesktopButton.setContentAlpha(mContentAlpha); + } int alphaInt = Math.round(alpha * 255); mEmptyMessagePaint.setAlpha(alphaInt); mEmptyIcon.setAlpha(alphaInt); @@ -4404,6 +4879,12 @@ public void updateRecentsRotation() { } } + public void reapplyActiveRotation() { + RotationTouchHelper rotationTouchHelper = RotationTouchHelper.INSTANCE.get(getContext()); + setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), + rotationTouchHelper.getDisplayRotation()); + } + public void setLayoutRotation(int touchRotation, int displayRotation) { if (mOrientationState.update(touchRotation, displayRotation)) { updateOrientationHandler(); @@ -4423,6 +4904,20 @@ public TaskView getNextTaskView() { return getTaskViewAt(getRunningTaskIndex() + 1); } + @Nullable + public TaskView getPreviousTaskView() { + return getTaskViewAt(getRunningTaskIndex() - 1); + } + + @Nullable + public TaskView getLastLargeTaskView() { + return mUtils.getLastLargeTaskView(); + } + + public int getLargeTilesCount() { + return mUtils.getLargeTileCount(); + } + @Nullable public TaskView getCurrentPageTaskView() { return getTaskViewAt(getCurrentPage()); @@ -4448,11 +4943,10 @@ public TaskView getTaskViewAt(int index) { } /** - * A version of {@link #getTaskViewAt} when the caller is sure about the input index. + * Returns iterable [TaskView] children. */ - @NonNull - private TaskView requireTaskViewAt(int index) { - return Objects.requireNonNull(getTaskViewAt(index)); + public RecentsViewUtils.TaskViewsIterable getTaskViews() { + return mUtils.getTaskViews(); } public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) { @@ -4460,13 +4954,14 @@ public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener liste } public void updateEmptyMessage() { - boolean isEmpty = getTaskViewCount() == 0; + boolean isEmpty = !hasTaskViews(); boolean hasSizeChanged = mLastMeasureSize.x != getWidth() || mLastMeasureSize.y != getHeight(); if (isEmpty == mShowEmptyMessage && !hasSizeChanged) { return; } setContentDescription(isEmpty ? mEmptyMessage : ""); + setFocusable(isEmpty); mShowEmptyMessage = isEmpty; updateEmptyStateUi(hasSizeChanged); invalidate(); @@ -4502,29 +4997,19 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto } private void updatePivots() { - if (mOverviewSelectEnabled) { - if (enableGridOnlyOverview()) { - getModalTaskSize(mTempRect); - Rect selectedTaskPosition = getSelectedTaskBounds(); - Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition, - mTempPointF); - } else { - mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom); - } + if (mOverviewSelectEnabled && !enableGridOnlyOverview()) { + mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom); } 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() && mContainer.getDeviceProfile().isTablet - && !mOverviewGridEnabled) { - mTempRect.set(mLastComputedCarouselTaskSize); - } else { - mTempRect.set(mLastComputedTaskSize); - } + mTempRect.set(mLastComputedTaskSize); getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect, mContainer.getDeviceProfile(), mTempPointF); } setPivotX(mTempPointF.x); setPivotY(mTempPointF.y); + if (enableGridOnlyOverview()) { + runActionOnRemoteHandles(remoteTargetHandle -> + remoteTargetHandle.getTaskViewSimulator().setPivotOverride(mTempPointF)); + } } /** @@ -4551,10 +5036,15 @@ private void updatePageOffsets() { ? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask)) : mOffsetMidpointIndexOverride; int modalMidpoint = getCurrentPage(); - boolean isModalGridWithoutFocusedTask = - showAsGrid && enableGridOnlyOverview() && mTaskModalness > 0; - if (isModalGridWithoutFocusedTask) { - modalMidpoint = indexOfChild(mSelectedTask); + TaskView carouselHiddenMidpointTask = runningTask != null ? runningTask + : mUtils.getFirstTaskViewInCarousel(/*nonRunningTaskCarouselHidden=*/true, + /*runningTaskView=*/null); + int carouselHiddenMidpoint = indexOfChild(carouselHiddenMidpointTask); + boolean shouldCalculateOffsetForAllTasks = showAsGrid + && (enableGridOnlyOverview() || enableLargeDesktopWindowingTile()) + && mTaskModalness > 0; + if (shouldCalculateOffsetForAllTasks) { + modalMidpoint = indexOfChild(getSelectedTaskView()); } float midpointOffsetSize = 0; @@ -4569,6 +5059,7 @@ private void updatePageOffsets() { float modalLeftOffsetSize = 0; float modalRightOffsetSize = 0; float gridOffsetSize = 0; + float carouselHiddenOffsetSize = 0; if (showAsGrid) { // In grid, we only focus the task on the side. The reference index used for offset @@ -4586,27 +5077,52 @@ private void updatePageOffsets() { : 0; } + int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight()); + float maxOverscroll = primarySize * OverScroll.OVERSCROLL_DAMP_FACTOR; for (int i = 0; i < count; i++) { + View child = getChildAt(i); float translation = i == midpoint ? midpointOffsetSize : i < midpoint ? leftOffsetSize : rightOffsetSize; - if (isModalGridWithoutFocusedTask) { + if (shouldCalculateOffsetForAllTasks) { gridOffsetSize = getHorizontalOffsetSize(i, modalMidpoint, modalOffset); gridOffsetSize = Math.abs(gridOffsetSize) * (i <= modalMidpoint ? 1 : -1); } + if (enableLargeDesktopWindowingTile()) { + if (child instanceof TaskView + && !mUtils.isVisibleInCarousel((TaskView) child, + runningTask, /*nonRunningTaskCarouselHidden=*/true)) { + // Increment carouselHiddenOffsetSize by maxOverscroll so it won't be on screen + // even when user overscroll. + carouselHiddenOffsetSize = (Math.abs(getMaxHorizontalOffsetSize(i, + carouselHiddenMidpoint)) + maxOverscroll) + * mDesktopCarouselDetachProgress; + carouselHiddenOffsetSize = carouselHiddenOffsetSize * ( + i <= carouselHiddenMidpoint ? 1 : -1); + } else { + carouselHiddenOffsetSize = 0; + } + } float modalTranslation = i == modalMidpoint ? modalMidpointOffsetSize : showAsGrid ? gridOffsetSize : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize; - float totalTranslationX = translation + modalTranslation; - View child = getChildAt(i); - FloatProperty translationPropertyX = child instanceof TaskView - ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty() - : getPagedOrientationHandler().getPrimaryViewTranslate(); - translationPropertyX.set(child, totalTranslationX); + boolean skipTranslationOffset = enableDesktopTaskAlphaAnimation() + && i == getRunningTaskIndex() + && child instanceof DesktopTaskView; + float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation + + carouselHiddenOffsetSize; + if (child instanceof TaskView taskView) { + taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX); + } else if (child instanceof ClearAllButton) { + getPagedOrientationHandler().getPrimaryViewTranslate().set(child, + totalTranslationX); + } else if (child instanceof AddDesktopButton addDesktopButton) { + addDesktopButton.setOffsetTranslationX(totalTranslationX); + } if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) { runActionOnRemoteHandles( remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() @@ -4614,11 +5130,11 @@ private void updatePageOffsets() { redrawLiveTile(); } - if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView) { - float totalTranslationY = getVerticalOffsetSize(i, modalOffset); - FloatProperty translationPropertyY = - ((TaskView) child).getSecondaryTaskOffsetTranslationProperty(); - translationPropertyY.set(child, totalTranslationY); + if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView taskView) { + float totalTranslationY = getVerticalOffsetSize(taskView, modalOffset); + FloatProperty translationPropertyY = + taskView.getSecondaryTaskOffsetTranslationProperty(); + translationPropertyY.set(taskView, totalTranslationY); } } updateCurveProperties(); @@ -4650,6 +5166,7 @@ private void getPersistentChildPosition(int childIndex, int midPointScroll, Rect /** * Computes the distance to offset the given child such that it is completely offscreen when * translating away from the given midpoint. + * * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. */ private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) { @@ -4658,6 +5175,14 @@ private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float o return 0; } + return getMaxHorizontalOffsetSize(childIndex, midpointIndex) * offsetProgress; + } + + /** + * Computes the distance to offset the given child such that it is completely offscreen when + * translating away from the given midpoint. + */ + private float getMaxHorizontalOffsetSize(int childIndex, int midpointIndex) { // First, get the position of the task relative to the midpoint. If there is no midpoint // then we just use the normal (centered) task position. RectF taskPosition = mTempRectF; @@ -4717,7 +5242,7 @@ private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float o } distanceToOffscreen -= mLastComputedTaskEndPushOutDistance; } - return distanceToOffscreen * offsetProgress; + return distanceToOffscreen; } /** @@ -4725,19 +5250,18 @@ private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float o * * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. */ - private float getVerticalOffsetSize(int childIndex, float offsetProgress) { + private float getVerticalOffsetSize(TaskView taskView, float offsetProgress) { if (offsetProgress == 0 || !(showAsGrid() && enableGridOnlyOverview()) - || mSelectedTask == null) { + || getSelectedTaskView() == null) { // Don't bother calculating everything below if we won't offset vertically. return 0; } // First, get the position of the task relative to the top row. - TaskView child = getTaskViewAt(childIndex); - Rect taskPosition = getTaskBounds(child); + Rect taskPosition = getTaskBounds(taskView); - boolean isSelectedTaskTopRow = mTopRowIdSet.contains(mSelectedTask.getTaskViewId()); - boolean isChildTopRow = mTopRowIdSet.contains(child.getTaskViewId()); + boolean isSelectedTaskTopRow = mTopRowIdSet.contains(getSelectedTaskView().getTaskViewId()); + boolean isChildTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); // Whether the task should be shifted to the top. boolean isTopShift = !isSelectedTaskTopRow && isChildTopRow; boolean isBottomShift = isSelectedTaskTopRow && !isChildTopRow; @@ -4754,9 +5278,9 @@ private float getVerticalOffsetSize(int childIndex, float offsetProgress) { protected void setTaskViewsResistanceTranslation(float translation) { mTaskViewsSecondaryTranslation = translation; - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView task = requireTaskViewAt(i); - task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY()); + for (TaskView taskView : getTaskViews()) { + taskView.getTaskResistanceTranslationProperty().set(taskView, + translation / getScaleY()); } runActionOnRemoteHandles( remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() @@ -4764,23 +5288,21 @@ protected void setTaskViewsResistanceTranslation(float translation) { } private void updateTaskViewsSnapshotRadius() { - for (int i = 0; i < getTaskViewCount(); i++) { - requireTaskViewAt(i).updateSnapshotRadius(); + for (TaskView taskView : getTaskViews()) { + taskView.updateFullscreenParams(); } } protected void setTaskViewsPrimarySplitTranslation(float translation) { mTaskViewsPrimarySplitTranslation = translation; - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView task = requireTaskViewAt(i); - task.getPrimarySplitTranslationProperty().set(task, translation); + for (TaskView taskView : getTaskViews()) { + taskView.getPrimarySplitTranslationProperty().set(taskView, translation); } } protected void setTaskViewsSecondarySplitTranslation(float translation) { mTaskViewsSecondarySplitTranslation = translation; - for (int i = 0; i < getTaskViewCount(); i++) { - TaskView taskView = requireTaskViewAt(i); + for (TaskView taskView : getTaskViews()) { if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) { continue; } @@ -4792,8 +5314,8 @@ protected void setTaskViewsSecondarySplitTranslation(float translation) { * Resets the visuals when exit modal state. */ public void resetModalVisuals() { - if (mSelectedTask != null) { - mSelectedTask.taskContainers.forEach( + if (getSelectedTaskView() != null) { + getSelectedTaskView().taskContainers.forEach( taskContainer -> taskContainer.getOverlay().resetModalVisuals()); } } @@ -4802,22 +5324,23 @@ public void resetModalVisuals() { * Primarily used by overview actions to initiate split from focused task, logs the source * of split invocation as such. */ - public void initiateSplitSelect(TaskView taskView) { + public void initiateSplitSelect(TaskContainer taskContainer) { int defaultSplitPosition = getPagedOrientationHandler() .getDefaultSplitPosition(mContainer.getDeviceProfile()); - initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT); + initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT); } /** TODO(b/266477929): Consolidate this call w/ the one below */ - public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition, + public void initiateSplitSelect(TaskContainer taskContainer, + @StagePosition int stagePosition, StatsLogManager.EventEnum splitEvent) { + TaskView taskView = taskContainer.getTaskView(); mSplitHiddenTaskView = taskView; mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition, - taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id); + taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id); mSplitSelectStateController.setAnimateCurrentTaskDismissal( true /*animateCurrentTaskDismissal*/); mSplitHiddenTaskViewIndex = indexOfChild(taskView); - updateDesktopTaskVisibility(false /* visible */); } /** @@ -4831,20 +5354,67 @@ public void initiateSplitSelect(SplitSelectSource splitSelectSource) { mSplitHiddenTaskView = getTaskViewByTaskId(splitSelectSource.alreadyRunningTaskId); mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView); mSplitSelectStateController - .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal); + .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal + && mSplitHiddenTaskView != null + && !(mSplitHiddenTaskView instanceof DesktopTaskView)); // Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null && mSplitHiddenTaskView instanceof GroupedTaskView); mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent, - splitSelectSource.position.stagePosition, splitSelectSource.itemInfo, + splitSelectSource.position.stagePosition, splitSelectSource.getItemInfo(), splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId); - updateDesktopTaskVisibility(false /* visible */); } - private void updateDesktopTaskVisibility(boolean visible) { - if (mDesktopTaskView != null) { - mDesktopTaskView.setVisibility(visible ? VISIBLE : GONE); + /** + * Animate DesktopTaskView(s) to hide in split select + */ + public void handleDesktopTaskInSplitSelectState(PendingAnimation builder, + Interpolator deskTopFadeInterPolator) { + SplitAnimationTimings timings = AnimUtils.getDeviceOverviewToSplitTimings( + mContainer.getDeviceProfile().isTablet); + if (enableLargeDesktopWindowingTile()) { + getTaskViews().forEachWithIndexInParent((index, taskView) -> { + if (taskView instanceof DesktopTaskView) { + // Setting pivot to scale down from screen centre. + if (isTaskViewVisible(taskView)) { + float pivotX = 0f; + if (index < mCurrentPage) { + pivotX = mIsRtl ? taskView.getWidth() / 2f - mPageSpacing + - taskView.getWidth() + : taskView.getWidth() / 2f + mPageSpacing + taskView.getWidth(); + } else if (index == mCurrentPage) { + pivotX = taskView.getWidth() / 2f; + } else { + pivotX = mIsRtl ? taskView.getWidth() + mPageSpacing + + taskView.getWidth() + : taskView.getWidth() - mPageSpacing - taskView.getWidth(); + } + taskView.setPivotX(pivotX); + taskView.setPivotY(taskView.getHeight() / 2f); + builder.add(ObjectAnimator + .ofFloat(taskView, TaskView.DISMISS_SCALE, 0.95f), + clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f, + timings.getDesktopFadeSplitAnimationEndOffset())); + } + builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f, + clampToProgress(deskTopFadeInterPolator, 0f, + timings.getDesktopFadeSplitAnimationEndOffset())); + } + }); + } + } + + /** + * While exiting from split mode, show all existing DesktopTaskViews. + */ + public void resetDesktopTaskFromSplitSelectState() { + if (enableLargeDesktopWindowingTile()) { + for (TaskView taskView : getTaskViews()) { + if (taskView instanceof DesktopTaskView) { + taskView.setSplitAlpha(1f); + } + } } } @@ -4857,32 +5427,46 @@ public void createSplitSelectInitAnimation(PendingAnimation builder, int duratio boolean isInitiatingTaskViewSplitPair = mSplitSelectStateController.isDismissingFromSplitPair(); if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair - && mSplitHiddenTaskView instanceof GroupedTaskView) { + && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) { // Splitting from Overview for split pair task createInitialSplitSelectAnimation(builder); // Animate pair thumbnail into full thumbnail - boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0] + boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id == mSplitSelectStateController.getInitialTaskId(); - TaskContainer taskContainer = mSplitHiddenTaskView - .getTaskContainers().get(primaryTaskSelected ? 1 : 0); - TaskThumbnailViewDeprecated thumbnail = taskContainer.getThumbnailViewDeprecated(); + TaskContainer taskContainer = + primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer() + : groupedTaskView.getLeftTopTaskContainer(); mSplitSelectStateController.getSplitAnimationController() .addInitialSplitFromPair(taskContainer, builder, mContainer.getDeviceProfile(), - mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(), + mSplitHiddenTaskView.getLayoutParams().width, + mSplitHiddenTaskView.getLayoutParams().height, primaryTaskSelected); - builder.addOnFrameCallback(() ->{ - thumbnail.refreshSplashView(); - mSplitHiddenTaskView.updateSnapshotRadius(); + builder.addOnFrameCallback(() -> { + if (!enableRefactorTaskThumbnail()) { + taskContainer.getThumbnailViewDeprecated().refreshSplashView(); + } + mSplitHiddenTaskView.updateFullscreenParams(); }); } else if (isInitiatingSplitFromTaskView) { + if (Flags.enableHoverOfChildElementsInTaskview()) { + mSplitHiddenTaskView.setBorderEnabled(false); + } // Splitting from Overview for fullscreen task createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration, - true /* dismissingForSplitSelection*/); + true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); } else { // Splitting from Home - createInitialSplitSelectAnimation(builder); + TaskView currentPageTaskView = getTaskViewAt(mCurrentPage); + // When current page is a Desktop task it needs special handling to + // display correct animation in split mode + if (currentPageTaskView instanceof DesktopTaskView) { + createTaskDismissAnimation(builder, null, true, false, duration, + true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); + } else { + createInitialSplitSelectAnimation(builder); + } } } @@ -4893,17 +5477,21 @@ public void createSplitSelectInitAnimation(PendingAnimation builder, int duratio * @param containerTaskView If our second selected app is currently running in Recents, this is * the "container" TaskView from Recents. If we are starting a fresh * instance of the app from an Intent, this will be null. - * @param task The Task corresponding to our second selected app. If we are starting a fresh - * instance of the app from an Intent, this will be null. - * @param drawable The Drawable corresponding to our second selected app's icon. - * @param secondView The View representing the current space on the screen where the second app - * is (either the ThumbnailView or the tapped icon). - * @param intent If we are launching a fresh instance of the app, this is the Intent for it. If - * the second app is already running in Recents, this will be null. - * @param user If we are launching a fresh instance of the app, this is the UserHandle for it. - * If the second app is already running in Recents, this will be null. + * @param task The Task corresponding to our second selected app. If we are + * starting a fresh + * instance of the app from an Intent, this will be null. + * @param drawable The Drawable corresponding to our second selected app's icon. + * @param secondView The View representing the current space on the screen where the + * second app + * is (either the ThumbnailView or the tapped icon). + * @param intent If we are launching a fresh instance of the app, this is the Intent + * for it. If + * the second app is already running in Recents, this will be null. + * @param user If we are launching a fresh instance of the app, this is the + * UserHandle for it. + * If the second app is already running in Recents, this will be null. * @return true if waiting for confirmation of second app or if split animations are running, - * false otherwise + * false otherwise */ public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable, View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user, @@ -4972,12 +5560,8 @@ public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawabl pendingAnimation.addEndListener(aBoolean -> { mSplitSelectStateController.launchSplitTasks( aBoolean1 -> { - if (FeatureFlags.enableSplitContextually()) { - mSplitSelectStateController.resetState(); - } else { - resetFromSplitSelectionState(); - } InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER); + mSplitSelectStateController.resetState(); }); }); @@ -4991,7 +5575,7 @@ public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawabl "Second tile selected"); // Fade out all other views underneath placeholders - ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0); + ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA, 1, 0); pendingAnimation.add(tvFade, DECELERATE_2, SpringProperty.DEFAULT); pendingAnimation.buildAnim().start(); return true; @@ -4999,17 +5583,14 @@ public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawabl @SuppressLint("WrongCall") protected void resetFromSplitSelectionState() { - if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1 || - FeatureFlags.enableSplitContextually()) { - safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView()); - safeRemoveDragLayerView(mSecondFloatingTaskView); - safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); - safeRemoveDragLayerView(mSplitScrim); - mSecondFloatingTaskView = null; - mSplitSelectSource = null; - mSplitSelectStateController.getSplitAnimationController() - .removeSplitInstructionsView(mContainer); - } + safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView()); + safeRemoveDragLayerView(mSecondFloatingTaskView); + safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); + safeRemoveDragLayerView(mSplitScrim); + mSecondFloatingTaskView = null; + mSplitSelectSource = null; + mSplitSelectStateController.getSplitAnimationController() + .removeSplitInstructionsView(mContainer); if (mSecondSplitHiddenView != null) { mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID); @@ -5021,11 +5602,6 @@ protected void resetFromSplitSelectionState() { setTaskViewsPrimarySplitTranslation(0); setTaskViewsSecondarySplitTranslation(0); - if (!FeatureFlags.enableSplitContextually()) { - // When flag is on, this method gets called from resetState() call below, let's avoid - // infinite recursion today - mSplitSelectStateController.resetState(); - } if (mSplitHiddenTaskViewIndex == -1) { return; } @@ -5044,9 +5620,15 @@ protected void resetFromSplitSelectionState() { mSplitHiddenTaskViewIndex = -1; if (mSplitHiddenTaskView != null) { mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID); + // mSplitHiddenTaskView is set when split select animation starts. The TaskView is only + // removed when when the animation finishes. So in the case of overview being dismissed + // during the animation, we should not call clearAndRecycleTaskView() because it has + // not been removed yet. + if (mSplitHiddenTaskView.getParent() == null) { + clearAndRecycleTaskView(mSplitHiddenTaskView); + } mSplitHiddenTaskView = null; } - updateDesktopTaskVisibility(true /* visible */); } private void safeRemoveDragLayerView(@Nullable View viewToRemove) { @@ -5096,7 +5678,7 @@ protected void onRotateInSplitSelectionState() { firstFloatingTaskView.update(mTempRectF, /*progress=*/1f); RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler(); - Pair, FloatProperty> taskViewsFloat = + Pair>, FloatProperty>> taskViewsFloat = orientationHandler.getSplitSelectTaskOffset( TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION, mContainer.getDeviceProfile()); @@ -5118,15 +5700,8 @@ private void updateDeadZoneRects() { mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin); } - // Get the deadzone rect between the task views - mTaskViewDeadZoneRect.setEmpty(); - int count = getTaskViewCount(); - if (count > 0) { - final View taskView = requireTaskViewAt(0); - requireTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect); - mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(), - taskView.getBottom()); - } + mUtils.updateTaskViewDeadZoneRect(mTaskViewDeadZoneRect, mTopRowDeadZoneRect, + mBottomRowDeadZoneRect); } private void updateEmptyStateUi(boolean sizeChanged) { @@ -5139,7 +5714,7 @@ private void updateEmptyStateUi(boolean sizeChanged) { if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) { int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding; mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(), - mEmptyMessagePaint, availableWidth) + mEmptyMessagePaint, availableWidth) .setAlignment(Layout.Alignment.ALIGN_CENTER) .build(); int totalHeight = mEmptyTextLayout.getHeight() @@ -5181,23 +5756,56 @@ protected void maybeDrawEmptyMessage(Canvas canvas) { * to the right. */ @SuppressLint("Recycle") - public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) { + public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView taskView) { AnimatorSet anim = new AnimatorSet(); - int taskIndex = indexOfChild(tv); + int taskIndex = indexOfChild(taskView); int centerTaskIndex = getCurrentPage(); float toScale = getMaxScaleForFullScreen(); boolean showAsGrid = showAsGrid(); - boolean launchingCenterTask = showAsGrid - ? tv.isFocusedTask() && isTaskViewFullyVisible(tv) - : taskIndex == centerTaskIndex; - if (launchingCenterTask) { + boolean zoomInTaskView = showAsGrid ? taskView.isLargeTile() : taskIndex == centerTaskIndex; + if (zoomInTaskView) { anim.play(ObjectAnimator.ofFloat(this, RECENTS_SCALE_PROPERTY, toScale)); anim.play(ObjectAnimator.ofFloat(this, FULLSCREEN_PROGRESS, 1)); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(@NonNull Animator animation) { + taskView.getThumbnailBounds(mTempRect, /*relativeToDragLayer=*/true); + getTaskDimension(mContext, mContainer.getDeviceProfile(), mTempPointF); + Rect fullscreenBounds = new Rect(0, 0, (int) mTempPointF.x, + (int) mTempPointF.y); + Utilities.getPivotsForScalingRectToRect(mTempRect, fullscreenBounds, + mTempPointF); + setPivotX(mTempPointF.x); + setPivotY(mTempPointF.y); + + // If live tile is not launching, apply pivot to live tile as well and bring it + // above RecentsView to avoid wallpaper blur from being applied to it. + if (!taskView.isRunningTask()) { + runActionOnRemoteHandles( + remoteTargetHandle -> + remoteTargetHandle.getTaskViewSimulator() + .setPivotOverride(mTempPointF)); + mBlurUtils.setDrawLiveTileBelowRecents(false); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + // If live tile is not launching, reset the pivot applied above. + if (!taskView.isRunningTask()) { + runActionOnRemoteHandles( + remoteTargetHandle -> { + remoteTargetHandle.getTaskViewSimulator().setPivotOverride( + null); + }); + } + } + }); } else if (!showAsGrid) { // We are launching an adjacent task, so parallax the center and other adjacent task. - float displacementX = tv.getWidth() * (toScale - 1f); + float displacementX = taskView.getWidth() * (toScale - 1f); float primaryTranslation = mIsRtl ? -displacementX : displacementX; anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation)); @@ -5225,6 +5833,20 @@ && getRemoteTargetHandles() != null) { } } anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0, 1)); + if (taskView instanceof DesktopTaskView) { + anim.play(ObjectAnimator.ofArgb(mContainer.getScrimView(), VIEW_BACKGROUND_COLOR, + Color.TRANSPARENT)); + if (enableDesktopExplodedView()) { + anim.play(ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, 1f, 0f)); + } + } + DepthController depthController = getDepthController(); + if (depthController != null) { + float targetDepth = taskView instanceof DesktopTaskView ? 0 : BACKGROUND_APP.getDepth( + mContainer); + anim.play(ObjectAnimator.ofFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE, + targetDepth)); + } return anim; } @@ -5232,31 +5854,28 @@ && getRemoteTargetHandles() != null) { * Returns the scale up required on the view, so that it coves the screen completely */ public float getMaxScaleForFullScreen() { - if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet - && !mOverviewGridEnabled) { - if (mLastComputedCarouselTaskSize.isEmpty()) { - mSizeStrategy.calculateCarouselTaskSize(mContainer, mContainer.getDeviceProfile(), - mLastComputedCarouselTaskSize, getPagedOrientationHandler()); - } - mTempRect.set(mLastComputedCarouselTaskSize); - } else { - if (mLastComputedTaskSize.isEmpty()) { - getTaskSize(mLastComputedTaskSize); - } - mTempRect.set(mLastComputedTaskSize); + if (mLastComputedTaskSize.isEmpty()) { + getTaskSize(mLastComputedTaskSize); } + mTempRect.set(mLastComputedTaskSize); return getPagedViewOrientedState().getFullScreenScaleAndPivot( mTempRect, mContainer.getDeviceProfile(), mTempPointF); } + /** + * Clears the existing PendingAnimation. + */ + public void clearPendingAnimation() { + mPendingAnimation = null; + } + public PendingAnimation createTaskLaunchAnimation( - TaskView tv, long duration, Interpolator interpolator) { + TaskView taskView, long duration, Interpolator interpolator) { if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { throw new IllegalStateException("Another pending animation is still running"); } - int count = getTaskViewCount(); - if (count == 0) { + if (!hasTaskViews()) { return new PendingAnimation(duration); } @@ -5265,13 +5884,13 @@ public PendingAnimation createTaskLaunchAnimation( updateGridProperties(); updateScrollSynchronously(); - int targetSysUiFlags = tv.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags(); - final boolean[] passedOverviewThreshold = new boolean[] {false}; - ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1); - progressAnim.addUpdateListener(animator -> { + int targetSysUiFlags = taskView.getSysUiStatusNavFlags(); + final boolean[] passedOverviewThreshold = new boolean[]{false}; + AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(taskView); + anim.play(new AnimatedFloat(v -> { // Once we pass a certain threshold, update the sysui flags to match the target // tasks' flags - if (animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD) { + if (v > UPDATE_SYSUI_FLAGS_THRESHOLD) { mContainer.getSystemUiController().updateUiState( UI_STATE_FULLSCREEN_TASK, targetSysUiFlags); } else { @@ -5279,8 +5898,7 @@ public PendingAnimation createTaskLaunchAnimation( } // Passing the threshold from taskview to fullscreen app will vibrate - final boolean passed = animator.getAnimatedFraction() >= - SUCCESS_TRANSITION_PROGRESS; + final boolean passed = v >= SUCCESS_TRANSITION_PROGRESS; if (passed != passedOverviewThreshold[0]) { passedOverviewThreshold[0] = passed; performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, @@ -5290,30 +5908,26 @@ public PendingAnimation createTaskLaunchAnimation( mRecentsAnimationController.setWillFinishToHome(!passed); } } - }); - - AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv); - - DepthController depthController = getDepthController(); - if (depthController != null) { - ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController.stateDepth, - MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mContainer)); - anim.play(depthAnimator); - } - anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0f, 1f)); - - anim.play(progressAnim); + }).animateToValue(0f, 1f)); anim.setInterpolator(interpolator); mPendingAnimation = new PendingAnimation(duration); mPendingAnimation.add(anim); - runActionOnRemoteHandles( - remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() - .addOverviewToAppAnim(mPendingAnimation, interpolator)); - mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); + if (taskView.isRunningTask()) { + runActionOnRemoteHandles( + remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() + .addOverviewToAppAnim(mPendingAnimation, interpolator)); + mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); + } + mPendingAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBlurUtils.setDrawLiveTileBelowRecents(false); + } + }); mPendingAnimation.addEndListener(isSuccess -> { if (isSuccess) { - if (tv instanceof GroupedTaskView && hasAllValidTaskIds(tv.getTaskIds()) + if (taskView instanceof GroupedTaskView && hasAllValidTaskIds(taskView.getTaskIds()) && mRemoteTargetHandles != null) { // TODO(b/194414938): make this part of the animations instead. TaskViewUtils.createSplitAuxiliarySurfacesAnimator( @@ -5323,13 +5937,13 @@ public PendingAnimation createTaskLaunchAnimation( dividerAnimator.end(); }); } - if (tv.isRunningTask()) { + if (taskView.isRunningTask()) { finishRecentsAnimation(false /* toRecents */, null); onTaskLaunchAnimationEnd(true /* success */); } else { - tv.launchTask(this::onTaskLaunchAnimationEnd); + taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd); } - mContainer.getStatsLogManager().logger().withItemInfo(tv.getFirstItemInfo()) + mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo()) .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN); } else { onTaskLaunchAnimationEnd(false); @@ -5342,6 +5956,12 @@ public PendingAnimation createTaskLaunchAnimation( protected Unit onTaskLaunchAnimationEnd(boolean success) { if (success) { resetTaskVisuals(); + } else { + // If launch animation didn't complete i.e. user dragged live tile down and then + // back up and returned to Overview, then we need to ensure we reset the + // view to draw below recents so that it can't be interacted with. + mBlurUtils.setDrawLiveTileBelowRecents(true); + redrawLiveTile(); } return Unit.INSTANCE; } @@ -5352,6 +5972,9 @@ protected void notifyPageSwitchListener(int prevPage) { updateCurrentTaskActionsVisibility(); loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); updateEnabledOverlays(); + if (enableRefactorTaskThumbnail()) { + mUtils.updateCentralTask(); + } } @Override @@ -5361,34 +5984,33 @@ protected String getCurrentPageDescription() { @Override public void addChildrenForAccessibility(ArrayList outChildren) { - // Add children in reverse order - for (int i = getChildCount() - 1; i >= 0; --i) { - outChildren.add(getChildAt(i)); - } + outChildren.addAll(getAccessibilityChildren()); + } + + public List getAccessibilityChildren() { + return mUtils.getAccessibilityChildren(); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); final AccessibilityNodeInfo.CollectionInfo - collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( - 1, getTaskViewCount(), false, - AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE); + collectionInfo = new AccessibilityNodeInfo.CollectionInfo( + 1, getAccessibilityChildren().size(), false); info.setCollectionInfo(collectionInfo); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - - final int taskViewCount = getTaskViewCount(); - event.setScrollable(taskViewCount > 0); + event.setScrollable(hasTaskViews()); if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + final List accessibilityChildren = getAccessibilityChildren(); final int[] visibleTasks = getVisibleChildrenRange(); - event.setFromIndex(taskViewCount - visibleTasks[1]); - event.setToIndex(taskViewCount - visibleTasks[0]); - event.setItemCount(taskViewCount); + event.setFromIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[1]))); + event.setToIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[0]))); + event.setItemCount(accessibilityChildren.size()); } } @@ -5407,6 +6029,10 @@ public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) { mEnableDrawingLiveTile = enableDrawingLiveTile; } + public boolean getEnableDrawingLiveTile() { + return mEnableDrawingLiveTile; + } + public void redrawLiveTile() { runActionOnRemoteHandles(remoteTargetHandle -> { TransformParams params = remoteTargetHandle.getTransformParams(); @@ -5416,6 +6042,7 @@ public void redrawLiveTile() { }); } + @Nullable public RemoteTargetHandle[] getRemoteTargetHandles() { return mRemoteTargetHandles; } @@ -5433,10 +6060,11 @@ public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimati } RemoteTargetGluer gluer; - if (recentsAnimationTargets.hasDesktopTasks()) { + if (recentsAnimationTargets.hasDesktopTasks(mContext)) { gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets, true /* forDesktop */); - mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets); + mRemoteTargetHandles = gluer.assignTargetsForDesktop( + recentsAnimationTargets, /* transitionInfo= */ null); } else { gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets, false); @@ -5449,6 +6077,14 @@ public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimati // mSyncTransactionApplier doesn't get transferred over runActionOnRemoteHandles(remoteTargetHandle -> { final TransformParams params = remoteTargetHandle.getTransformParams(); + if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) { + params.setHomeBuilderProxy((builder, app, transformParams) -> { + mTmpMatrix.setScale( + 1f, 1f, app.localBounds.exactCenterX(), app.localBounds.exactCenterY()); + builder.setMatrix(mTmpMatrix).setAlpha(1f).setShow(); + }); + } + if (mSyncTransactionApplier != null) { params.setSyncTransactionApplier(mSyncTransactionApplier); params.getTargetSet().addReleaseCheck(mSyncTransactionApplier); @@ -5488,12 +6124,19 @@ public void finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinis finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete); } + /** + * Finish recents animation. + */ + public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, + @Nullable Runnable onFinishComplete) { + finishRecentsAnimation(toRecents, shouldPip, false, 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) { + boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) { Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: " + mRecentsAnimationController); // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe? @@ -5507,8 +6150,9 @@ public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, } final boolean sendUserLeaveHint = toRecents && shouldPip; - if (sendUserLeaveHint) { + if (sendUserLeaveHint && !com.android.wm.shell.Flags.enablePip2()) { // Notify the SysUI to use fade-in animation when entering PiP from live tile. + // Note: PiP2 handles entering differently, so skip if enable_pip2=true. final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext()); systemUiProxy.setPipAnimationTypeToAlpha(); systemUiProxy.setShelfHeight(true, mContainer.getDeviceProfile().hotseatBarSizePx); @@ -5525,7 +6169,7 @@ public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, tx, null /* overlay */); } } - mRecentsAnimationController.finish(toRecents, () -> { + mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> { if (onFinishComplete != null) { onFinishComplete.run(); } @@ -5552,6 +6196,9 @@ public void onRecentsAnimationComplete() { mRecentsAnimationController = null; mSplitSelectStateController.setRecentsAnimationRunning(false); executeSideTaskLaunchCallback(); + if (enableOverviewBackgroundWallpaperBlur()) { + mBlurUtils.setDrawLiveTileBelowRecents(false); + } } public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { @@ -5560,6 +6207,17 @@ public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { updateMinAndMaxScrollX(); } } + /** + * Update the value of [mDisallowScrollToAddDesk] + */ + public void setDisallowScrollToAddDesk(boolean disallowScrollToAddDesk) { + if (mDisallowScrollToAddDesk != disallowScrollToAddDesk) { + mDisallowScrollToAddDesk = disallowScrollToAddDesk; + updateMinAndMaxScrollX(); + } + } + + /** * Updates page scroll synchronously after measure and layout child views. @@ -5598,7 +6256,7 @@ protected void updateMinAndMaxScrollX() { @Override protected int computeMinScroll() { - if (getTaskViewCount() <= 0) { + if (!hasTaskViews()) { return super.computeMinScroll(); } @@ -5607,7 +6265,7 @@ protected int computeMinScroll() { @Override protected int computeMaxScroll() { - if (getTaskViewCount() <= 0) { + if (!hasTaskViews()) { return super.computeMaxScroll(); } @@ -5615,26 +6273,42 @@ protected int computeMaxScroll() { } private int getFirstViewIndex() { - TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null; - return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0; + final View firstView; + if (mShowAsGridLastOnLayout) { + // For grid Overview, it always start if a large tile (focused task or desktop task) if + // they exist, otherwise it start with the first task. + TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(); + if (firstLargeTaskView != null) { + firstView = firstLargeTaskView; + } else { + firstView = mUtils.getFirstSmallTaskView(); + } + } else { + firstView = mUtils.getFirstTaskViewInCarousel( + /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0); + } + return indexOfChild(firstView); } private int getLastViewIndex() { + final View lastView; if (!mDisallowScrollToClearAll) { - return indexOfChild(mClearAllButton); - } - - if (!mShowAsGridLastOnLayout) { - return getTaskViewCount() - 1; - } - - TaskView lastGridTaskView = getLastGridTaskView(); - if (lastGridTaskView != null) { - return indexOfChild(lastGridTaskView); + // When ClearAllButton is present, it always end with ClearAllButton. + lastView = mClearAllButton; + } else if (mShowAsGridLastOnLayout) { + // When ClearAllButton is absent, for the grid Overview, it always end with a grid task + // if they exist, otherwise it ends with a large tile (focused task or desktop task). + TaskView lastGridTaskView = getLastGridTaskView(); + if (lastGridTaskView != null) { + lastView = lastGridTaskView; + } else { + lastView = mUtils.getLastLargeTaskView(); + } + } else { + lastView = mUtils.getLastTaskViewInCarousel( + /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0); } - - // Returns focus task if there are no grid tasks. - return indexOfChild(getFocusedTaskView()); + return indexOfChild(lastView); } /** @@ -5660,42 +6334,55 @@ protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren, mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff); } - boolean pageScrollChanged = false; - + int[] oldPageScrolls = Arrays.copyOf(outPageScrolls, outPageScrolls.length); int clearAllIndex = indexOfChild(mClearAllButton); int clearAllScroll = 0; int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton); if (clearAllIndex != -1 && clearAllIndex < outPageScrolls.length) { float scrollDiff = mClearAllButton.getScrollAdjustment(showAsFullscreen, showAsGrid); - clearAllScroll = newPageScrolls[clearAllIndex] + (int) scrollDiff; - if (outPageScrolls[clearAllIndex] != clearAllScroll) { - pageScrollChanged = true; - outPageScrolls[clearAllIndex] = clearAllScroll; - } + clearAllScroll = newPageScrolls[clearAllIndex] + Math.round(scrollDiff); + outPageScrolls[clearAllIndex] = clearAllScroll; } - final int taskCount = getTaskViewCount(); int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth); - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); + getTaskViews().forEachWithIndexInParent((index, taskView) -> { float scrollDiff = taskView.getScrollAdjustment(showAsGrid); - int pageScroll = newPageScrolls[i] + Math.round(scrollDiff); + int pageScroll = newPageScrolls[index] + Math.round(scrollDiff); if ((mIsRtl && pageScroll < lastTaskScroll) || (!mIsRtl && pageScroll > lastTaskScroll)) { pageScroll = lastTaskScroll; } - if (outPageScrolls[i] != pageScroll) { - pageScrollChanged = true; - outPageScrolls[i] = pageScroll; + outPageScrolls[index] = pageScroll; + if (DEBUG) { + Log.d(TAG, + "getPageScrolls - outPageScrolls[" + index + "]: " + outPageScrolls[index]); + } + }); + + int addDesktopButtonIndex = indexOfChild(mAddDesktopButton); + if (addDesktopButtonIndex >= 0 && addDesktopButtonIndex < outPageScrolls.length) { + int firstViewIndex = getFirstViewIndex(); + if (firstViewIndex >= 0 && firstViewIndex < outPageScrolls.length) { + // If we can scroll to [AddDesktopButton], make its page scroll equal to + // the first [TaskView]. Otherwise, make its page scroll out of range of + // [minScroll, maxScroll]. + if (!mDisallowScrollToAddDesk) { + outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex]; + } else { + outPageScrolls[addDesktopButtonIndex] = + outPageScrolls[firstViewIndex] + (mIsRtl ? 1 : -1); + } } + if (DEBUG) { - Log.d(TAG, "getPageScrolls - outPageScrolls[" + i + "]: " + outPageScrolls[i]); + Log.d(TAG, "getPageScrolls - addDesktopButtonScroll: " + + outPageScrolls[addDesktopButtonIndex]); } } if (DEBUG) { Log.d(TAG, "getPageScrolls - clearAllScroll: " + clearAllScroll); } - return pageScrollChanged; + return !Arrays.equals(oldPageScrolls, outPageScrolls); } @Override @@ -5712,12 +6399,12 @@ protected int getChildOffset(int index) { } @Override - protected int getChildVisibleSize(int index) { - final TaskView taskView = getTaskViewAt(index); + protected int getChildVisibleSize(int childIndex) { + final TaskView taskView = getTaskViewAt(childIndex); if (taskView == null) { - return super.getChildVisibleSize(index); + return super.getChildVisibleSize(childIndex); } - return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment( + return (int) (super.getChildVisibleSize(childIndex) * taskView.getSizeAdjustment( showAsFullscreen())); } @@ -5725,6 +6412,11 @@ public ClearAllButton getClearAllButton() { return mClearAllButton; } + @Nullable + public AddDesktopButton getAddDeskButton() { + return mAddDesktopButton; + } + /** * @return How many pixels the running task is offset on the currently laid out dominant axis. */ @@ -5749,6 +6441,7 @@ public int getScrollOffsetForKeyboardTaskFocus() { * Sets whether or not we should clamp the scroll offset. * This is used to avoid x-axis movement when swiping up transient taskbar. * Should only be set at the beginning and end of the gesture, otherwise a jump may occur. + * * @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is * met. */ @@ -5775,17 +6468,17 @@ public int getScrollOffset(int pageIndex) { * Returns how many pixels the page is offset on the currently laid out dominant axis. */ private int getUnclampedScrollOffset(int pageIndex) { - if (pageIndex == -1) { + if (pageIndex == INVALID_PAGE) { return 0; } // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that // the page can move freely given there's no visual indication why it shouldn't. int overScrollShift = mAdjacentPageHorizontalOffset > 0 - ? (int) Utilities.mapRange( - mAdjacentPageHorizontalOffset, - getOverScrollShift(), - getUndampedOverScrollShift()) - : getOverScrollShift(); + ? (int) Utilities.mapRange( + mAdjacentPageHorizontalOffset, + getOverScrollShift(), + getUndampedOverScrollShift()) + : getOverScrollShift(); return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this) + overScrollShift + getOffsetFromScrollPosition(pageIndex); } @@ -5794,7 +6487,8 @@ private int getUnclampedScrollOffset(int pageIndex) { * Returns how many pixels the page is offset from its scroll position. */ private int getOffsetFromScrollPosition(int pageIndex) { - return getOffsetFromScrollPosition(pageIndex, getTopRowIdArray(), getBottomRowIdArray()); + return getOffsetFromScrollPosition(pageIndex, mUtils.getTopRowIdArray(), + mUtils.getBottomRowIdArray()); } private int getOffsetFromScrollPosition( @@ -5844,12 +6538,12 @@ private int getPositionInRow( } /** - * @return true if the task in on the top of the grid + * @return true if the task in on the bottom of the grid */ public boolean isOnGridBottomRow(TaskView taskView) { return showAsGrid() && !mTopRowIdSet.contains(taskView.getTaskViewId()) - && taskView.getTaskViewId() != mFocusedTaskViewId; + && !taskView.isLargeTile(); } public Consumer getEventDispatcher(float navbarRotation) { @@ -5882,19 +6576,27 @@ public Consumer getEventDispatcher(float navbarRotation) { } private void updateEnabledOverlays() { - TaskView focusedTaskView = getFocusedTaskView(); - int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - TaskView taskView = requireTaskViewAt(i); - if (taskView == focusedTaskView) { - continue; + if (enableRefactorTaskThumbnail()) { + Set fullyVisibleTaskIds = new HashSet<>(); + for (TaskView taskView : getTaskViews()) { + if (isTaskViewFullyVisible(taskView)) { + fullyVisibleTaskIds.addAll(taskView.getTaskIdSet()); + } + } + mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds); + } else { + TaskView focusedTaskView = getFocusedTaskView(); + for (TaskView taskView : getTaskViews()) { + if (taskView == focusedTaskView) { + continue; + } + taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView)); + } + // Focus task overlay should be enabled and refreshed at last + if (focusedTaskView != null) { + focusedTaskView.setOverlayEnabled( + mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView)); } - taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView)); - } - // Focus task overlay should be enabled and refreshed at last - if (focusedTaskView != null) { - focusedTaskView.setOverlayEnabled( - mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView)); } } @@ -5902,6 +6604,10 @@ public void setOverlayEnabled(boolean overlayEnabled) { if (mOverlayEnabled != overlayEnabled) { mOverlayEnabled = overlayEnabled; updateEnabledOverlays(); + + if (enableRefactorTaskThumbnail()) { + mRecentsViewModel.setOverlayEnabled(overlayEnabled); + } } } @@ -5949,32 +6655,19 @@ public void switchToScreenshot(Runnable onFinishRunnable) { return; } - switchToScreenshotInternal(onFinishRunnable); - } - - private void switchToScreenshotInternal(Runnable onFinishRunnable) { TaskView taskView = getRunningTaskView(); if (taskView == null) { onFinishRunnable.run(); return; } - setRunningTaskViewShowScreenshot(true); - for (TaskContainer container : taskView.getTaskContainers()) { - if (container == null) { - continue; - } - - ThumbnailData td = - mRecentsAnimationController.screenshotTask(container.getTask().key.id); - TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailViewDeprecated(); - if (td != null) { - thumbnailView.setThumbnail(container.getTask(), td); - } else { - thumbnailView.refresh(); - } + Map updatedThumbnails = mUtils.screenshotTasks(taskView); + if (enableRefactorTaskThumbnail()) { + mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable); + } else { + setRunningTaskViewShowScreenshot(true, updatedThumbnails); + ViewUtils.postFrameDrawn(taskView, onFinishRunnable); } - ViewUtils.postFrameDrawn(taskView, onFinishRunnable); } /** @@ -5988,9 +6681,12 @@ public void switchToScreenshot(@Nullable HashMap thumbna Runnable onFinishRunnable) { final TaskView taskView = getRunningTaskView(); if (taskView != null) { - taskView.setShouldShowScreenshot(true); - taskView.refreshThumbnails(thumbnailDatas); - ViewUtils.postFrameDrawn(taskView, onFinishRunnable); + if (enableRefactorTaskThumbnail()) { + mHelper.switchToScreenshot(taskView, thumbnailDatas, onFinishRunnable); + } else { + taskView.setShouldShowScreenshot(true, thumbnailDatas); + ViewUtils.postFrameDrawn(taskView, onFinishRunnable); + } } else { onFinishRunnable.run(); } @@ -6003,8 +6699,14 @@ public void switchToScreenshot(@Nullable HashMap thumbna private void setTaskModalness(float modalness) { mTaskModalness = modalness; updatePageOffsets(); - if (mSelectedTask != null) { - mSelectedTask.setModalness(modalness); + if (getSelectedTaskView() != null) { + if (enableGridOnlyOverview()) { + for (TaskView taskView : getTaskViews()) { + taskView.setModalness(modalness); + } + } else { + getSelectedTaskView().setModalness(modalness); + } } else if (getCurrentPageTaskView() != null) { getCurrentPageTaskView().setModalness(modalness); } @@ -6035,6 +6737,12 @@ public BaseContainerInterface getSizeStrategy() { return mSizeStrategy; } + + /** + * Returns the container interface + */ + protected abstract BaseContainerInterface getContainerInterface(int displayId); + /** * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the * tasks to be dimmed while other elements in the recents view are left alone. @@ -6055,12 +6763,11 @@ public void showForegroundScrim(boolean show) { } /** Tint the RecentsView and TaskViews in to simulate a scrim. */ - // TODO(b/187528071): Replace this tinting with a scrim on top of RecentsView private void setColorTint(float tintAmount) { mColorTint = tintAmount; - for (int i = 0; i < getTaskViewCount(); i++) { - requireTaskViewAt(i).setColorTint(mColorTint, mTintingColor); + for (TaskView taskView : getTaskViews()) { + taskView.setColorTint(mColorTint, mTintingColor); } Drawable scrimBg = mContainer.getScrimView().getBackground(); @@ -6087,10 +6794,10 @@ private float getColorTint() { public boolean showAsGrid() { return mOverviewGridEnabled || (mCurrentGestureEndTarget != null && mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget) - .displayOverviewTasksAsGrid(mContainer.getDeviceProfile())); + .displayOverviewTasksAsGrid(mContainer.getDeviceProfile())); } - private boolean showAsFullscreen() { + protected boolean showAsFullscreen() { return mOverviewFullscreenEnabled && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS; } @@ -6128,7 +6835,7 @@ public void removeOnScrollChangedListener(OnScrollChangedListener listener) { /** * @return Corner radius in pixel value for PiP window, which is updated via - * {@link #mIPipAnimationListener} + * {@link #mIPipAnimationListener} */ public int getPipCornerRadius() { return mPipCornerRadius; @@ -6136,7 +6843,7 @@ public int getPipCornerRadius() { /** * @return Shadow radius in pixel value for PiP window, which is updated via - * {@link #mIPipAnimationListener} + * {@link #mIPipAnimationListener} */ public int getPipShadowRadius() { return mPipShadowRadius; @@ -6300,6 +7007,68 @@ public void onExpandPip() { } } + @Override + public void onCanCreateDesksChanged(boolean canCreateDesks) { + // TODO: b/389209338 - update the AddDesktopButton's visibility on this. + } + + @Override + public void onDeskAdded(int displayId, int deskId) { + // Ignore desk changes that don't belong to this display. + if (displayId != mContainer.getDisplay().getDisplayId()) { + return; + } + + if (mUtils.getDesktopTaskViewForDeskId(deskId) != null) { + Log.e(TAG, "A task view for this desk has already been added."); + return; + } + + TaskView currentTaskView = getTaskViewAt(mCurrentPage); + + // We assume that a newly added desk is always empty and gets added to the left of the + // `AddNewDesktopButton`. + DesktopTaskView desktopTaskView = + (DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP); + desktopTaskView.bind(new DesktopTask(deskId, displayId, new ArrayList<>()), + mOrientationState, mTaskOverlayFactory); + + Objects.requireNonNull(mAddDesktopButton); + final int insertionIndex = 1 + indexOfChild(mAddDesktopButton); + addView(desktopTaskView, insertionIndex); + + updateTaskSize(); + mUtils.updateChildTaskOrientations(); + updateScrollSynchronously(); + + // Set Current Page based on the stored TaskView. + if (currentTaskView != null) { + setCurrentPage(indexOfChild(currentTaskView)); + } + } + + @Override + public void onDeskRemoved(int displayId, int deskId) { + // Ignore desk changes that don't belong to this display. + if (displayId != mContainer.getDisplay().getDisplayId()) { + return; + } + + // We need to distinguish between desk removals that are triggered from outside of overview + // vs. the ones that were initiated from overview by dismissing the corresponding desktop + // task view. + var taskView = mUtils.getDesktopTaskViewForDeskId(deskId); + if (taskView != null) { + dismissTaskView(taskView, true, true); + } + } + + @Override + public void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) { + // TODO: b/400870600 - We may need to add code here to special case when an empty desk gets + // activated, since `RemoteDesktopLaunchTransitionRunner` doesn't always get triggered. + } + /** Get the color used for foreground scrimming the RecentsView for sharing. */ public static int getForegroundScrimDimColor(Context context) { return context.getColor(R.color.overview_foreground_scrim_color); @@ -6350,11 +7119,31 @@ private void moveTaskToDesktopInternal(TaskContainer taskContainer, if (mDesktopRecentsTransitionController == null) { return; } - mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id, - transitionSource); + + mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource, + successCallback); + } + + /** + * Move the provided task into external display and invoke {@code successCallback} if succeeded. + */ + public void moveTaskToExternalDisplay(TaskContainer taskContainer, Runnable successCallback) { + if (!DesktopModeStatus.canEnterDesktopMode(mContext)) { + return; + } + switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false, + () -> moveTaskToDesktopInternal(taskContainer, successCallback))); + } + + private void moveTaskToDesktopInternal(TaskContainer taskContainer, Runnable successCallback) { + if (mDesktopRecentsTransitionController == null) { + return; + } + mDesktopRecentsTransitionController.moveToExternalDisplay(taskContainer.getTask().key.id); successCallback.run(); } + // Logs when the orientation of Overview changes. We log both real and fake orientation changes. private void logOrientationChanged() { // Only log when Overview is showing. @@ -6373,7 +7162,47 @@ private void logOrientationChanged() { } } + private int getFontWeight() { + int fontWeightAdjustment = getResources().getConfiguration().fontWeightAdjustment; + if (fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { + return Typeface.Builder.NORMAL_WEIGHT + fontWeightAdjustment; + } + return Typeface.Builder.NORMAL_WEIGHT; + } + + /** + * Creates the spring animations which run as a task settles back into its place in overview. + * + *

When a task dismiss is cancelled, the task will return to its original position via a + * spring animation. As it passes the threshold of its settling state, its neighbors will + * spring in response to the perceived impact of the settling task. + */ + public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView, + float velocity, boolean isDismissing, int dismissLength, + Function0 onEndRunnable) { + return mDismissUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity, + isDismissing, dismissLength, onEndRunnable); + } + + /** + * Animates RecentsView's scale to the provided value, using spring animations. + */ + public SpringAnimation animateRecentsScale(float scale) { + return mDismissUtils.animateRecentsScale(scale); + } + public interface TaskLaunchListener { void onTaskLaunched(); } + + /** + * Sets whether the remote animation targets should draw below the recents view. + * + * @param drawBelowRecents whether the surface should draw below Recents. + * @param remoteTargetHandles collection of remoteTargetHandles in Recents. + */ + public void setDrawBelowRecents(boolean drawBelowRecents, + RemoteTargetHandle[] remoteTargetHandles) { + mBlurUtils.setDrawBelowRecents(drawBelowRecents, remoteTargetHandles); + } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java index 060c71e4467..e61d4026985 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java +++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java @@ -16,7 +16,6 @@ package com.android.quickstep.views; -import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; import android.content.LocusId; @@ -26,9 +25,11 @@ import android.view.View; import android.view.Window; +import androidx.annotation.Nullable; + import com.android.launcher3.BaseActivity; import com.android.launcher3.logger.LauncherAtom; -import com.android.launcher3.util.SystemUiController; +import com.android.launcher3.taskbar.TaskbarUIController; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.ScrimView; @@ -41,7 +42,7 @@ 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 containerFromContext(Context context) { + static T containerFromContext(Context context) { if (context instanceof RecentsViewContainer) { return (T) context; } else if (context instanceof ContextWrapper) { @@ -51,11 +52,6 @@ static T containerFromContext(Context context) } } - /** - * Returns {@link SystemUiController} to manage various window flags to control system UI. - */ - SystemUiController getSystemUiController(); - /** * Returns {@link ScrimView} */ @@ -66,19 +62,6 @@ static T containerFromContext(Context context) */ 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) */ @@ -92,7 +75,7 @@ default Context asContext() { /** * Returns overview actions view as a view */ - View getActionsView(); + OverviewActionsView getActionsView(); /** * @see BaseActivity#addForceInvisibleFlag(int) @@ -139,12 +122,6 @@ default Context asContext() { */ void runOnBindToTouchInteractionService(Runnable r); - /** - * @see Activity#getWindow() - * @return Window - */ - Window getWindow(); - /** * @see * BaseActivity#addMultiWindowModeChangedListener(BaseActivity.MultiWindowModeChangedListener) @@ -173,6 +150,25 @@ void removeMultiWindowModeChangedListener( */ boolean isRecentsViewVisible(); + /** + * Begins transition to start home through container + */ + default void startHome(){ + // no op + } + + /** + * Checks container to see if we can start home transition safely + */ + boolean canStartHomeSafely(); + + + /** + * Enter staged split directly from the current running app. + * @param leftOrTop if the staged split will be positioned left or top. + */ + default void enterStageSplitFromRunningApp(boolean leftOrTop){} + /** * Overwrites any logged item in Launcher that doesn't have a container with the * {@link com.android.launcher3.touch.PagedOrientationHandler} in use for Overview. @@ -198,4 +194,8 @@ default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuil .setOrientationHandler(orientationForLogging)) .build()); } + + void setTaskbarUIController(@Nullable TaskbarUIController taskbarUIController); + + @Nullable TaskbarUIController getTaskbarUIController(); } diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt new file mode 100644 index 00000000000..ff711da70fe --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt @@ -0,0 +1,55 @@ +/* + * 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 com.android.launcher3.util.coroutines.DispatcherProvider +import com.android.quickstep.ViewUtils +import com.android.quickstep.recents.viewmodel.RecentsViewModel +import com.android.systemui.shared.recents.model.ThumbnailData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** Helper for [RecentsView] to interact with the [RecentsViewModel]. */ +class RecentsViewModelHelper( + private val recentsViewModel: RecentsViewModel, + private val recentsCoroutineScope: CoroutineScope, + private val dispatcherProvider: DispatcherProvider, +) { + fun onDestroy() { + recentsCoroutineScope.cancel("RecentsView is being destroyed") + } + + fun switchToScreenshot( + taskView: TaskView, + updatedThumbnails: Map?, + onFinishRunnable: Runnable, + ) { + // Update recentsViewModel and apply the thumbnailOverride ASAP, before waiting inside + // viewAttachedScope. + recentsViewModel.setRunningTaskShowScreenshot(true) + recentsCoroutineScope.launch(dispatcherProvider.background) { + recentsViewModel.waitForRunningTaskShowScreenshotToUpdate() + recentsViewModel.waitForThumbnailsToUpdate(updatedThumbnails) + withContext(Dispatchers.Main.immediate) { + ViewUtils.postFrameDrawn(taskView, onFinishRunnable) + } + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt new file mode 100644 index 00000000000..b265b13393b --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt @@ -0,0 +1,497 @@ +/* + * 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.graphics.PointF +import android.graphics.Rect +import android.util.FloatProperty +import android.view.KeyEvent +import android.view.View +import android.view.View.LAYOUT_DIRECTION_LTR +import android.view.View.LAYOUT_DIRECTION_RTL +import androidx.core.view.children +import com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU +import com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType +import com.android.launcher3.Flags.enableGridOnlyOverview +import com.android.launcher3.Flags.enableLargeDesktopWindowingTile +import com.android.launcher3.Flags.enableOverviewIconMenu +import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks +import com.android.launcher3.Utilities.getPivotsForScalingRectToRect +import com.android.launcher3.statehandlers.DesktopVisibilityController +import com.android.launcher3.statehandlers.DesktopVisibilityController.Companion.INACTIVE_DESK_ID +import com.android.launcher3.util.IntArray +import com.android.quickstep.util.DesksUtils.Companion.areMultiDesksFlagsEnabled +import com.android.quickstep.util.DesktopTask +import com.android.quickstep.util.GroupTask +import com.android.quickstep.util.isExternalDisplay +import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.ThumbnailData +import com.android.wm.shell.shared.GroupedTaskInfo +import java.util.function.BiConsumer +import kotlin.math.min +import kotlin.reflect.KMutableProperty1 + +/** + * Helper class for [RecentsView]. This util class contains refactored and extracted functions from + * RecentsView to facilitate the implementation of unit tests. + */ +class RecentsViewUtils(private val recentsView: RecentsView<*, *>) { + val taskViews = TaskViewsIterable(recentsView) + + /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */ + fun screenshotTasks(taskView: TaskView): Map { + val recentsAnimationController = recentsView.recentsAnimationController ?: return emptyMap() + return taskView.taskContainers.associate { + it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id) + } + } + + /** + * Sorts task groups to move desktop tasks to the end of the list. + * + * @param tasks List of group tasks to be sorted. + * @return Sorted list of GroupTasks to be used in the RecentsView. + */ + fun sortDesktopTasksToFront(tasks: List): List { + var (desktopTasks, otherTasks) = tasks.partition { it.taskViewType == TaskViewType.DESKTOP } + if (areMultiDesksFlagsEnabled()) { + // Desk IDs of newer desks are larger than those of older desks, hence we can use them + // to sort desks from old to new. + desktopTasks = desktopTasks.sortedBy { (it as DesktopTask).deskId } + } + return otherTasks + desktopTasks + } + + fun sortExternalDisplayTasksToFront(tasks: List): List { + val (externalDisplayTasks, otherTasks) = + tasks.partition { it.tasks.firstOrNull().isExternalDisplay } + return otherTasks + externalDisplayTasks + } + + class TaskViewsIterable(val recentsView: RecentsView<*, *>) : Iterable { + /** Iterates TaskViews when its index inside the RecentsView is needed. */ + fun forEachWithIndexInParent(consumer: BiConsumer) { + recentsView.children.forEachIndexed { index, child -> + (child as? TaskView)?.let { consumer.accept(index, it) } + } + } + + override fun iterator(): Iterator = + recentsView.children.mapNotNull { it as? TaskView }.iterator() + } + + /** Counts [TaskView]s that are [DesktopTaskView] instances. */ + private fun getDesktopTaskViewCount(): Int = taskViews.count { it is DesktopTaskView } + + /** Counts [TaskView]s that are not [DesktopTaskView] instances. */ + fun getNonDesktopTaskViewCount(): Int = taskViews.count { it !is DesktopTaskView } + + /** Returns a list of all large TaskView Ids from [TaskView]s */ + fun getLargeTaskViewIds(): List = taskViews.filter { it.isLargeTile }.map { it.taskViewId } + + /** Returns a list of all large TaskViews [TaskView]s */ + fun getLargeTaskViews(): List = taskViews.filter { it.isLargeTile } + + /** Returns all the TaskViews in the top row, without the focused task */ + fun getTopRowTaskViews(): List = + taskViews.filter { recentsView.mTopRowIdSet.contains(it.taskViewId) } + + /** Returns all the task Ids in the top row, without the focused task */ + fun getTopRowIdArray(): IntArray = getTopRowTaskViews().map { it.taskViewId }.toIntArray() + + /** Returns all the TaskViews in the bottom row, without the focused task */ + fun getBottomRowTaskViews(): List = + taskViews.filter { !recentsView.mTopRowIdSet.contains(it.taskViewId) && !it.isLargeTile } + + /** Returns all the task Ids in the bottom row, without the focused task */ + fun getBottomRowIdArray(): IntArray = getBottomRowTaskViews().map { it.taskViewId }.toIntArray() + + private fun List.toIntArray() = IntArray(size).apply { this@toIntArray.forEach(::add) } + + /** Counts [TaskView]s that are large tiles. */ + fun getLargeTileCount(): Int = taskViews.count { it.isLargeTile } + + /** Counts [TaskView]s that are grid tasks. */ + fun getGridTaskCount(): Int = taskViews.count { it.isGridTask } + + /** Returns the first TaskView that should be displayed as a large tile. */ + fun getFirstLargeTaskView(): TaskView? = + taskViews.firstOrNull { + it.isLargeTile && !(recentsView.isSplitSelectionActive && it is DesktopTaskView) + } + + /** + * Returns the [DesktopTaskView] that matches the given [deskId], or null if it doesn't exist. + */ + fun getDesktopTaskViewForDeskId(deskId: Int): DesktopTaskView? { + if (deskId == INACTIVE_DESK_ID) { + return null + } + return taskViews.firstOrNull { it is DesktopTaskView && it.deskId == deskId } + as? DesktopTaskView + } + + /** Returns the active desk ID of the display that contains the [recentsView] instance. */ + fun getActiveDeskIdOnThisDisplay(): Int = + DesktopVisibilityController.INSTANCE.get(recentsView.context) + .getActiveDeskId(recentsView.mContainer.display.displayId) + + /** Returns the expected focus task. */ + fun getFirstNonDesktopTaskView(): TaskView? = + if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView } + else taskViews.firstOrNull() + + /** + * Returns the [TaskView] that should be the current page during task binding, in the following + * priorities: + * 1. Running task + * 2. Focused task + * 3. First non-desktop task + * 4. Last desktop task + * 5. null otherwise + */ + fun getExpectedCurrentTask(runningTaskView: TaskView?, focusedTaskView: TaskView?): TaskView? = + runningTaskView + ?: focusedTaskView + ?: taskViews.firstOrNull { + it !is DesktopTaskView && + !(enableSeparateExternalDisplayTasks() && it.isExternalDisplay) + } + ?: taskViews.lastOrNull() + + private fun getDeviceProfile() = (recentsView.mContainer as RecentsViewContainer).deviceProfile + + fun getRunningTaskExpectedIndex(runningTaskView: TaskView): Int { + if (areMultiDesksFlagsEnabled() && runningTaskView is DesktopTaskView) { + // Use the [deskId] to keep desks in the order of their creation, as a newer desk + // always has a larger [deskId] than the older desks. + val desktopTaskView = + taskViews.firstOrNull { + it is DesktopTaskView && + it.deskId != INACTIVE_DESK_ID && + it.deskId <= runningTaskView.deskId + } + if (desktopTaskView != null) return recentsView.indexOfChild(desktopTaskView) + } + val firstTaskViewIndex = recentsView.indexOfChild(getFirstTaskView()) + return if (getDeviceProfile().isTablet) { + var index = firstTaskViewIndex + if (enableLargeDesktopWindowingTile() && runningTaskView !is DesktopTaskView) { + // For fullsreen tasks, skip over Desktop tasks in its section + index += + if (enableSeparateExternalDisplayTasks()) { + if (runningTaskView.isExternalDisplay) { + taskViews.count { it is DesktopTaskView && it.isExternalDisplay } + } else { + taskViews.count { it is DesktopTaskView && !it.isExternalDisplay } + } + } else { + getDesktopTaskViewCount() + } + } + if (enableSeparateExternalDisplayTasks() && !runningTaskView.isExternalDisplay) { + // For main display section, skip over external display tasks + index += taskViews.count { it.isExternalDisplay } + } + index + } else { + val currentIndex: Int = recentsView.indexOfChild(runningTaskView) + return if (currentIndex != -1) { + currentIndex // Keep the position if running task already in layout. + } else { + // New running task are added to the front to begin with. + firstTaskViewIndex + } + } + } + + /** Returns the first TaskView if it exists, or null otherwise. */ + fun getFirstTaskView(): TaskView? = taskViews.firstOrNull() + + /** Returns the last TaskView if it exists, or null otherwise. */ + fun getLastTaskView(): TaskView? = taskViews.lastOrNull() + + /** Returns the first TaskView that is not large */ + fun getFirstSmallTaskView(): TaskView? = taskViews.firstOrNull { !it.isLargeTile } + + /** Returns the last TaskView that should be displayed as a large tile. */ + fun getLastLargeTaskView(): TaskView? = taskViews.lastOrNull { it.isLargeTile } + + /** + * Gets the list of accessibility children. Currently all the children of RecentsViews are + * added, and in the reverse order to the list. + */ + fun getAccessibilityChildren(): List = recentsView.children.toList().reversed() + + @JvmOverloads + /** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */ + fun getFirstTaskViewInCarousel( + nonRunningTaskCarouselHidden: Boolean, + runningTaskView: TaskView? = recentsView.runningTaskView, + ): TaskView? = + taskViews.firstOrNull { + it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden) + } + + /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */ + fun getLastTaskViewInCarousel(nonRunningTaskCarouselHidden: Boolean): TaskView? = + taskViews.lastOrNull { + it.isVisibleInCarousel(recentsView.runningTaskView, nonRunningTaskCarouselHidden) + } + + /** Returns if any small tasks are fully visible */ + fun isAnySmallTaskFullyVisible(): Boolean = + taskViews.any { !it.isLargeTile && recentsView.isTaskViewFullyVisible(it) } + + /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */ + fun applyAttachAlpha(nonRunningTaskCarouselHidden: Boolean) { + taskViews.forEach { taskView -> + taskView.attachAlpha = + if (taskView == recentsView.runningTaskView) { + RUNNING_TASK_ATTACH_ALPHA.get(recentsView) + } else { + if ( + taskView.isVisibleInCarousel( + recentsView.runningTaskView, + nonRunningTaskCarouselHidden, + ) + ) + 1f + else 0f + } + } + } + + fun TaskView.isVisibleInCarousel( + runningTaskView: TaskView?, + nonRunningTaskCarouselHidden: Boolean, + ): Boolean = + if (!nonRunningTaskCarouselHidden) true + else getCarouselType() == runningTaskView.getCarouselType() + + /** Returns the carousel type of the TaskView, and default to fullscreen if it's null. */ + private fun TaskView?.getCarouselType(): TaskViewCarousel = + if (this is DesktopTaskView) TaskViewCarousel.DESKTOP else TaskViewCarousel.FULL_SCREEN + + private enum class TaskViewCarousel { + FULL_SCREEN, + DESKTOP, + } + + /** Returns true if there are at least one TaskView has been added to the RecentsView. */ + fun hasTaskViews() = taskViews.any() + + fun getTaskContainerById(taskId: Int) = + taskViews.firstNotNullOfOrNull { it.getTaskContainerById(taskId) } + + private fun getRowRect(firstView: View?, lastView: View?, outRowRect: Rect) { + outRowRect.setEmpty() + firstView?.let { + it.getHitRect(TEMP_RECT) + outRowRect.union(TEMP_RECT) + } + lastView?.let { + it.getHitRect(TEMP_RECT) + outRowRect.union(TEMP_RECT) + } + } + + private fun getRowRect(rowTaskViewIds: IntArray, outRowRect: Rect) { + if (rowTaskViewIds.isEmpty) { + outRowRect.setEmpty() + return + } + getRowRect( + recentsView.getTaskViewFromTaskViewId(rowTaskViewIds.get(0)), + recentsView.getTaskViewFromTaskViewId(rowTaskViewIds.get(rowTaskViewIds.size() - 1)), + outRowRect, + ) + } + + fun updateTaskViewDeadZoneRect( + outTaskViewRowRect: Rect, + outTopRowRect: Rect, + outBottomRowRect: Rect, + ) { + if (!getDeviceProfile().isTablet) { + getRowRect(getFirstTaskView(), getLastTaskView(), outTaskViewRowRect) + return + } + getRowRect(getFirstLargeTaskView(), getLastLargeTaskView(), outTaskViewRowRect) + getRowRect(getTopRowIdArray(), outTopRowRect) + getRowRect(getBottomRowIdArray(), outBottomRowRect) + + // Expand large tile Rect to include space between top/bottom row. + val nonEmptyRowRect = + when { + !outTopRowRect.isEmpty -> outTopRowRect + !outBottomRowRect.isEmpty -> outBottomRowRect + else -> return + } + if (recentsView.isRtl) { + if (outTaskViewRowRect.left > nonEmptyRowRect.right) { + outTaskViewRowRect.left = nonEmptyRowRect.right + } + } else { + if (outTaskViewRowRect.right < nonEmptyRowRect.left) { + outTaskViewRowRect.right = nonEmptyRowRect.left + } + } + + // Expand the shorter row Rect to include the space between the 2 rows. + if (outTopRowRect.isEmpty || outBottomRowRect.isEmpty) return + if (outTopRowRect.width() <= outBottomRowRect.width()) { + if (outTopRowRect.bottom < outBottomRowRect.top) { + outTopRowRect.bottom = outBottomRowRect.top + } + } else { + if (outBottomRowRect.top > outTopRowRect.bottom) { + outBottomRowRect.top = outTopRowRect.bottom + } + } + } + + private fun getTaskMenu(): TaskMenuView? = + getTopOpenViewWithType(recentsView.mContainer, TYPE_TASK_MENU) as? TaskMenuView + + fun shouldInterceptKeyEvent(event: KeyEvent): Boolean { + if (enableOverviewIconMenu()) { + return getTaskMenu()?.isOpen == true || event.keyCode == KeyEvent.KEYCODE_TAB + } + return false + } + + fun updateChildTaskOrientations() { + with(recentsView) { + taskViews.forEach { it.setOrientationState(mOrientationState) } + if (enableOverviewIconMenu()) { + children.forEach { + it.layoutDirection = if (isRtl) LAYOUT_DIRECTION_LTR else LAYOUT_DIRECTION_RTL + } + } + + // Return when it's not fake landscape + if (mOrientationState.isRecentsActivityRotationAllowed) return@with + + // Rotation is supported on phone (details at b/254198019#comment4) + getTaskMenu()?.onRotationChanged() + } + } + + fun updateCentralTask() { + val isTablet: Boolean = getDeviceProfile().isTablet + val actionsViewCanRelateToTaskView = !(isTablet && enableGridOnlyOverview()) + val focusedTaskView = recentsView.focusedTaskView + val currentPageTaskView = recentsView.currentPageTaskView + + fun isInExpectedScrollPosition(taskView: TaskView?) = + taskView?.let { recentsView.isTaskInExpectedScrollPosition(it) } ?: false + + val centralTaskIds: Set = + when { + !actionsViewCanRelateToTaskView -> emptySet() + isTablet && isInExpectedScrollPosition(focusedTaskView) -> + focusedTaskView!!.taskIdSet + isInExpectedScrollPosition(currentPageTaskView) -> currentPageTaskView!!.taskIdSet + else -> emptySet() + } + + recentsView.mRecentsViewModel.updateCentralTaskIds(centralTaskIds) + } + + var deskExplodeProgress: Float = 0f + set(value) { + field = value + taskViews.filterIsInstance().forEach { it.explodeProgress = field } + } + + var selectedTaskView: TaskView? = null + set(newValue) { + val oldValue = field + field = newValue + if (oldValue != newValue) { + onSelectedTaskViewUpdated(oldValue, newValue) + } + } + + private fun onSelectedTaskViewUpdated( + oldSelectedTaskView: TaskView?, + newSelectedTaskView: TaskView?, + ) { + if (!enableGridOnlyOverview()) return + with(recentsView) { + oldSelectedTaskView?.modalScale = 1f + oldSelectedTaskView?.modalPivot = null + + if (newSelectedTaskView == null) return + + val modalTaskBounds = mTempRect + getModalTaskSize(modalTaskBounds) + val selectedTaskBounds = getTaskBounds(newSelectedTaskView) + + // Map bounds to selectedTaskView's coordinate system. + modalTaskBounds.offset(-selectedTaskBounds.left, -selectedTaskBounds.top) + selectedTaskBounds.offset(-selectedTaskBounds.left, -selectedTaskBounds.top) + + val modalScale = + min( + (modalTaskBounds.height().toFloat() / selectedTaskBounds.height()), + (modalTaskBounds.width().toFloat() / selectedTaskBounds.width()), + ) + val modalPivot = PointF() + getPivotsForScalingRectToRect(modalTaskBounds, selectedTaskBounds, modalPivot) + + newSelectedTaskView.modalScale = modalScale + newSelectedTaskView.modalPivot = modalPivot + } + } + + /** + * Creates a [DesktopTaskView] for the currently active desk on this display, which contains the + * tasks with the given [groupedTaskInfo]. + */ + fun createDesktopTaskViewForActiveDesk(groupedTaskInfo: GroupedTaskInfo): DesktopTaskView { + val desktopTaskView = + recentsView.getTaskViewFromPool(TaskViewType.DESKTOP) as DesktopTaskView + val tasks: List = groupedTaskInfo.taskInfoList.map { taskInfo -> Task.from(taskInfo) } + desktopTaskView.bind( + DesktopTask(groupedTaskInfo.deskId, groupedTaskInfo.deskDisplayId, tasks), + recentsView.mOrientationState, + recentsView.mTaskOverlayFactory, + ) + return desktopTaskView + } + + companion object { + class RecentsViewFloatProperty( + private val utilsProperty: KMutableProperty1 + ) : FloatProperty>(utilsProperty.name) { + override fun get(recentsView: RecentsView<*, *>): Float = + utilsProperty.get(recentsView.mUtils) + + override fun setValue(recentsView: RecentsView<*, *>, value: Float) { + utilsProperty.set(recentsView.mUtils, value) + } + } + + @JvmField + val DESK_EXPLODE_PROGRESS = RecentsViewFloatProperty(RecentsViewUtils::deskExplodeProgress) + + val TEMP_RECT = Rect() + } +} diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java index e86b5a0fd9a..fb23039c62a 100644 --- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java +++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java @@ -16,12 +16,10 @@ 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 android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; @@ -40,11 +38,11 @@ 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.BaseState; import com.android.launcher3.statemanager.StateManager; -import com.android.launcher3.states.StateAnimationConfig; +import com.android.quickstep.util.AnimUtils; import com.android.quickstep.util.SplitSelectStateController; +import com.android.wm.shell.shared.TypefaceUtils; +import com.android.wm.shell.shared.TypefaceUtils.FontFamily; /** * A rounded rectangular component containing a single TextView. @@ -56,7 +54,6 @@ 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 RecentsViewContainer mContainer; public boolean mIsCurrentlyAnimating = false; @@ -126,36 +123,35 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } private void init() { - TextView cancelTextView = findViewById(R.id.split_instructions_text); + TextView cancelTextView = findViewById(R.id.split_instructions_text_cancel); TextView instructionTextView = findViewById(R.id.split_instructions_text); - if (FeatureFlags.enableSplitContextually()) { - 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(R.dimen.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; - } + cancelTextView.setVisibility(VISIBLE); + cancelTextView.setOnClickListener((v) -> exitSplitSelection()); + instructionTextView.setText(R.string.toast_contextual_split_select_app); + TypefaceUtils.setTypeface(instructionTextView, FontFamily.GSF_BODY_MEDIUM); + + // After layout, expand touch target of cancel button to meet minimum a11y measurements. + post(() -> { + int minTouchSize = getResources() + .getDimensionPixelSize(R.dimen.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)); - }); - } + setTouchDelegate(new TouchDelegate(r, cancelTextView)); + }); // Set accessibility title, will be announced by a11y tools. if (Utilities.ATLEAST_P) { @@ -166,25 +162,11 @@ private void init() { private void exitSplitSelection() { RecentsView recentsView = mContainer.getOverviewPanel(); SplitSelectStateController splitSelectController = recentsView.getSplitSelectController(); - 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(); + + AnimUtils.goToNormalStateWithSplitDismissal(stateManager, mContainer, + LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, + splitSelectController.getSplitAnimationController()); } void ensureProperRotation() { diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt new file mode 100644 index 00000000000..086ac774f17 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt @@ -0,0 +1,230 @@ +/* + * 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.graphics.Bitmap +import android.graphics.Matrix +import android.util.Log +import android.view.View +import android.view.View.OnClickListener +import com.android.launcher3.Flags.enableOverviewIconMenu +import com.android.launcher3.Flags.enableRefactorTaskThumbnail +import com.android.launcher3.model.data.TaskViewItemInfo +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.launcher3.util.TransformingTouchDelegate +import com.android.quickstep.TaskOverlayFactory +import com.android.quickstep.ViewUtils.addAccessibleChildToList +import com.android.quickstep.recents.domain.usecase.ThumbnailPosition +import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper +import com.android.quickstep.recents.ui.viewmodel.TaskData +import com.android.quickstep.task.thumbnail.TaskThumbnailView +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.ThumbnailData + +/** Holder for all Task dependent information. */ +class TaskContainer( + val taskView: TaskView, + val task: Task, + val snapshotView: View, + 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 */ + @SplitConfigurationOptions.StagePosition val stagePosition: Int, + val digitalWellBeingToast: DigitalWellBeingToast?, + val showWindowsView: View?, + taskOverlayFactory: TaskOverlayFactory, +) { + val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this) + var thumbnailPosition: ThumbnailPosition? = null + private var overlayEnabledStatus = false + + init { + if (enableRefactorTaskThumbnail()) { + require(snapshotView is TaskThumbnailView) + } else { + require(snapshotView is TaskThumbnailViewDeprecated) + } + } + + internal var thumbnailData: ThumbnailData? = null + private set + + val thumbnail: Bitmap? + /** If possible don't use this. It should be replaced as part of b/331753115. */ + get() = + if (enableRefactorTaskThumbnail()) thumbnailData?.thumbnail + else thumbnailViewDeprecated.thumbnail + + val thumbnailView: TaskThumbnailView + get() { + require(enableRefactorTaskThumbnail()) + return snapshotView as TaskThumbnailView + } + + val thumbnailViewDeprecated: TaskThumbnailViewDeprecated + get() { + require(!enableRefactorTaskThumbnail()) + return snapshotView as TaskThumbnailViewDeprecated + } + + var isThumbnailValid: Boolean = false + internal set + + val shouldShowSplashView: Boolean + get() = + if (enableRefactorTaskThumbnail()) taskView.shouldShowSplash() + else thumbnailViewDeprecated.shouldShowSplashView() + + /** Builds proto for logging */ + val itemInfo: TaskViewItemInfo + get() = TaskViewItemInfo(taskView, this) + + fun bind() = { + digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition) + if (!enableRefactorTaskThumbnail()) { + thumbnailViewDeprecated.bind(task, overlay, taskView) + } + } + + fun destroy() = { + digitalWellBeingToast?.destroy() + snapshotView.scaleX = 1f + snapshotView.scaleY = 1f + overlay.reset() + if (enableRefactorTaskThumbnail()) { + isThumbnailValid = false + thumbnailData = null + thumbnailView.onRecycle() + } else { + thumbnailViewDeprecated.setShowSplashForSplitSelection(false) + } + + if (enableOverviewIconMenu()) { + (iconView as IconAppChipView).reset() + } + } + + fun setOverlayEnabled(enabled: Boolean) { + if (!enableRefactorTaskThumbnail()) { + thumbnailViewDeprecated.setOverlayEnabled(enabled) + } + } + + fun setOverlayEnabled(enabled: Boolean, thumbnailPosition: ThumbnailPosition?) { + if (enableRefactorTaskThumbnail()) { + if (overlayEnabledStatus != enabled || this.thumbnailPosition != thumbnailPosition) { + overlayEnabledStatus = enabled + + refreshOverlay(thumbnailPosition) + } + } + } + + fun refreshOverlay(thumbnailPosition: ThumbnailPosition?) = { + this.thumbnailPosition = thumbnailPosition + when { + !overlayEnabledStatus -> overlay.reset() + thumbnailPosition == null -> { + Log.e(TAG, "Thumbnail position was null during overlay refresh", Exception()) + overlay.reset() + } + else -> + overlay.initOverlay( + task, + thumbnailData?.thumbnail, + thumbnailPosition.matrix, + thumbnailPosition.isRotated, + ) + } + } + + fun addChildForAccessibility(outChildren: ArrayList) { + addAccessibleChildToList(iconView.asView(), outChildren) + addAccessibleChildToList(snapshotView, outChildren) + showWindowsView?.let { addAccessibleChildToList(it, outChildren) } + digitalWellBeingToast?.let { addAccessibleChildToList(it, outChildren) } + overlay.addChildForAccessibility(outChildren) + } + + fun setState( + state: TaskData?, + liveTile: Boolean, + hasHeader: Boolean, + clickCloseListener: OnClickListener?, + ) = { + thumbnailView.setState( + TaskUiStateMapper.toTaskThumbnailUiState( + state, + liveTile, + hasHeader, + clickCloseListener, + ), + state?.taskId, + ) + thumbnailData = if (state is TaskData.Data) state.thumbnailData else null + overlay.setThumbnailState(thumbnailData) + } + + fun updateTintAmount(tintAmount: Float) { + thumbnailView.updateTintAmount(tintAmount) + } + + /** + * Updates the progress of the menu opening animation. + * + * This function propagates the given `progress` value to the `thumbnailView` allowing the + * thumbnail view to animate its visual state in sync with the menu's opening/closing + * transition. + * + * @param progress The progress of the menu opening animation (from closed=0 to fully open=1) + */ + fun updateMenuOpenProgress(progress: Float) { + thumbnailView.updateMenuOpenProgress(progress) + } + + /** + * Updates the thumbnail splash progress for a given task. + * + * This function manages the visual feedback of a "splash" effect that can be displayed over a + * thumbnail image, typically during loading or updating. It calculates the alpha (transparency) + * of the splash based on the provided progress and then applies this alpha to the thumbnail + * view if it should be displayed. + * + * @param progress The progress of the operation, ranging from 0.0 to 1.0 + */ + fun updateThumbnailSplashProgress(progress: Float) { + if (enableRefactorTaskThumbnail()) { + thumbnailView.updateSplashAlpha(progress) + } else { + thumbnailViewDeprecated.setSplashAlpha(progress) + } + } + + fun updateThumbnailMatrix(matrix: Matrix) { + thumbnailView.setImageMatrix(matrix) + } + + companion object { + const val TAG = "TaskContainer" + } +} diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java deleted file mode 100644 index 1eba5bfe37f..00000000000 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.views; - -import static com.android.app.animation.Interpolators.EMPHASIZED; -import static com.android.launcher3.Flags.enableOverviewIconMenu; -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.TaskThumbnailViewDeprecated.DIM_ALPHA; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Outline; -import android.graphics.Rect; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.app.animation.Interpolators; -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.R; -import com.android.launcher3.anim.AnimationSuccessListener; -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; -import com.android.launcher3.popup.SystemShortcut; -import com.android.launcher3.views.BaseDragLayer; -import com.android.quickstep.TaskOverlayFactory; -import com.android.quickstep.TaskUtils; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.util.TaskCornerRadius; -import com.android.quickstep.views.TaskView.TaskContainer; - -import app.lawnchair.theme.drawable.DrawableTokens; - -/** - * Contains options for a recent task when long-pressing its icon. - */ -public class TaskMenuView extends AbstractFloatingView { - - private static final Rect sTempRect = new Rect(); - - private static final int REVEAL_OPEN_DURATION = enableOverviewIconMenu() ? 417 : 150; - private static final int REVEAL_CLOSE_DURATION = enableOverviewIconMenu() ? 333 : 100; - - private RecentsViewContainer mContainer; - private TextView mTaskName; - @Nullable - private AnimatorSet mOpenCloseAnimator; - @Nullable - private ValueAnimator mRevealAnimator; - @Nullable - private Runnable mOnClosingStartCallback; - private TaskView mTaskView; - private TaskContainer mTaskContainer; - private LinearLayout mOptionLayout; - private float mMenuTranslationYBeforeOpen; - private float mMenuTranslationXBeforeOpen; - - public TaskMenuView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - mContainer = RecentsViewContainer.containerFromContext(context); - setClipToOutline(true); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mTaskName = findViewById(R.id.task_name); - mTaskName.setBackground(DrawableTokens.TaskMenuItemBg.resolve(getContext())); - mOptionLayout = findViewById(R.id.menu_option_layout); - } - - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - BaseDragLayer dl = mContainer.getDragLayer(); - if (!dl.isEventOverView(this, ev)) { - // TODO: log this once we have a new container type for it? - close(true); - return true; - } - } - return false; - } - - @Override - protected void handleClose(boolean animate) { - animateClose(); - } - - @Override - protected boolean isOfType(int type) { - return (type & TYPE_TASK_MENU) != 0; - } - - @Override - public ViewOutlineProvider getOutlineProvider() { - return new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), - TaskCornerRadius.get(view.getContext())); - } - }; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - 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); - } - - public void onRotationChanged() { - if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { - mOpenCloseAnimator.end(); - } - if (mIsOpen) { - mOptionLayout.removeAllViews(); - if (enableOverviewIconMenu() || !populateAndLayoutMenu()) { - close(false); - } - } - } - - /** - * Show a task menu for the given taskContainer. - */ - public static boolean showForTask(TaskContainer taskContainer, - @Nullable Runnable onClosingStartCallback) { - RecentsViewContainer container = RecentsViewContainer.containerFromContext( - taskContainer.getTaskView().getContext()); - final TaskMenuView taskMenuView = (TaskMenuView) container.getLayoutInflater().inflate( - R.layout.task_menu, container.getDragLayer(), false); - taskMenuView.setOnClosingStartCallback(onClosingStartCallback); - return taskMenuView.populateAndShowForTask(taskContainer); - } - - /** - * Show a task menu for the given taskContainer. - */ - public static boolean showForTask(TaskContainer taskContainer) { - return showForTask(taskContainer, null); - } - - private boolean populateAndShowForTask(TaskContainer taskContainer) { - if (isAttachedToWindow()) { - return false; - } - mContainer.getDragLayer().addView(this); - mTaskView = taskContainer.getTaskView(); - mTaskContainer = taskContainer; - if (!populateAndLayoutMenu()) { - return false; - } - post(this::animateOpen); - return true; - } - - /** - * @return true if successfully able to populate task view menu, false otherwise - */ - private boolean populateAndLayoutMenu() { - addMenuOptions(mTaskContainer); - orientAroundTaskView(mTaskContainer); - return true; - } - - private void addMenuOptions(TaskContainer taskContainer) { - if (enableOverviewIconMenu()) { - removeView(mTaskName); - } else { - mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask())); - mTaskName.setOnClickListener(v -> close(true)); - } - TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer) - .forEach(this::addMenuOption); - } - - private void addMenuOption(SystemShortcut menuOption) { - LinearLayout menuOptionView = (LinearLayout) mContainer.getLayoutInflater().inflate( - R.layout.task_view_menu_option, this, false); - if (enableOverviewIconMenu()) { - ((GradientDrawable) menuOptionView.getBackground()).setCornerRadius(0); - } - menuOption.setIconAndLabelFor( - menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); - LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams(); - mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp, - 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(TaskContainer taskContainer) { - RecentsView recentsView = mContainer.getOverviewPanel(); - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - - // Get Position - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - mContainer.getDragLayer().getDescendantRectRelativeToSelf( - enableOverviewIconMenu() - ? getIconView().findViewById(R.id.icon_view_menu_anchor) - : taskContainer.getThumbnailViewDeprecated(), - sTempRect); - Rect insets = mContainer.getDragLayer().getInsets(); - BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams(); - 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); - setScaleX(mTaskView.getScaleX()); - setScaleY(mTaskView.getScaleY()); - - // Set divider spacing - ShapeDrawable divider = new ShapeDrawable(new RectShape()); - divider.getPaint().setColor(getResources().getColor(android.R.color.transparent)); - int dividerSpacing = (int) getResources().getDimension(R.dimen.task_menu_spacing); - mOptionLayout.setShowDividers( - enableOverviewIconMenu() ? SHOW_DIVIDER_NONE : SHOW_DIVIDER_MIDDLE); - - orientationHandler.setTaskOptionsMenuLayoutOrientation( - deviceProfile, mOptionLayout, dividerSpacing, divider); - float thumbnailAlignedX = sTempRect.left - insets.left; - float thumbnailAlignedY = sTempRect.top - insets.top; - - // Changing pivot to make computations easier - // NOTE: Changing the pivots means the rotated view gets rotated about the new - // pivots set, - // which would render the X and Y position set here incorrect - setPivotX(0); - setPivotY(0); - setRotation(orientationHandler.getDegreesRotated()); - - if (enableOverviewIconMenu()) { - setTranslationX(thumbnailAlignedX); - setTranslationY(thumbnailAlignedY); - } else { - // Margin that insets the menuView inside the taskView - float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin); - setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX, - mTaskContainer.getThumbnailViewDeprecated(), deviceProfile, taskInsetMargin, - getIconView())); - setTranslationY(orientationHandler.getTaskMenuY( - thumbnailAlignedY, mTaskContainer.getThumbnailViewDeprecated(), - mTaskContainer.getStagePosition(), this, taskInsetMargin, - getIconView())); - } - } - - private void animateOpen() { - mMenuTranslationYBeforeOpen = getTranslationY(); - mMenuTranslationXBeforeOpen = getTranslationX(); - animateOpenOrClosed(false); - mIsOpen = true; - } - - private View getIconView() { - return mTaskContainer.getIconView().asView(); - } - - private void animateClose() { - animateOpenOrClosed(true); - } - - private void animateOpenOrClosed(boolean closing) { - if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { - mOpenCloseAnimator.cancel(); - } - mOpenCloseAnimator = new AnimatorSet(); - // If we're opening, we just start from the beginning as a new `TaskMenuView` is - // created - // each time we do the open animation so there will never be a partial value - // here. - float revealAnimationStartProgress = 0f; - if (closing && mRevealAnimator != null) { - revealAnimationStartProgress = 1f - mRevealAnimator.getAnimatedFraction(); - } - mRevealAnimator = createOpenCloseOutlineProvider() - .createRevealAnimator(this, closing, revealAnimationStartProgress); - mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED - : Interpolators.DECELERATE); - - if (enableOverviewIconMenu()) { - IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView(); - - float additionalTranslationY = 0; - 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 = mContainer.getDeviceProfile().heightPx - - mContainer.getDeviceProfile().getOverviewActionsClaimedSpaceBelow(); - float midpoint = (taskBottom + taskbarTop) / 2f; - additionalTranslationY = -Math.max(menuBottom - midpoint, 0); - } - ObjectAnimator translationYAnim = ObjectAnimator.ofFloat(this, TRANSLATION_Y, - closing ? mMenuTranslationYBeforeOpen - : mMenuTranslationYBeforeOpen + additionalTranslationY); - translationYAnim.setInterpolator(EMPHASIZED); - - ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat( - iconAppChip.getMenuTranslationY(), - MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY); - menuTranslationYAnim.setInterpolator(EMPHASIZED); - - float additionalTranslationX = 0; - 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() - (mContainer.getDeviceProfile().widthPx - - getResources().getDimensionPixelSize( - R.dimen.task_menu_edge_padding) * 2), - 0); - } - - ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X, - closing ? mMenuTranslationXBeforeOpen - : mMenuTranslationXBeforeOpen - additionalTranslationX); - translationXAnim.setInterpolator(EMPHASIZED); - - ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat( - iconAppChip.getMenuTranslationX(), - MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX); - menuTranslationXAnim.setInterpolator(EMPHASIZED); - - mOpenCloseAnimator.playTogether(translationYAnim, translationXAnim, - menuTranslationXAnim, menuTranslationYAnim); - } - mOpenCloseAnimator.playTogether(mRevealAnimator, - ObjectAnimator.ofFloat( - 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) { - setVisibility(VISIBLE); - if (closing && mOnClosingStartCallback != null) { - mOnClosingStartCallback.run(); - } - } - - @Override - public void onAnimationSuccess(Animator animator) { - if (closing) { - closeComplete(); - } - } - }); - mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION : REVEAL_OPEN_DURATION); - mOpenCloseAnimator.start(); - } - - private void closeComplete() { - mIsOpen = false; - mContainer.getDragLayer().removeView(this); - mRevealAnimator = null; - } - - private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { - float radius = TaskCornerRadius.get(mContext); - Rect fromRect = new Rect( - enableOverviewIconMenu() && isLayoutRtl() ? getWidth() : 0, - 0, - enableOverviewIconMenu() && !isLayoutRtl() ? 0 : getWidth(), - 0); - Rect toRect = new Rect(0, 0, getWidth(), getHeight()); - return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect); - } - - /** - * Calculates max height based on how much space we have available. - * If not enough space then the view will scroll. The maximum menu size will sit - * inside the task - * with a margin on the top and bottom. - */ - private int calculateMaxHeight() { - float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin); - return mTaskView.getPagedOrientationHandler().getTaskMenuHeight(taskInsetMargin, - mContainer.getDeviceProfile(), getTranslationX(), getTranslationY()); - } - - private void setOnClosingStartCallback(Runnable onClosingStartCallback) { - mOnClosingStartCallback = onClosingStartCallback; - } -} diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt new file mode 100644 index 00000000000..f7cb7bef788 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Outline +import android.graphics.Rect +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RectShape +import android.util.AttributeSet +import android.view.Gravity +import android.view.KeyEvent +import android.view.MotionEvent +import android.view.View +import android.view.ViewOutlineProvider +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.res.ResourcesCompat +import com.android.app.animation.Interpolators +import com.android.launcher3.AbstractFloatingView +import com.android.launcher3.Flags.enableOverviewIconMenu +import com.android.launcher3.Flags.enableRefactorTaskThumbnail +import com.android.launcher3.R +import com.android.launcher3.anim.AnimationSuccessListener +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider +import com.android.launcher3.popup.SystemShortcut +import com.android.launcher3.util.MultiPropertyFactory +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.launcher3.views.BaseDragLayer +import com.android.quickstep.TaskOverlayFactory +import com.android.quickstep.TaskUtils +import com.android.quickstep.util.TaskCornerRadius +import java.util.function.Consumer +import kotlin.math.max + +/** Contains options for a recent task when long-pressing its icon. */ +class TaskMenuView +@JvmOverloads +constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : + AbstractFloatingView(context, attrs, defStyleAttr) { + private val recentsViewContainer: RecentsViewContainer = + RecentsViewContainer.containerFromContext(context) + private val tempRect = Rect() + private val taskName: TextView by lazy { findViewById(R.id.task_name) } + private val optionLayout: LinearLayout by lazy { findViewById(R.id.menu_option_layout) } + private var openCloseAnimator: AnimatorSet? = null + private var revealAnimator: ValueAnimator? = null + private var onClosingStartCallback: Runnable? = null + private lateinit var taskView: TaskView + private lateinit var taskContainer: TaskContainer + private var menuTranslationXBeforeOpen = 0f + private var menuTranslationYBeforeOpen = 0f + + // Spaced claimed below Overview (taskbar and insets) + private val taskbarTop by lazy { + recentsViewContainer.deviceProfile.heightPx - + recentsViewContainer.deviceProfile.overviewActionsClaimedSpaceBelow + } + private val minMenuTop by lazy { taskContainer.iconView.height.toFloat() } + // TODO(b/401476868): Replace overviewRowSpacing with correct margin to the taskbarTop. + private val maxMenuBottom by lazy { + (taskbarTop - recentsViewContainer.deviceProfile.overviewRowSpacing).toFloat() + } + + init { + clipToOutline = true + } + + override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean { + if (ev.action == MotionEvent.ACTION_DOWN) { + if (!recentsViewContainer.dragLayer.isEventOverView(this, ev)) { + // TODO: log this once we have a new container type for it? + animateOpenOrClosed(true) + return true + } + } + return false + } + + override fun handleClose(animate: Boolean) { + animateOpenOrClosed(true, animated = false) + } + + override fun isOfType(type: Int): Boolean = (type and TYPE_TASK_MENU) != 0 + + override fun getOutlineProvider(): ViewOutlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + 0, + view.width, + view.height, + TaskCornerRadius.get(view.context), + ) + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + var heightMeasure = heightMeasureSpec + val maxMenuHeight = calculateMaxHeight() + if (MeasureSpec.getSize(heightMeasure) > maxMenuHeight) { + heightMeasure = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST) + } + super.onMeasure(widthMeasureSpec, heightMeasure) + } + + fun onRotationChanged() { + openCloseAnimator?.let { if (it.isRunning) it.end() } + if (mIsOpen) { + optionLayout.removeAllViews() + if (enableOverviewIconMenu() || !populateAndLayoutMenu()) { + close(false) + } + } + } + + private fun populateAndShowForTask(taskContainer: TaskContainer): Boolean { + if (isAttachedToWindow) return false + recentsViewContainer.dragLayer.addView(this) + taskView = taskContainer.taskView + this.taskContainer = taskContainer + if (!populateAndLayoutMenu()) return false + post { this.animateOpen() } + return true + } + + /** @return true if successfully able to populate task view menu, false otherwise */ + private fun populateAndLayoutMenu(): Boolean { + addMenuOptions(taskContainer) + orientAroundTaskView(taskContainer) + return true + } + + private fun addMenuOptions(taskContainer: TaskContainer) { + if (enableOverviewIconMenu()) { + removeView(taskName) + } else { + taskName.text = TaskUtils.getTitle(context, taskContainer.task) + taskName.setOnClickListener { close(true) } + } + TaskOverlayFactory.getEnabledShortcuts(taskView, taskContainer) + .forEach(Consumer { menuOption: SystemShortcut<*> -> this.addMenuOption(menuOption) }) + } + + private fun addMenuOption(menuOption: SystemShortcut<*>) { + val menuOptionView = + recentsViewContainer.layoutInflater.inflate(R.layout.task_view_menu_option, this, false) + as LinearLayout + if (enableOverviewIconMenu()) { + menuOptionView.background = + ResourcesCompat.getDrawable( + resources, + R.drawable.app_chip_menu_item_bg, + context.theme, + ) + menuOptionView.foreground = + ResourcesCompat.getDrawable( + resources, + R.drawable.app_chip_menu_item_fg, + context.theme, + ) + } + menuOption.setIconAndLabelFor( + menuOptionView.findViewById(R.id.icon), + menuOptionView.findViewById(R.id.text), + ) + val lp = menuOptionView.layoutParams as LayoutParams + taskView.pagedOrientationHandler.setLayoutParamsForTaskMenuOptionItem( + lp, + menuOptionView, + recentsViewContainer.deviceProfile, + ) + // Set an onClick listener on each menu option. The onClick method is responsible for + // ending LiveTile mode on the thumbnail if needed. + menuOptionView.setOnClickListener { v: View? -> menuOption.onClick(v) } + optionLayout.addView(menuOptionView) + } + + private fun orientAroundTaskView(taskContainer: TaskContainer) { + val recentsView = recentsViewContainer.getOverviewPanel>() + val orientationHandler = recentsView.pagedOrientationHandler + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) + + // Get Position + val deviceProfile = recentsViewContainer.deviceProfile + recentsViewContainer.dragLayer.getDescendantRectRelativeToSelf( + if (enableOverviewIconMenu()) iconView.findViewById(R.id.icon_view_menu_anchor) + else taskContainer.snapshotView, + tempRect, + ) + val insets = recentsViewContainer.dragLayer.getInsets() + val params = layoutParams as BaseDragLayer.LayoutParams + params.width = + orientationHandler.getTaskMenuWidth( + taskContainer.snapshotView, + deviceProfile, + taskContainer.stagePosition, + ) + // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start + params.gravity = Gravity.LEFT + layoutParams = params + scaleX = taskView.scaleX + scaleY = taskView.scaleY + + // Set divider spacing + val divider = ShapeDrawable(RectShape()) + divider.paint.color = resources.getColor(android.R.color.transparent) + val dividerSpacing = resources.getDimension(R.dimen.task_menu_spacing).toInt() + optionLayout.showDividers = + if (enableOverviewIconMenu()) SHOW_DIVIDER_NONE else SHOW_DIVIDER_MIDDLE + + optionLayout.background = + if (enableOverviewIconMenu()) { + ResourcesCompat.getDrawable(resources, R.drawable.app_chip_menu_bg, context.theme) + } else { + null + } + + orientationHandler.setTaskOptionsMenuLayoutOrientation( + deviceProfile, + optionLayout, + dividerSpacing, + divider, + ) + val thumbnailAlignedX = (tempRect.left - insets.left).toFloat() + val thumbnailAlignedY = (tempRect.top - insets.top).toFloat() + + // Changing pivot to make computations easier + // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set, + // which would render the X and Y position set here incorrect + pivotX = 0f + pivotY = 0f + rotation = orientationHandler.degreesRotated + + if (enableOverviewIconMenu()) { + elevation = resources.getDimension(R.dimen.task_thumbnail_icon_menu_elevation) + translationX = thumbnailAlignedX + translationY = thumbnailAlignedY + } else { + // Margin that insets the menuView inside the taskView + val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin) + translationX = + orientationHandler.getTaskMenuX( + thumbnailAlignedX, + this.taskContainer.snapshotView, + deviceProfile, + taskInsetMargin, + iconView, + ) + translationY = + orientationHandler.getTaskMenuY( + thumbnailAlignedY, + this.taskContainer.snapshotView, + this.taskContainer.stagePosition, + this, + taskInsetMargin, + iconView, + ) + } + } + + private fun animateOpen() { + menuTranslationYBeforeOpen = translationY + menuTranslationXBeforeOpen = translationX + animateOpenOrClosed(false) + mIsOpen = true + } + + private val iconView: View + get() = taskContainer.iconView.asView() + + private fun animateOpenOrClosed(closing: Boolean, animated: Boolean = true) { + openCloseAnimator?.let { if (it.isRunning) it.cancel() } + openCloseAnimator = AnimatorSet() + // If we're opening, we just start from the beginning as a new `TaskMenuView` is created + // each time we do the open animation so there will never be a partial value here. + var revealAnimationStartProgress = 0f + if (closing && revealAnimator != null) { + revealAnimationStartProgress = 1f - revealAnimator!!.animatedFraction + } + revealAnimator = + createOpenCloseOutlineProvider() + .createRevealAnimator(this, closing, revealAnimationStartProgress) + revealAnimator!!.interpolator = + if (enableOverviewIconMenu()) Interpolators.EMPHASIZED else Interpolators.DECELERATE + val openCloseAnimatorBuilder = openCloseAnimator!!.play(revealAnimator) + if (enableOverviewIconMenu()) { + animateOpenOrCloseAppChip(closing, openCloseAnimatorBuilder) + } + openCloseAnimatorBuilder.with( + ObjectAnimator.ofFloat(this, ALPHA, (if (closing) 0 else 1).toFloat()) + ) + if (enableRefactorTaskThumbnail()) { + revealAnimator?.addUpdateListener { animation: ValueAnimator -> + val animatedFraction = animation.animatedFraction + val openProgress = if (closing) (1 - animatedFraction) else animatedFraction + taskContainer.updateMenuOpenProgress(openProgress) + } + } else { + openCloseAnimatorBuilder.with( + ObjectAnimator.ofFloat( + taskContainer.thumbnailViewDeprecated, + TaskThumbnailViewDeprecated.DIM_ALPHA, + if (closing) 0f else TaskView.MAX_PAGE_SCRIM_ALPHA, + ) + ) + } + openCloseAnimator!!.addListener( + object : AnimationSuccessListener() { + override fun onAnimationStart(animation: Animator) { + visibility = VISIBLE + if (closing) onClosingStartCallback?.run() + } + + override fun onAnimationSuccess(animator: Animator) { + if (closing) closeComplete() + } + } + ) + val animationDuration = + when { + animated && closing -> REVEAL_CLOSE_DURATION + animated && !closing -> REVEAL_OPEN_DURATION + else -> 0L + } + openCloseAnimator!!.setDuration(animationDuration) + openCloseAnimator!!.start() + } + + private fun TaskView.isOnGridBottomRow(): Boolean = + (recentsViewContainer.getOverviewPanel() as RecentsView<*, *>).isOnGridBottomRow(this) + + private fun closeComplete() { + mIsOpen = false + recentsViewContainer.dragLayer.removeView(this) + revealAnimator = null + } + + private fun createOpenCloseOutlineProvider(): RoundedRectRevealOutlineProvider { + val radius = TaskCornerRadius.get(mContext) + val fromRect = + Rect( + if (enableOverviewIconMenu() && isLayoutRtl) width else 0, + 0, + if (enableOverviewIconMenu() && !isLayoutRtl) 0 else width, + 0, + ) + val toRect = Rect(0, 0, width, height) + return RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect) + } + + /** + * Calculates max height based on how much space we have available. If not enough space then the + * view will scroll. The maximum menu size will sit inside the task with a margin on the top and + * bottom. + */ + private fun calculateMaxHeight(): Int = + taskView.pagedOrientationHandler.getTaskMenuHeight( + taskInsetMargin = resources.getDimension(R.dimen.task_card_margin), // taskInsetMargin + deviceProfile = recentsViewContainer.deviceProfile, + taskMenuX = translationX, + taskMenuY = + // Bottom menu can translate up to show more options. So we use the min + // translation allowed to calculate its max height. + if (enableOverviewIconMenu() && taskView.isOnGridBottomRow()) minMenuTop + else translationY, + ) + + private fun setOnClosingStartCallback(onClosingStartCallback: Runnable?) { + this.onClosingStartCallback = onClosingStartCallback + } + + private fun animateOpenOrCloseAppChip(closing: Boolean, animatorBuilder: AnimatorSet.Builder) { + val iconAppChip = taskContainer.iconView.asView() as IconAppChipView + + // Animate menu up for enough room to display full menu when task on bottom row. + var additionalTranslationY = 0f + if (taskView.isOnGridBottomRow()) { + val currentMenuBottom: Float = menuTranslationYBeforeOpen + height + additionalTranslationY = + if (currentMenuBottom < maxMenuBottom) 0f + // Translate menu up for enough room to display full menu when task on bottom row. + else maxMenuBottom - currentMenuBottom + + val currentMenuTop = menuTranslationYBeforeOpen + additionalTranslationY + // If it translate above the min accepted, it translates to the top of the screen + if (currentMenuTop < minMenuTop) { + // It subtracts the menuTranslation to make it 0 (top of the screen) + chip size. + additionalTranslationY = -menuTranslationYBeforeOpen + minMenuTop + } + } + + val translationYAnim = + ObjectAnimator.ofFloat( + this, + TRANSLATION_Y, + if (closing) menuTranslationYBeforeOpen + else menuTranslationYBeforeOpen + additionalTranslationY, + ) + translationYAnim.interpolator = Interpolators.EMPHASIZED + animatorBuilder.with(translationYAnim) + + val menuTranslationYAnim: ObjectAnimator = + ObjectAnimator.ofFloat( + iconAppChip.getMenuTranslationY(), + MultiPropertyFactory.MULTI_PROPERTY_VALUE, + if (closing) 0f else additionalTranslationY, + ) + menuTranslationYAnim.interpolator = Interpolators.EMPHASIZED + animatorBuilder.with(menuTranslationYAnim) + + var additionalTranslationX = 0f + if ( + taskContainer.stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT + ) { + // Animate menu and icon when split task would display off the side of the screen. + additionalTranslationX = + max( + (translationX + width - + (recentsViewContainer.deviceProfile.widthPx - + resources.getDimensionPixelSize( + R.dimen.task_menu_edge_padding + ) * 2)) + .toDouble(), + 0.0, + ) + .toFloat() + } + + val translationXAnim = + ObjectAnimator.ofFloat( + this, + TRANSLATION_X, + if (closing) menuTranslationXBeforeOpen + else menuTranslationXBeforeOpen - additionalTranslationX, + ) + translationXAnim.interpolator = Interpolators.EMPHASIZED + animatorBuilder.with(translationXAnim) + + val menuTranslationXAnim: ObjectAnimator = + ObjectAnimator.ofFloat( + iconAppChip.getMenuTranslationX(), + MultiPropertyFactory.MULTI_PROPERTY_VALUE, + if (closing) 0f else -additionalTranslationX, + ) + menuTranslationXAnim.interpolator = Interpolators.EMPHASIZED + animatorBuilder.with(menuTranslationXAnim) + } + + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (enableOverviewIconMenu()) { + if (event.action != KeyEvent.ACTION_DOWN) return super.dispatchKeyEvent(event) + + val isFirstMenuOptionFocused = optionLayout.indexOfChild(optionLayout.focusedChild) == 0 + val isLastMenuOptionFocused = + optionLayout.indexOfChild(optionLayout.focusedChild) == optionLayout.childCount - 1 + if ( + (isLastMenuOptionFocused && event.keyCode == KeyEvent.KEYCODE_DPAD_DOWN) || + (isFirstMenuOptionFocused && event.keyCode == KeyEvent.KEYCODE_DPAD_UP) + ) { + iconView.requestFocus() + return true + } + } + return super.dispatchKeyEvent(event) + } + + companion object { + private val REVEAL_OPEN_DURATION = if (enableOverviewIconMenu()) 417L else 150L + private val REVEAL_CLOSE_DURATION = if (enableOverviewIconMenu()) 333L else 100L + + /** Show a task menu for the given taskContainer. */ + /** Show a task menu for the given taskContainer. */ + @JvmOverloads + fun showForTask( + taskContainer: TaskContainer, + onClosingStartCallback: Runnable? = null, + ): Boolean { + val container: RecentsViewContainer = + RecentsViewContainer.containerFromContext(taskContainer.taskView.context) + val taskMenuView = + container.layoutInflater.inflate(R.layout.task_menu, container.dragLayer, false) + as TaskMenuView + taskMenuView.setOnClosingStartCallback(onClosingStartCallback) + return taskMenuView.populateAndShowForTask(taskContainer) + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt index 346eb2ff654..833683b59eb 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt +++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt @@ -30,7 +30,6 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout import app.lawnchair.theme.color.tokens.ColorTokens -import com.android.launcher3.BaseDraggingActivity import com.android.launcher3.DeviceProfile import com.android.launcher3.InsettableFrameLayout import com.android.launcher3.R @@ -39,13 +38,16 @@ import com.android.launcher3.popup.RoundedArrowDrawable import com.android.launcher3.popup.SystemShortcut import com.android.launcher3.util.Themes import com.android.quickstep.TaskOverlayFactory -import com.android.quickstep.views.TaskView.TaskContainer class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T : Context { companion object { const val TAG = "TaskMenuViewWithArrow" - fun showForTask(taskContainer: TaskContainer, alignedOptionIndex: Int = 0): Boolean { + fun showForTask( + taskContainer: TaskContainer, + alignedOptionIndex: Int = 0, + onClosedCallback: Runnable? = null + ): Boolean { val container: RecentsViewContainer = RecentsViewContainer.containerFromContext(taskContainer.taskView.context) val taskMenuViewWithArrow = @@ -55,12 +57,18 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T false ) as TaskMenuViewWithArrow<*> - return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignedOptionIndex) + return taskMenuViewWithArrow.populateAndShowForTask( + taskContainer, + alignedOptionIndex, + onClosedCallback + ) } } constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor( context: Context, attrs: AttributeSet, @@ -82,6 +90,7 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T private var alignedOptionIndex: Int = 0 private val extraSpaceForRowAlignment: Int get() = optionMeasuredHeight * alignedOptionIndex + private val menuPaddingEnd = context.resources.getDimensionPixelSize(R.dimen.task_card_margin) private lateinit var taskView: TaskView @@ -91,13 +100,14 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T private var optionMeasuredHeight = 0 private val arrowHorizontalPadding: Int get() = - if (taskView.isFocusedTask) + if (taskView.isLargeTile) resources.getDimensionPixelSize(R.dimen.task_menu_horizontal_padding) else 0 private var iconView: IconView? = null private var scrim: View? = null private val scrimAlpha = 0.8f + private var onClosedCallback: Runnable? = null override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0 @@ -141,7 +151,8 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T private fun populateAndShowForTask( taskContainer: TaskContainer, - alignedOptionIndex: Int + alignedOptionIndex: Int, + onClosedCallback: Runnable? ): Boolean { if (isAttachedToWindow) { return false @@ -150,6 +161,7 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T taskView = taskContainer.taskView this.taskContainer = taskContainer this.alignedOptionIndex = alignedOptionIndex + this.onClosedCallback = onClosedCallback if (!populateMenu()) return false addScrim() show() @@ -252,6 +264,7 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T super.closeComplete() popupContainer.removeView(scrim) popupContainer.removeView(iconView) + onClosedCallback?.run() } /** diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java index 4283d0e4cfa..74d76e6432a 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java @@ -28,16 +28,13 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; -import android.os.Build; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Property; @@ -45,18 +42,16 @@ import android.widget.ImageView; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.core.graphics.ColorUtils; 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.FullscreenDrawParams; import com.android.quickstep.TaskOverlayFactory.TaskOverlay; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.views.TaskView.FullscreenDrawParams; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.recents.utilities.PreviewPositionHelper; @@ -70,8 +65,6 @@ */ @Deprecated public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable { - private static final MainThreadInitializedObject TEMP_PARAMS = - new MainThreadInitializedObject<>(FullscreenDrawParams::new); public static final Property DIM_ALPHA = new FloatProperty("dimAlpha") { @@ -99,36 +92,6 @@ public Float get(TaskThumbnailViewDeprecated thumbnailView) { } }; - /** Use to animate thumbnail translationX while first app in split selection is initiated */ - public static final Property SPLIT_SELECT_TRANSLATE_X = - new FloatProperty("splitSelectTranslateX") { - @Override - public void setValue(TaskThumbnailViewDeprecated thumbnail, - float splitSelectTranslateX) { - thumbnail.applySplitSelectTranslateX(splitSelectTranslateX); - } - - @Override - 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 SPLIT_SELECT_TRANSLATE_Y = - new FloatProperty("splitSelectTranslateY") { - @Override - public void setValue(TaskThumbnailViewDeprecated thumbnail, - float splitSelectTranslateY) { - thumbnail.applySplitSelectTranslateY(splitSelectTranslateY); - } - - @Override - public Float get(TaskThumbnailViewDeprecated thumbnailView) { - return thumbnailView.mSplitSelectTranslateY; - } - }; - private final RecentsViewContainer mContainer; private TaskOverlay mOverlay; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -141,9 +104,10 @@ public Float get(TaskThumbnailViewDeprecated thumbnailView) { // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0. private final Rect mPreviewRect = new Rect(); private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper(); - private TaskView.FullscreenDrawParams mFullscreenParams; + private FullscreenDrawParams mFullscreenParams; private ImageView mSplashView; private Drawable mSplashViewDrawable; + private TaskView mTaskView; @Nullable private Task mTask; @@ -160,8 +124,6 @@ public Float get(TaskThumbnailViewDeprecated thumbnailView) { private boolean mOverlayEnabled; /** Used as a placeholder when the original thumbnail animates out to. */ private boolean mShowSplashForSplitSelection; - private float mSplitSelectTranslateX; - private float mSplitSelectTranslateY; public TaskThumbnailViewDeprecated(Context context) { this(context, null); @@ -180,8 +142,7 @@ public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mContainer = RecentsViewContainer.containerFromContext(context); // Initialize with placeholder value. It is overridden later by TaskView - mFullscreenParams = TEMP_PARAMS.get(context); - + mFullscreenParams = new FullscreenDrawParams(context, __ -> 0f, __ -> 0f); mDimColor = RecentsView.getForegroundScrimDimColor(context); mDimmingPaintAfterClearing.setColor(mDimColor); } @@ -189,10 +150,11 @@ public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs /** * Updates the thumbnail to draw the provided task */ - public void bind(Task task, TaskOverlay overlay) { + public void bind(Task task, TaskOverlay overlay, TaskView taskView) { mOverlay = overlay; mOverlay.reset(); mTask = task; + mTaskView = taskView; int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000; mPaint.setColor(color); mBackgroundPaint.setColor(color); @@ -200,17 +162,6 @@ public void bind(Task task, TaskOverlay overlay) { updateSplashView(mTask.icon); } - /** - * 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. * @@ -298,40 +249,6 @@ public float getDimAlpha() { return mDimAlpha; } - /** - * 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) - public Insets getScaledInsets() { - if (mThumbnailData == null) { - return Insets.NONE; - } - - RectF bitmapRect = new RectF( - 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 - // inverse tells us where the view would be in the bitmaps coordinates. The insets are the - // difference between the bitmap bounds and the projected view bounds. - Matrix boundsToBitmapSpace = new Matrix(); - mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace); - RectF boundsInBitmapSpace = new RectF(); - boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect); - - DeviceProfile dp = mContainer.getDeviceProfile(); - int bottomInset = dp.isTablet - ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0; - return Insets.of(0, 0, 0, bottomInset); - } - - @SystemUiControllerFlags public int getSysUiStatusNavFlags() { if (mThumbnailData != null) { @@ -358,7 +275,7 @@ protected void onDraw(Canvas canvas) { canvas.save(); // Draw the insets if we're being drawn fullscreen (we do this for quick switch). drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), - mFullscreenParams.getCurrentDrawnCornerRadius()); + mFullscreenParams.getCurrentCornerRadius()); canvas.restore(); } @@ -366,15 +283,15 @@ public PreviewPositionHelper getPreviewPositionHelper() { return mPreviewPositionHelper; } - public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) { + public void setFullscreenParams(FullscreenDrawParams fullscreenParams) { mFullscreenParams = fullscreenParams; invalidate(); } public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius) { - if (mTask != null && getTaskView().isRunningTask() - && !getTaskView().getShouldShowScreenshot()) { + if (mTask != null && mTaskView.isRunningTask() + && !mTaskView.getShouldShowScreenshot()) { canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint); canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mDimmingPaintAfterClearing); @@ -415,35 +332,6 @@ public void drawOnCanvas(Canvas canvas, float x, float y, float width, float hei } } - /** See {@link #SPLIT_SELECT_TRANSLATE_X} */ - protected void applySplitSelectTranslateX(float splitSelectTranslateX) { - mSplitSelectTranslateX = splitSelectTranslateX; - applyTranslateX(); - } - - /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */ - protected void applySplitSelectTranslateY(float splitSelectTranslateY) { - mSplitSelectTranslateY = splitSelectTranslateY; - applyTranslateY(); - } - - private void applyTranslateX() { - setTranslationX(mSplitSelectTranslateX); - } - - private void applyTranslateY() { - setTranslationY(mSplitSelectTranslateY); - } - - protected void resetViewTransforms() { - mSplitSelectTranslateX = 0; - mSplitSelectTranslateY = 0; - } - - public TaskView getTaskView() { - return (TaskView) getParent(); - } - public void setOverlayEnabled(boolean overlayEnabled) { if (mOverlayEnabled != overlayEnabled) { mOverlayEnabled = overlayEnabled; @@ -496,9 +384,9 @@ private void updateSplashView(Drawable icon) { float viewCenterY = viewHeight / 2f; float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f; float centeredDrawableTop = (viewHeight - drawableHeight) / 2f; - float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale(); - float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null - ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen(); + float nonGridScale = mTaskView == null ? 1 : 1 / mTaskView.getNonGridScale(); + float recentsMaxScale = mTaskView == null || mTaskView.getRecentsView() == null + ? 1 : 1 / mTaskView.getRecentsView().getMaxScaleForFullScreen(); float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX()); float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY()); @@ -524,8 +412,13 @@ private boolean isThumbnailAspectRatioDifferentFromThumbnailData() { thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT); } - private boolean isThumbnailRotationDifferentFromTask() { - RecentsView recents = getTaskView().getRecentsView(); + /** + * Returns whether or not the current thumbnail is a different orientation to the task. + *

+ * Used to disable modal state when screenshot doesn't match the device orientation. + */ + public boolean isThumbnailRotationDifferentFromTask() { + RecentsView recents = mTaskView.getRecentsView(); if (recents == null || mThumbnailData == null) { return false; } @@ -544,7 +437,9 @@ private boolean isThumbnailRotationDifferentFromTask() { */ private void refreshOverlay() { if (mOverlayEnabled) { - mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(), + mOverlay.initOverlay(mTask, + mThumbnailData != null ? mThumbnailData.getThumbnail() : null, + mPreviewPositionHelper.getMatrix(), mPreviewPositionHelper.isOrientationChanged()); } else { mOverlay.reset(); @@ -571,7 +466,7 @@ private void updateThumbnailMatrix() { if (mBitmapShader != null && mThumbnailData != null) { mPreviewRect.set(0, 0, mThumbnailData.getThumbnail().getWidth(), mThumbnailData.getThumbnail().getHeight()); - int currentRotation = getTaskView().getOrientedState().getRecentsActivityRotation(); + int currentRotation = mTaskView.getOrientedState().getRecentsActivityRotation(); boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData, getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl); @@ -579,7 +474,7 @@ private void updateThumbnailMatrix() { mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix()); mPaint.setShader(mBitmapShader); } - getTaskView().updateCurrentFullscreenParams(); + mTaskView.updateFullscreenParams(); invalidate(); } @@ -617,6 +512,10 @@ public boolean isRealSnapshot() { return mThumbnailData.isRealSnapshot && !mTask.isLocked; } + public Matrix getThumbnailMatrix() { + return mPreviewPositionHelper.getMatrix(); + } + @Override public void onRecycle() { // Do nothing diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt new file mode 100644 index 00000000000..9a8805bf0f7 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import com.android.launcher3.R +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader + +class TaskThumbnailViewHeader +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { + + private val headerTitleView: TextView by lazy { findViewById(R.id.header_app_title) } + private val headerIconView: ImageView by lazy { findViewById(R.id.header_app_icon) } + private val headerCloseButton: ImageButton by lazy { findViewById(R.id.header_close_button) } + + fun setHeader(header: ThumbnailHeader) { + headerTitleView.setText(header.title) + headerIconView.setImageDrawable(header.icon) + headerCloseButton.setOnClickListener(header.clickCloseListener) + } +} diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index 3b2be643ecc..97bbe87d454 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -22,7 +22,6 @@ 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 @@ -39,7 +38,6 @@ 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 @@ -47,51 +45,51 @@ import androidx.annotation.RequiresApi import androidx.annotation.VisibleForTesting import androidx.core.view.updateLayoutParams import com.android.app.animation.Interpolators +import com.android.launcher3.AbstractFloatingView import com.android.launcher3.Flags.enableCursorHoverStates -import com.android.launcher3.Flags.enableFocusOutline +import com.android.launcher3.Flags.enableDesktopExplodedView import com.android.launcher3.Flags.enableGridOnlyOverview +import com.android.launcher3.Flags.enableHoverOfChildElementsInTaskview +import com.android.launcher3.Flags.enableLargeDesktopWindowingTile 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.Flags.enableSeparateExternalDisplayTasks 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.model.data.TaskViewItemInfo 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.KFloatProperty +import com.android.launcher3.util.MultiPropertyDelegate import com.android.launcher3.util.MultiPropertyFactory -import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE +import com.android.launcher3.util.MultiValueAlpha import com.android.launcher3.util.RunnableList -import com.android.launcher3.util.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.coroutines.DispatcherProvider import com.android.launcher3.util.rects.set -import com.android.launcher3.views.ActivityContext +import com.android.quickstep.FullscreenDrawParams import com.android.quickstep.RecentsModel import com.android.quickstep.RemoteAnimationTargets -import com.android.quickstep.TaskAnimationManager +import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle 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.recents.di.RecentsDependencies +import com.android.quickstep.recents.di.get +import com.android.quickstep.recents.di.inject +import com.android.quickstep.recents.domain.usecase.ThumbnailPosition +import com.android.quickstep.recents.ui.viewmodel.TaskData +import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState +import com.android.quickstep.recents.ui.viewmodel.TaskViewModel import com.android.quickstep.util.ActiveGestureErrorDetector import com.android.quickstep.util.ActiveGestureLog import com.android.quickstep.util.BorderAnimator @@ -99,11 +97,20 @@ import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAni import com.android.quickstep.util.RecentsOrientedState import com.android.quickstep.util.TaskCornerRadius import com.android.quickstep.util.TaskRemovedDuringLaunchListener +import com.android.quickstep.util.isExternalDisplay +import com.android.quickstep.util.safeDisplayId +import com.android.quickstep.views.IconAppChipView.AppChipStatus +import com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL +import com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED 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 +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch /** A task in the Recents view. */ open class TaskView @@ -114,7 +121,9 @@ constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0, focusBorderAnimator: BorderAnimator? = null, - hoverBorderAnimator: BorderAnimator? = null + hoverBorderAnimator: BorderAnimator? = null, + val type: TaskViewType = TaskViewType.SINGLE, + protected val thumbnailFullscreenParams: FullscreenDrawParams = FullscreenDrawParams(context), ) : FrameLayout(context, attrs), ViewPool.Reusable { /** * Used in conjunction with [onTaskListVisibilityChanged], providing more granularity on which @@ -124,37 +133,38 @@ constructor( @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 - get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray() + val taskIdSet: Set + /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */ + get() = taskContainers.map { it.task.key.id }.toSet() + + val snapshotViews: Array + get() = taskContainers.map { it.snapshotView }.toTypedArray() val isGridTask: Boolean /** Returns whether the task is part of overview grid and not being focused. */ - get() = container.deviceProfile.isTablet && !isFocusedTask + get() = container.deviceProfile.isTablet && !isLargeTile val isRunningTask: Boolean get() = this === recentsView?.runningTaskView - val isFocusedTask: Boolean - get() = this === recentsView?.focusedTaskView + private val isSelectedTask: Boolean + get() = this === recentsView?.selectedTaskView + + open val displayId: Int + get() = taskContainers.firstOrNull()?.task.safeDisplayId - val taskCornerRadius: Float - get() = currentFullscreenParams.cornerRadius + val isExternalDisplay: Boolean + get() = displayId.isExternalDisplay + + val isLargeTile: Boolean + get() = + this == recentsView?.focusedTaskView || + (enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP) || + (enableSeparateExternalDisplayTasks() && isExternalDisplay) val recentsView: RecentsView<*, *>? get() = parent as? RecentsView<*, *> @@ -162,21 +172,25 @@ constructor( val pagedOrientationHandler: RecentsPagedOrientationHandler get() = orientedState.orientationHandler - @get:Deprecated("Use [taskContainers] instead.") - val firstTask: Task + val firstTaskContainer: TaskContainer? + get() = taskContainers.firstOrNull() + + val firstTask: Task? /** Returns the first task bound to this TaskView. */ - get() = taskContainers[0].task + get() = firstTaskContainer?.task - @get:Deprecated("Use [taskContainers] instead.") - val firstThumbnailViewDeprecated: TaskThumbnailViewDeprecated - /** Returns the first thumbnailView of the TaskView. */ - get() = taskContainers[0].thumbnailViewDeprecated + val firstItemInfo: ItemInfo? + get() = firstTaskContainer?.itemInfo - @get:Deprecated("Use [taskContainers] instead.") - val firstItemInfo: ItemInfo - get() = taskContainers[0].itemInfo + /** + * A [TaskViewItemInfo] of this TaskView. The [firstTaskContainer] will be used to get some + * specific information like user, title etc of the Task. However, these task specific + * information will be skipped if the TaskView has no [taskContainers]. Note, please use + * [TaskContainer.itemInfo] for [TaskViewItemInfo] on a specific [TaskContainer]. + */ + val itemInfo: TaskViewItemInfo + get() = TaskViewItemInfo(this, firstTaskContainer) - private val currentFullscreenParams = FullscreenDrawParams(context) protected val container: RecentsViewContainer = RecentsViewContainer.containerFromContext(context) protected val lastTouchDownPosition = PointF() @@ -194,12 +208,9 @@ constructor( * 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)) + get() = (getNonGridTrans(nonGridTranslationX) + getGridTrans(this.gridTranslationX)) - protected val persistentTranslationY: Float + 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). @@ -210,21 +221,21 @@ constructor( get() = pagedOrientationHandler.getPrimaryValue( SPLIT_SELECT_TRANSLATION_X, - SPLIT_SELECT_TRANSLATION_Y + SPLIT_SELECT_TRANSLATION_Y, ) protected val secondarySplitTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue( SPLIT_SELECT_TRANSLATION_X, - SPLIT_SELECT_TRANSLATION_Y + SPLIT_SELECT_TRANSLATION_Y, ) - protected val primaryDismissTranslationProperty: FloatProperty + val primaryDismissTranslationProperty: FloatProperty get() = pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y) - protected val secondaryDismissTranslationProperty: FloatProperty + val secondaryDismissTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y) @@ -232,26 +243,61 @@ constructor( get() = pagedOrientationHandler.getPrimaryValue( TASK_OFFSET_TRANSLATION_X, - TASK_OFFSET_TRANSLATION_Y + TASK_OFFSET_TRANSLATION_Y, ) protected val secondaryTaskOffsetTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue( TASK_OFFSET_TRANSLATION_X, - TASK_OFFSET_TRANSLATION_Y + TASK_OFFSET_TRANSLATION_Y, ) protected val taskResistanceTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue( TASK_RESISTANCE_TRANSLATION_X, - TASK_RESISTANCE_TRANSLATION_Y + TASK_RESISTANCE_TRANSLATION_Y, ) private val tempCoordinates = FloatArray(2) - private val focusBorderAnimator: BorderAnimator? - private val hoverBorderAnimator: BorderAnimator? + private val focusBorderAnimator: BorderAnimator? = + focusBorderAnimator + ?: createSimpleBorderAnimator( + TaskCornerRadius.get(context).toInt(), + context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width), + this::getThumbnailBounds, + this, + context + .obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes) + .getColor( + R.styleable.TaskView_focusBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR, + ), + ) + + private val hoverBorderAnimator: BorderAnimator? = + hoverBorderAnimator + ?: if (enableCursorHoverStates()) + createSimpleBorderAnimator( + TaskCornerRadius.get(context).toInt(), + context.resources.getDimensionPixelSize(R.dimen.task_hover_border_width), + this::getThumbnailBounds, + this, + context + .obtainStyledAttributes( + attrs, + R.styleable.TaskView, + defStyleAttr, + defStyleRes, + ) + .getColor( + R.styleable.TaskView_hoverBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR, + ), + ) + else null + private val rootViewDisplayId: Int get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY @@ -263,11 +309,17 @@ constructor( var taskViewId = UNBOUND_TASK_VIEW_ID var isEndQuickSwitchCuj = false + var sysUiStatusNavFlags: Int = 0 + get() = + if (enableRefactorTaskThumbnail()) field + else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0 + private set // 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) { + if (value == field && enableOverviewIconMenu()) return field = Utilities.boundToRange(value, 0f, 1f) onFullscreenProgressChanged(field) } @@ -292,6 +344,18 @@ constructor( onModalnessUpdated(field) } + var modalPivot: PointF? = null + set(value) { + field = value + updatePivots() + } + + var splitSplashAlpha = 0f + set(value) { + field = value + applyThumbnailSplashAlpha() + } + protected var taskThumbnailSplashAlpha = 0f set(value) { field = value @@ -310,6 +374,12 @@ constructor( applyScale() } + var modalScale = 1f + set(value) { + field = value + applyScale() + } + private var dismissTranslationX = 0f set(value) { field = value @@ -381,12 +451,6 @@ constructor( applyTranslationX() } - protected var nonGridPivotTranslationX = 0f - set(value) { - field = value - applyTranslationX() - } - // Used when in SplitScreenSelectState private var splitSelectTranslationY = 0f set(value) { @@ -400,14 +464,15 @@ constructor( applyTranslationX() } - protected var stableAlpha = 1f - set(value) { - field = value - alpha = stableAlpha - } + private val taskViewAlpha = MultiValueAlpha(this, Alpha.entries.size) + protected var stableAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Stable) + var attachAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Attach) + var splitAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Split) + private var modalAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Modal) protected var shouldShowScreenshot = false get() = !isRunningTask || field + private set /** Enable or disable showing border on hover and focus change */ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @@ -423,37 +488,79 @@ constructor( focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true) } - private var focusTransitionProgress = 1f + /** + * Used to cache the hover border state so we don't repeatedly call the border animator with + * every hover event when the user hasn't crossed the threshold of the [thumbnailBounds]. + */ + private var hoverBorderVisible = false set(value) { + if (field == value) { + return + } field = value - onFocusTransitionProgressUpdated(field) + Log.d( + TAG, + "${taskIds.contentToString()} - setting border animator visibility to: $field", + ) + hoverBorderAnimator?.setBorderVisibility(visible = field, animated = true) } - private val focusTransitionPropertyFactory = + // Used to cache thumbnail bounds to avoid recalculating on every hover move. + private var thumbnailBounds = Rect() + + // Progress variable indicating if the TaskView is in a settled state: + // 0 = The TaskView is in a transitioning state e.g. during gesture, in quickswitch carousel, + // becoming focus task etc. + // 1 = The TaskView is settled and no longer transitioning + private var settledProgress = 1f + set(value) { + if (value == field && enableOverviewIconMenu()) return + field = value + onSettledProgressUpdated(field) + } + + private val settledProgressPropertyFactory = MultiPropertyFactory( this, - FOCUS_TRANSITION, - FOCUS_TRANSITION_INDEX_COUNT, + SETTLED_PROGRESS, + SettledProgress.entries.size, { x: Float, y: Float -> x * y }, - 1f + 1f, ) - private val focusTransitionFullscreen = - focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN) - private val focusTransitionScaleAndDim = - focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM) + private var settledProgressFullscreen by + MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Fullscreen) + private var settledProgressGesture by + MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Gesture) + private var settledProgressDismiss by + MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Dismiss) + + private var viewModel: TaskViewModel? = null + private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject() + private val coroutineScope: CoroutineScope by RecentsDependencies.inject() + private val coroutineJobs = mutableListOf() /** - * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in + * Returns an animator of [settledProgressDismiss] that transition in with a built-in * interpolator. */ - fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator = + fun getDismissIconFadeInAnimator(): ObjectAnimator = + ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_DISMISS, 1f).apply { + duration = FADE_IN_ICON_DURATION + interpolator = FADE_IN_ICON_INTERPOLATOR + } + + /** + * Returns an animator of [settledProgressDismiss] that transition out with a built-in + * interpolator. [AnimatedFloat] is used to apply another level of interpolation, on top of + * interpolator set to the [Animator] by the caller. + */ + fun getDismissIconFadeOutAnimator(): ObjectAnimator = AnimatedFloat { v -> - focusTransitionScaleAndDim.value = - FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v) + settledProgressDismiss = SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v) } .animateToValue(1f, 0f) - private var iconAndDimAnimator: ObjectAnimator? = null + private var iconFadeInOnGestureCompleteAnimator: ObjectAnimator? = null // The current background requests to load the task thumbnail and icon private val pendingThumbnailLoadRequests = mutableListOf>() private val pendingIconLoadRequests = mutableListOf>() @@ -461,56 +568,15 @@ constructor( init { setOnClickListener { _ -> onClick() } - val keyboardFocusHighlightEnabled = - (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline()) - val cursorHoverStatesEnabled = enableCursorHoverStates() - setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled) - val attrs = - context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes) - try { - this.focusBorderAnimator = - focusBorderAnimator - ?: if (keyboardFocusHighlightEnabled) - createSimpleBorderAnimator( - currentFullscreenParams.cornerRadius.toInt(), - context.resources.getDimensionPixelSize( - R.dimen.keyboard_quick_switch_border_width - ), - { bounds: Rect -> getThumbnailBounds(bounds) }, - this, - attrs.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, - attrs.getColor( - R.styleable.TaskView_hoverBorderColor, - BorderAnimator.DEFAULT_BORDER_COLOR - ) - ) - else null - } finally { - attrs.recycle() - } - } + setWillNotDraw(!enableCursorHoverStates()) + } @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public override fun onFocusChanged( gainFocus: Boolean, direction: Int, - previouslyFocusedRect: Rect? + previouslyFocusedRect: Rect?, ) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect) if (borderEnabled) { @@ -521,20 +587,28 @@ constructor( 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) + MotionEvent.ACTION_HOVER_ENTER -> { + hoverBorderVisible = + if (enableHoverOfChildElementsInTaskview()) { + getThumbnailBounds(thumbnailBounds) + event.isWithinThumbnailBounds() + } else { + true + } + } + MotionEvent.ACTION_HOVER_MOVE -> + if (enableHoverOfChildElementsInTaskview()) + hoverBorderVisible = event.isWithinThumbnailBounds() + MotionEvent.ACTION_HOVER_EXIT -> hoverBorderVisible = false 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 onInterceptHoverEvent(event: MotionEvent): Boolean = + if (enableHoverOfChildElementsInTaskview()) super.onInterceptHoverEvent(event) + else if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(event) override fun dispatchTouchEvent(ev: MotionEvent): Boolean { val recentsView = recentsView ?: return false @@ -562,37 +636,70 @@ constructor( super.draw(canvas) } + override fun setLayoutDirection(layoutDirection: Int) { + super.setLayoutDirection(layoutDirection) + if (enableOverviewIconMenu()) { + val deviceLayoutDirection = resources.configuration.layoutDirection + taskContainers.forEach { + (it.iconView as IconAppChipView).layoutDirection = deviceLayoutDirection + } + } + } + @RequiresApi(Build.VERSION_CODES.Q) 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 - } + updatePivots() systemGestureExclusionRects = SYSTEM_GESTURE_EXCLUSION_RECT.onEach { it.right = width it.bottom = height } + if (enableHoverOfChildElementsInTaskview()) { + getThumbnailBounds(thumbnailBounds) + } + } + + private fun updatePivots() { + val modalPivot = modalPivot + if (modalPivot != null) { + pivotX = modalPivot.x + pivotY = modalPivot.y + } else { + 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 + } + } } override fun onRecycle() { resetPersistentViewTransforms() + + viewModel = null + attachAlpha = 1f + splitAlpha = 1f + splitSplashAlpha = 0f + modalAlpha = 1f + modalScale = 1f + modalPivot = null + taskThumbnailSplashAlpha = 0f // 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 { + if (!enableRefactorTaskThumbnail()) { taskContainers.forEach { it.thumbnailViewDeprecated.setThumbnail(it.task, null) } } setOverlayEnabled(false) onTaskListVisibilityChanged(false) borderEnabled = false + hoverBorderVisible = false taskViewId = UNBOUND_TASK_VIEW_ID + // TODO(b/390583187): Clean the components UI State when TaskView is recycled. taskContainers.forEach { it.destroy() } } @@ -602,34 +709,36 @@ constructor( 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)) + // Only make actions available if the app icon menu is visible to the user. + // When modalness is >0, the user is in select mode and the icon menu is hidden. + // When split selection is active, they should only be able to select the app and not + // take any other action. + val shouldPopulateAccessibilityMenu = + modalness == 0f && recentsView?.isSplitSelectionActive == false + if (shouldPopulateAccessibilityMenu) { + 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) + // Add DWB accessibility action at the end of the list + taskContainers.forEach { + it.digitalWellBeingToast?.getDWBAccessibilityAction()?.let(::addAction) + } } recentsView?.let { collectionItemInfo = - AccessibilityNodeInfo.CollectionItemInfo.obtain( + AccessibilityNodeInfo.CollectionItemInfo( 0, 1, - it.taskViewCount - it.indexOfChild(this@TaskView) - 1, + it.getAccessibilityChildren().indexOf(this@TaskView), 1, - false + false, ) } } @@ -637,11 +746,6 @@ constructor( 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 @@ -658,13 +762,155 @@ constructor( return super.performAccessibilityAction(action, arguments) } + override fun onFinishInflate() { + super.onFinishInflate() + inflateViewStubs() + } + + protected open fun inflateViewStubs() { + findViewById(R.id.snapshot) + ?.apply { + layoutResource = + if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail + else R.layout.task_thumbnail_deprecated + } + ?.inflate() + findViewById(R.id.icon) + ?.apply { + layoutResource = + if (enableOverviewIconMenu()) R.layout.icon_app_chip_view + else R.layout.icon_view + } + ?.inflate() + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (enableRefactorTaskThumbnail()) { + // The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in + // onRecycle. So it should be initialized at this point. TaskView Lifecycle: + // `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle + coroutineJobs += + coroutineScope.launch(dispatcherProvider.main) { + viewModel!!.state.collectLatest(::updateTaskViewState) + } + } + } + + private fun updateTaskViewState(state: TaskTileUiState) { + sysUiStatusNavFlags = state.sysUiStatusNavFlags + + // Updating containers + val mapOfTasks = state.tasks.associateBy { it.taskId } + taskContainers.forEach { container -> + val taskId = container.task.key.id + val containerState = mapOfTasks[taskId] + val shouldHaveHeader = (type == TaskViewType.DESKTOP) && enableDesktopExplodedView() + container.setState( + state = containerState, + liveTile = state.isLiveTile, + hasHeader = shouldHaveHeader, + clickCloseListener = + if (shouldHaveHeader) { + { + // Update the layout UI to remove this task from the layout grid, + // and remove the task from ActivityManager afterwards. + recentsView?.dismissTask( + taskId, + /* animate= */ true, + /* removeTask= */ true, + ) + } + } else { + null + }, + ) + updateThumbnailValidity(container) + val thumbnailPosition = + updateThumbnailMatrix( + container = container, + width = container.thumbnailView.width, + height = container.thumbnailView.height, + ) + container.setOverlayEnabled(state.taskOverlayEnabled, thumbnailPosition) + if (state.isCentralTask) { + this.container.actionsView.let { + it.updateDisabledFlags( + DISABLED_ROTATED, + thumbnailPosition?.isRotated ?: false, + ) + it.updateDisabledFlags( + DISABLED_NO_THUMBNAIL, + state.tasks.any { taskData -> + (taskData as? TaskData.Data)?.thumbnailData?.thumbnail == null + }, + ) + } + } + + if (enableOverviewIconMenu()) { + setIconState(container, containerState) + } + } + } + + private fun updateThumbnailValidity(container: TaskContainer) { + container.isThumbnailValid = + viewModel?.isThumbnailValid( + thumbnail = container.thumbnailData, + width = container.thumbnailView.width, + height = container.thumbnailView.height, + ) ?: return + applyThumbnailSplashAlpha() + } + + /** + * Updates the thumbnail's transformation matrix and rotation state within a TaskContainer. + * + * This function is called to reposition the thumbnail in the following scenarios: + * - When the TTV's size changes (onSizeChanged), and it's displaying a SnapshotSplash. + * - When drawing a snapshot (drawSnapshot). + * + * @param container The TaskContainer holding the thumbnail to be updated. + * @param width The desired width of the thumbnail's container. + * @param height The desired height of the thumbnail's container. + */ + private fun updateThumbnailMatrix( + container: TaskContainer, + width: Int, + height: Int, + ): ThumbnailPosition? { + val thumbnailPosition = + viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl) + ?: return null + container.updateThumbnailMatrix(thumbnailPosition.matrix) + return thumbnailPosition + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + if (enableRefactorTaskThumbnail()) { + // The jobs are being cancelled in the background thread. So we make a copy of the + // list to prevent cleaning a new job that might be added to this list during + // onAttach or another moment in the lifecycle. + val coroutineJobsToCancel = coroutineJobs.toList() + coroutineJobs.clear() + coroutineScope.launch(dispatcherProvider.background) { + coroutineJobsToCancel.forEach { + it.cancel("TaskView detaching from window") + } + } + } + } + /** Updates this task view to the given {@param task}. */ open fun bind( task: Task, orientedState: RecentsOrientedState, - taskOverlayFactory: TaskOverlayFactory + taskOverlayFactory: TaskOverlayFactory, ) { cancelPendingLoadTasks() + this.orientedState = orientedState // Needed for dependencies taskContainers = listOf( createTaskContainer( @@ -672,69 +918,83 @@ constructor( R.id.snapshot, R.id.icon, R.id.show_windows, + R.id.digital_wellbeing_toast, STAGE_POSITION_UNDEFINED, - taskOverlayFactory + taskOverlayFactory, ) ) - setOrientationState(orientedState) + onBind(orientedState) } + protected open fun onBind(orientedState: RecentsOrientedState) { + if (enableRefactorTaskThumbnail()) { + val scopeId = context + Log.d(TAG, "onBind $scopeId ${orientedState.containerInterface}") + viewModel = + TaskViewModel( + taskViewType = type, + recentsViewData = RecentsDependencies.get(scopeId), + getTaskUseCase = RecentsDependencies.get(scopeId), + getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(scopeId), + isThumbnailValidUseCase = RecentsDependencies.get(scopeId), + getThumbnailPositionUseCase = RecentsDependencies.get(scopeId), + dispatcherProvider = RecentsDependencies.get(scopeId), + ) + .apply { bind(*taskIds) } + } + + taskContainers.forEach { container -> + container.bind() + if (enableRefactorTaskThumbnail()) { + container.thumbnailView.cornerRadius = + thumbnailFullscreenParams.currentCornerRadius + container.thumbnailView.doOnSizeChange { width, height -> + updateThumbnailValidity(container) + val thumbnailPosition = updateThumbnailMatrix(container, width, height) + container.refreshOverlay(thumbnailPosition) + } + } + } + setOrientationState(orientedState) + } + + private fun applyThumbnailSplashAlpha() { + val alpha = getSplashAlphaProgress() + taskContainers.forEach { it.updateThumbnailSplashProgress(alpha) } + } + + private fun getSplashAlphaProgress(): Float = + when { + !enableRefactorTaskThumbnail() -> taskThumbnailSplashAlpha + splitSplashAlpha > 0f -> splitSplashAlpha + shouldShowSplash() -> taskThumbnailSplashAlpha + else -> 0f + } + + internal fun shouldShowSplash(): Boolean = taskContainers.any { !it.isThumbnailValid } + protected fun createTaskContainer( task: Task, @IdRes thumbnailViewId: Int, @IdRes iconViewId: Int, @IdRes showWindowViewId: Int, + @IdRes digitalWellbeingBannerId: Int, @StagePosition stagePosition: Int, - taskOverlayFactory: TaskOverlayFactory + 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( + val iconView = findViewById(iconViewId) as TaskViewIcon + return TaskContainer( + this, task, - thumbnailView, - thumbnailViewDeprecated, + findViewById(thumbnailViewId), iconView, TransformingTouchDelegate(iconView.asView()), stagePosition, - DigitalWellBeingToast(container, this), + findViewById(digitalWellbeingBannerId)!!, findViewById(showWindowViewId)!!, - taskOverlayFactory + taskOverlayFactory, ) - .apply { - if (enableRefactorTaskThumbnail()) { - thumbnailViewDeprecated.setTaskOverlay(overlay) - bindThumbnailView() - } else { - thumbnailViewDeprecated.bind(task, overlay) - } - } - } - - protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon { - val iconView = findViewById(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 @@ -748,15 +1008,15 @@ constructor( fun containsTaskId(taskId: Int) = getTaskContainerById(taskId) != null open fun setOrientationState(orientationState: RecentsOrientedState) { - this.orientedState = orientationState - taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) } - setThumbnailOrientation(orientationState) - } + 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) + it.digitalWellBeingToast?.initialize() } } @@ -764,11 +1024,7 @@ constructor( * 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 - ) { + open fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) { val thumbnailPadding = container.deviceProfile.overviewTaskThumbnailTopMarginPx val taskWidth = lastComputedTaskSize.width() val taskHeight = lastComputedTaskSize.height() @@ -779,9 +1035,10 @@ constructor( 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. + + // Focused task and Desktop tasks should use focusTaskRatio that is associated + // with the original orientation of the focused task. + if (isLargeTile) { boxWidth = taskWidth boxHeight = taskHeight } else { @@ -795,22 +1052,15 @@ constructor( expectedHeight = boxHeight + thumbnailPadding // Scale to to fit task Rect. - nonGridScale = - if (enableGridOnlyOverview()) { - lastComputedCarouselTaskSize.width() / taskWidth.toFloat() - } else { - taskWidth / boxWidth.toFloat() - } + nonGridScale = 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 + expectedWidth = taskWidth + expectedHeight = taskHeight + thumbnailPadding } this.nonGridScale = nonGridScale this.boxTranslationY = boxTranslationY @@ -824,9 +1074,10 @@ constructor( 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 { + firstTaskContainer?.snapshotView?.updateLayoutParams { topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx } + taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() } } /** Returns the thumbnail's bounds, optionally relative to the screen. */ @@ -838,7 +1089,7 @@ constructor( if (relativeToDragLayer) { container.dragLayer.getDescendantRectRelativeToSelf( it.snapshotView, - thumbnailBounds + thumbnailBounds, ) } else { thumbnailBounds.set(it.snapshotView) @@ -870,7 +1121,8 @@ constructor( taskContainers.forEach { if (visible) { recentsModel.thumbnailCache - .updateThumbnailInBackground(it.task) { thumbnailData -> + .getThumbnailInBackground(it.task) { thumbnailData -> + it.task.thumbnail = thumbnailData it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData) } ?.also { request -> pendingThumbnailLoadRequests.add(request) } @@ -882,28 +1134,24 @@ constructor( } } } - if (needsUpdate(changes, FLAG_UPDATE_ICON)) { + if (needsUpdate(changes, FLAG_UPDATE_ICON) && !enableOverviewIconMenu()) { 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) + .getIconInBackground(it.task) { icon, contentDescription, title -> + it.task.icon = icon + it.task.titleDescription = contentDescription + it.task.title = title + onIconLoaded(it) } ?.also { request -> pendingIconLoadRequests.add(request) } } else { - setIcon(it.iconView, null) - if (enableOverviewIconMenu()) { - setText(it.iconView, null) - } + onIconUnloaded(it) } } } if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) { - currentFullscreenParams.updateCornerRadius(context) + thumbnailFullscreenParams.updateCornerRadius(context) } } @@ -911,10 +1159,38 @@ constructor( (dataChange and flag) == flag protected open fun cancelPendingLoadTasks() { - pendingThumbnailLoadRequests.forEach { it.cancel() } - pendingThumbnailLoadRequests.clear() - pendingIconLoadRequests.forEach { it.cancel() } - pendingIconLoadRequests.clear() + pendingThumbnailLoadRequests.forEach { it.cancel() } + pendingThumbnailLoadRequests.clear() + pendingIconLoadRequests.forEach { it.cancel() } + pendingIconLoadRequests.clear() + } + + protected open fun setIconState(container: TaskContainer, state: TaskData?) { + if (enableOverviewIconMenu()) { + if (state is TaskData.Data) { + setIcon(container.iconView, state.icon) + container.iconView.setText(state.title) + container.digitalWellBeingToast?.initialize() + } else { + setIcon(container.iconView, null) + container.iconView.setText(null) + } + } + } + + protected open fun onIconLoaded(taskContainer: TaskContainer) { + setIcon(taskContainer.iconView, taskContainer.task.icon) + if (enableOverviewIconMenu()) { + taskContainer.iconView.setText(taskContainer.task.title) + } + taskContainer.digitalWellBeingToast?.initialize() + } + + protected open fun onIconUnloaded(taskContainer: TaskContainer) { + setIcon(taskContainer.iconView, null) + if (enableOverviewIconMenu()) { + taskContainer.iconView.setText(null) + } } protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) { @@ -938,13 +1214,14 @@ constructor( } } - protected fun setText(iconView: TaskViewIcon, text: CharSequence?) { - iconView.setText(text) - } - - open fun refreshThumbnails(thumbnailDatas: HashMap?) { + @JvmOverloads + open fun setShouldShowScreenshot( + shouldShowScreenshot: Boolean, + thumbnailDatas: Map? = null, + ) { + if (this.shouldShowScreenshot == shouldShowScreenshot) return + this.shouldShowScreenshot = shouldShowScreenshot if (enableRefactorTaskThumbnail()) { - // TODO(b/342560598) add thumbnail logic return } @@ -964,7 +1241,7 @@ constructor( return } val callbackList = - launchTasks()?.apply { + launchWithAnimation()?.apply { add { Log.d("b/310064698", "${taskIds.contentToString()} - onClick - launchCompleted") } @@ -972,79 +1249,180 @@ constructor( Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList") container.statsLogManager .logger() - .withItemInfo(firstItemInfo) + .withItemInfo(itemInfo) .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP) } + /** Launch of the current task (both live and inactive tasks) with an animation. */ + fun launchWithAnimation(): RunnableList? { + return if (isRunningTask && recentsView?.remoteTargetHandles != null) { + launchAsLiveTile(recentsView?.remoteTargetHandles!!) + } else { + launchAsStaticTile() + } + } + + private fun launchAsLiveTile(remoteTargetHandles: Array): RunnableList? { + val recentsView = recentsView ?: return null + if (!isClickableAsLiveTile) { + Log.e( + TAG, + "launchAsLiveTile - TaskView is not clickable as a live tile; returning to home: ${taskIds.contentToString()}", + ) + return null + } + isClickableAsLiveTile = false + val targets = + if (remoteTargetHandles.isNotEmpty()) { + 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, + ) + } + } else { + null + } + 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 = launchAsStaticTile() + if (runnableList == null) { + Log.e( + TAG, + "launchAsLiveTile - Recents animation cancelled and cannot launch task as non-live tile; returning to home: ${taskIds.contentToString()}", + ) + } + isClickableAsLiveTile = true + return runnableList + } + TestLogging.recordEvent( + TestProtocol.SEQUENCE_MAIN, + "composeRecentsLaunchAnimator", + taskIds.contentToString(), + ) + val runnableList = RunnableList() + with(AnimatorSet()) { + TaskViewUtils.composeRecentsLaunchAnimator( + this, + this@TaskView, + targets.apps, + targets.wallpapers, + targets.nonApps, + true, /* launcherClosing */ + recentsView.stateManager, + recentsView, + recentsView.depthController, + /* transitionInfo= */ null, + ) + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animator: Animator) { + if (taskContainers.any { it.task.key.displayId != rootViewDisplayId }) { + launchAsStaticTile() + } + isClickableAsLiveTile = true + runEndCallback() + } + + override fun onAnimationCancel(animation: Animator) { + runEndCallback() + } + + private fun runEndCallback() { + runnableList.executeAllAndDestroy() + } + } + ) + start() + } + Log.d(TAG, "launchAsLiveTile - composeRecentsLaunchAnimator: ${taskIds.contentToString()}") + recentsView.onTaskLaunchedInLiveTileMode() + return runnableList + } + /** * 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? { + open fun launchAsStaticTile(): RunnableList? { + val firstTaskContainer = firstTaskContainer ?: return null TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", - taskIds.contentToString() + taskIds.contentToString(), ) val opts = container.getActivityLaunchOptions(this, null).apply { - options.launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY + options.launchDisplayId = displayId } if ( ActivityManagerWrapper.getInstance() - .startActivityFromRecents(taskContainers[0].task.key, opts.options) + .startActivityFromRecents(firstTaskContainer.task.key, opts.options) ) { Log.d( TAG, - "launchTaskAnimated - startActivityFromRecents: ${taskIds.contentToString()}" + "launchAsStaticTile - startActivityFromRecents: ${taskIds.contentToString()}", ) ActiveGestureLog.INSTANCE.trackEvent( ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED ) val recentsView = recentsView ?: return null - if (recentsView.runningTaskViewId != -1) { + if ( + recentsView.runningTaskViewId != -1 && + recentsView.mRecentsAnimationController != null + ) { 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) - } + // 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() + notifyTaskLaunchFailed("launchAsStaticTile") 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) { + @JvmOverloads + open fun launchWithoutAnimation( + isQuickSwitch: Boolean = false, + callback: (launched: Boolean) -> Unit, + ) { + val firstTaskContainer = firstTaskContainer ?: return TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", - taskIds.contentToString() + 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() + failureListener.register(container, firstTaskContainer.task.key.id) { + notifyTaskLaunchFailed("launchWithoutAnimation") recentsView?.let { // Disable animations for now, as it is an edge case and the app usually // covers launcher and also any state transition animation also gets @@ -1066,7 +1444,7 @@ constructor( 0, 0, Executors.MAIN_EXECUTOR.handler, - { callback(true) } + { callback(true) }, ) { failureListener.onTransitionFinished() } @@ -1075,113 +1453,32 @@ constructor( if (isQuickSwitch) { setFreezeRecentTasksReordering() } - // TODO(b/334826842) add splash functionality to new TTV - if (!enableRefactorTaskThumbnail()) { - disableStartingWindow = - firstContainer.thumbnailViewDeprecated.shouldShowSplashView() - } + // TODO(b/331754864): Update this to use TV.shouldShowSplash + disableStartingWindow = firstTaskContainer.shouldShowSplashView } Executors.UI_HELPER_EXECUTOR.execute { if ( !ActivityManagerWrapper.getInstance() - .startActivityFromRecents(firstContainer.task.key, if (Utilities.ATLEAST_Q) opts else null) + .startActivityFromRecents(firstTaskContainer.task.key, if (Utilities.ATLEAST_Q) opts else null) ) { // 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() + notifyTaskLaunchFailed("launchTask") 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() - } - } + Log.d( + TAG, + "launchWithoutAnimation - startActivityFromRecents: ${taskIds.contentToString()}", ) - start() } - Log.d(TAG, "launchTasks - composeRecentsLaunchAnimator: ${taskIds.contentToString()}") - recentsView.onTaskLaunchedInLiveTileMode() - return runnableList } - private fun notifyTaskLaunchFailed() { - val sb = StringBuilder("Failed to launch task \n") + private fun notifyTaskLaunchFailed(launchMethod: String) { + val sb = + StringBuilder("$launchMethod - Failed to launch task: ${taskIds.contentToString()}\n") taskContainers.forEach { sb.append("(task=${it.task.key.baseIntent} userId=${it.task.key.userId})\n") } @@ -1189,14 +1486,6 @@ constructor( 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 @@ -1212,11 +1501,11 @@ constructor( this, container.task, container.iconView.drawable, - container.thumbnailViewDeprecated, - container.thumbnailViewDeprecated.thumbnail, /* intent */ - null, /* user */ - null, - container.itemInfo + container.snapshotView, + container.thumbnail, + /* intent */ null, + /* user */ null, + container.itemInfo, ) } @@ -1245,12 +1534,38 @@ constructor( return showTaskMenuWithContainer(menuContainer) } + private fun closeTaskMenu(): Boolean { + val floatingView: AbstractFloatingView? = + AbstractFloatingView.getTopOpenViewWithType( + container, + AbstractFloatingView.TYPE_TASK_MENU, + ) + if (floatingView?.isOpen == true) { + floatingView.close(true) + return true + } else { + return false + } + } + private fun showTaskMenuWithContainer(menuContainer: TaskContainer): Boolean { val recentsView = recentsView ?: return false + if (enableHoverOfChildElementsInTaskview()) { + // Disable hover on all TaskView's whilst menu is showing. + recentsView.setTaskBorderEnabled(false) + } return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) { - menuContainer.iconView.revealAnim(/* isRevealing= */ true) - TaskMenuView.showForTask(menuContainer) { - menuContainer.iconView.revealAnim(/* isRevealing= */ false) + if (menuContainer.iconView.status == AppChipStatus.Expanded) { + closeTaskMenu() + } else { + menuContainer.iconView.revealAnim(/* isRevealing= */ true) + TaskMenuView.showForTask(menuContainer) { + val isAnimated = !recentsView.isSplitSelectionActive + menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated) + if (enableHoverOfChildElementsInTaskview()) { + recentsView.setTaskBorderEnabled(true) + } + } } } else if (container.deviceProfile.isTablet) { val alignedOptionIndex = @@ -1270,9 +1585,17 @@ constructor( } else { 0 } - TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex) + TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex) { + if (enableHoverOfChildElementsInTaskview()) { + recentsView.setTaskBorderEnabled(true) + } + } } else { - TaskMenuView.showForTask(menuContainer) + TaskMenuView.showForTask(menuContainer) { + if (enableHoverOfChildElementsInTaskview()) { + recentsView.setTaskBorderEnabled(true) + } + } } } @@ -1295,7 +1618,7 @@ constructor( private fun computeAndSetIconTouchDelegate( view: TaskViewIcon, tempCenterCoordinates: FloatArray, - transformingTouchDelegate: TransformingTouchDelegate + transformingTouchDelegate: TransformingTouchDelegate, ) { val viewHalfWidth = view.width / 2f val viewHalfHeight = view.height / 2f @@ -1306,13 +1629,13 @@ constructor( this[0] = viewHalfWidth this[1] = viewHalfHeight }, - false + false, ) transformingTouchDelegate.setBounds( (tempCenterCoordinates[0] - viewHalfWidth).toInt(), (tempCenterCoordinates[1] - viewHalfHeight).toInt(), (tempCenterCoordinates[0] + viewHalfWidth).toInt(), - (tempCenterCoordinates[1] + viewHalfHeight).toInt() + (tempCenterCoordinates[1] + viewHalfHeight).toInt(), ) } @@ -1322,7 +1645,7 @@ constructor( it.showWindowsView?.let { showWindowsView -> updateFilterCallback( showWindowsView, - getFilterUpdateCallback(it.task.key.packageName) + getFilterUpdateCallback(it.task.key.packageName), ) } } @@ -1355,23 +1678,23 @@ constructor( * 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) { + private fun onSettledProgressUpdated(settledProgress: Float) { taskContainers.forEach { - it.iconView.setContentAlpha(focusTransitionProgress) - it.digitalWellBeingToast?.updateBannerOffset(1f - focusTransitionProgress) + it.iconView.setContentAlpha(settledProgress) + it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - settledProgress } } - fun animateIconScaleAndDimIntoView() { - iconAndDimAnimator?.cancel() - iconAndDimAnimator = - ObjectAnimator.ofFloat(focusTransitionScaleAndDim, MULTI_PROPERTY_VALUE, 0f, 1f).apply { - duration = SCALE_ICON_DURATION + fun startIconFadeInOnGestureComplete() { + iconFadeInOnGestureCompleteAnimator?.cancel() + iconFadeInOnGestureCompleteAnimator = + ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_GESTURE, 1f).apply { + duration = FADE_IN_ICON_DURATION interpolator = Interpolators.LINEAR addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - iconAndDimAnimator = null + iconFadeInOnGestureCompleteAnimator = null } } ) @@ -1379,20 +1702,21 @@ constructor( } } - fun setIconScaleAndDim(iconScale: Float) { - iconAndDimAnimator?.cancel() - focusTransitionScaleAndDim.value = iconScale + fun setIconVisibleForGesture(isVisible: Boolean) { + iconFadeInOnGestureCompleteAnimator?.cancel() + settledProgressGesture = if (isVisible) 1f else 0f } /** 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 + if (enableRefactorTaskThumbnail()) { + it.updateTintAmount(amount) + } else { it.thumbnailViewDeprecated.dimAlpha = amount } it.iconView.setIconColorTint(tintColor, amount) - it.digitalWellBeingToast?.setBannerColorTint(tintColor, amount) + it.digitalWellBeingToast?.setColorTint(tintColor, amount) } } @@ -1406,7 +1730,7 @@ constructor( taskContainers.forEach { if (visibility == VISIBLE || it.task.key.id == taskId) { it.snapshotView.visibility = visibility - it.digitalWellBeingToast?.setBannerVisibility(visibility) + it.digitalWellBeingToast?.visibility = visibility it.showWindowsView?.visibility = visibility it.overlay.setVisibility(visibility) } @@ -1414,16 +1738,13 @@ constructor( } 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) } + taskContainers.forEach { it.setOverlayEnabled(overlayEnabled) } } } protected open fun refreshTaskThumbnailSplash() { if (!enableRefactorTaskThumbnail()) { - // TODO(b/334826842) add splash functionality to new TTV taskContainers.forEach { it.thumbnailViewDeprecated.refreshSplashView() } } } @@ -1431,27 +1752,15 @@ constructor( protected fun getScrollAdjustment(gridEnabled: Boolean) = if (gridEnabled) gridTranslationX else nonGridTranslationX - protected fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled) + fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled) fun getSizeAdjustment(fullscreenEnabled: Boolean) = if (fullscreenEnabled) nonGridScale else 1f private fun applyScale() { - val scale = persistentScale * dismissScale + val scale = persistentScale * dismissScale * Utilities.mapRange(modalness, 1f, modalScale) 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) - } - } + updateFullscreenParams() } private fun applyTranslationX() { @@ -1481,45 +1790,42 @@ constructor( protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) { taskContainers.forEach { - it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE) + if (!enableOverviewIconMenu()) { + it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE) + } it.overlay.setFullscreenProgress(fullscreenProgress) } - focusTransitionFullscreen.value = - FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress) - updateSnapshotRadius() + settledProgressFullscreen = + SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress) + updateFullscreenParams() } - protected open fun updateSnapshotRadius() { - updateCurrentFullscreenParams() + protected open fun updateFullscreenParams() { + updateFullscreenParams(thumbnailFullscreenParams) taskContainers.forEach { - it.thumbnailViewDeprecated.setFullscreenParams(getThumbnailFullscreenParams()) - it.overlay.setFullscreenParams(getThumbnailFullscreenParams()) + if (enableRefactorTaskThumbnail()) { + it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius + } else { + it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams) + } + it.overlay.setFullscreenParams(thumbnailFullscreenParams) } } - 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) { + isClickable = modalness == 0f taskContainers.forEach { - it.iconView.setModalAlpha(1 - modalness) - it.digitalWellBeingToast?.updateBannerOffset(modalness) + it.iconView.setModalAlpha(1f - modalness) + it.digitalWellBeingToast?.bannerOffsetPercentage = modalness + } + if (enableGridOnlyOverview()) { + modalAlpha = if (isSelectedTask) 1f else (1f - modalness) + applyScale() } - } - - /** 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() { @@ -1527,11 +1833,14 @@ constructor( gridTranslationX = 0f gridTranslationY = 0f boxTranslationY = 0f - nonGridPivotTranslationX = 0f + taskContainers.forEach { + it.snapshotView.translationX = 0f + it.snapshotView.translationY = 0f + } resetViewTransforms() } - open fun resetViewTransforms() { + fun resetViewTransforms() { // fullscreenTranslation and accumulatedTranslation should not be reset, as // resetViewTransforms is called during QuickSwitch scrolling. dismissTranslationX = 0f @@ -1547,13 +1856,9 @@ constructor( } dismissScale = 1f translationZ = 0f - alpha = stableAlpha - setIconScaleAndDim(1f) + setIconVisibleForGesture(true) + settledProgressDismiss = 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) = @@ -1562,243 +1867,93 @@ constructor( 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) - } - } + private fun MotionEvent.isWithinThumbnailBounds(): Boolean { + return thumbnailBounds.contains(x.toInt(), y.toInt()) + } - /** 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 addChildrenForAccessibility(outChildren: ArrayList) { + (if (isLayoutRtl) taskContainers.reversed() else taskContainers).forEach { + it.addChildForAccessibility(outChildren) } - - 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 + companion object { + private const val TAG = "TaskView" - fun destroy() { - digitalWellBeingToast?.destroy() - thumbnailView?.let { taskView.removeView(it) } + private enum class Alpha { + Stable, + Attach, + Split, + Modal, } - // 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)) + private enum class SettledProgress { + Fullscreen, + Gesture, + Dismiss, } - } - 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 + const val FADE_IN_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 = + private const val SETTLE_TRANSITION_THRESHOLD = + FADE_IN_ICON_DURATION.toFloat() / DIM_ANIM_DURATION + val SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR = Interpolators.clampToProgress( Interpolators.FAST_OUT_SLOW_IN, - 1f - FOCUS_TRANSITION_THRESHOLD, - 1f + 1f - SETTLE_TRANSITION_THRESHOLD, + 1f, )!! + private val FADE_IN_ICON_INTERPOLATOR = Interpolators.LINEAR private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect()) - private val FOCUS_TRANSITION: FloatProperty = - object : FloatProperty("focusTransition") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.focusTransitionProgress = v - } + private val SETTLED_PROGRESS: FloatProperty = + KFloatProperty(TaskView::settledProgress) - override fun get(taskView: TaskView) = taskView.focusTransitionProgress - } + private val SETTLED_PROGRESS_GESTURE: FloatProperty = + KFloatProperty(TaskView::settledProgressGesture) - private val SPLIT_SELECT_TRANSLATION_X: FloatProperty = - object : FloatProperty("splitSelectTranslationX") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.splitSelectTranslationX = v - } + private val SETTLED_PROGRESS_DISMISS: FloatProperty = + KFloatProperty(TaskView::settledProgressDismiss) - override fun get(taskView: TaskView) = taskView.splitSelectTranslationX - } + private val SPLIT_SELECT_TRANSLATION_X: FloatProperty = + KFloatProperty(TaskView::splitSelectTranslationX) private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty = - object : FloatProperty("splitSelectTranslationY") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.splitSelectTranslationY = v - } - - override fun get(taskView: TaskView) = taskView.splitSelectTranslationY - } + KFloatProperty(TaskView::splitSelectTranslationY) private val DISMISS_TRANSLATION_X: FloatProperty = - object : FloatProperty("dismissTranslationX") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.dismissTranslationX = v - } - - override fun get(taskView: TaskView) = taskView.dismissTranslationX - } + KFloatProperty(TaskView::dismissTranslationX) private val DISMISS_TRANSLATION_Y: FloatProperty = - object : FloatProperty("dismissTranslationY") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.dismissTranslationY = v - } - - override fun get(taskView: TaskView) = taskView.dismissTranslationY - } + KFloatProperty(TaskView::dismissTranslationY) private val TASK_OFFSET_TRANSLATION_X: FloatProperty = - object : FloatProperty("taskOffsetTranslationX") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.taskOffsetTranslationX = v - } - - override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX - } + KFloatProperty(TaskView::taskOffsetTranslationX) private val TASK_OFFSET_TRANSLATION_Y: FloatProperty = - object : FloatProperty("taskOffsetTranslationY") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.taskOffsetTranslationY = v - } - - override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY - } + KFloatProperty(TaskView::taskOffsetTranslationY) private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty = - object : FloatProperty("taskResistanceTranslationX") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.taskResistanceTranslationX = v - } - - override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX - } + KFloatProperty(TaskView::taskResistanceTranslationX) private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty = - object : FloatProperty("taskResistanceTranslationY") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.taskResistanceTranslationY = v - } - - override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY - } + KFloatProperty(TaskView::taskResistanceTranslationY) @JvmField val GRID_END_TRANSLATION_X: FloatProperty = - object : FloatProperty("gridEndTranslationX") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.gridEndTranslationX = v - } - - override fun get(taskView: TaskView) = taskView.gridEndTranslationX - } + KFloatProperty(TaskView::gridEndTranslationX) @JvmField - val DISMISS_SCALE: FloatProperty = - object : FloatProperty("dismissScale") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.dismissScale = v - } + val DISMISS_SCALE: FloatProperty = KFloatProperty(TaskView::dismissScale) - override fun get(taskView: TaskView) = taskView.dismissScale - } + @JvmField val SPLIT_ALPHA: FloatProperty = KFloatProperty(TaskView::splitAlpha) } } diff --git a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java index 94739cbc909..80e3a2b932f 100644 --- a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java +++ b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java @@ -47,6 +47,11 @@ public interface TaskViewIcon { */ void setModalAlpha(float alpha); + /** + * Sets the opacity of the view for flex split state. + */ + void setFlexSplitAlpha(float alpha); + /** * Returns this icon view's drawable. */ diff --git a/quickstep/src/com/android/quickstep/views/TaskViewType.kt b/quickstep/src/com/android/quickstep/views/TaskViewType.kt new file mode 100644 index 00000000000..b2a32a96c84 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/TaskViewType.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.views + +/** Type of the [TaskView] */ +enum class TaskViewType { + SINGLE, + GROUPED, + DESKTOP +} diff --git a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java new file mode 100644 index 00000000000..cb7254f1fe2 --- /dev/null +++ b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.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.util; + +import static com.android.quickstep.util.QuickstepProtoLogGroup.LAUNCHER_STATE_MANAGER; +import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized; + +import android.window.DesktopModeFlags.DesktopModeFlag; + +import androidx.annotation.NonNull; + +import com.android.internal.protolog.ProtoLog; +import com.android.launcher3.Flags; + +/** + * Proxy class used for StateManager ProtoLog support. + */ +public class StateManagerProtoLogProxy { + private static final DesktopModeFlag ENABLE_STATE_MANAGER_PROTO_LOG = + new DesktopModeFlag(Flags::enableStateManagerProtoLog, true); + public static void logGoToState( + @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) { + if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(LAUNCHER_STATE_MANAGER, + "StateManager.goToState: fromState: %s, toState: %s, partial trace:\n%s", + fromState, + toState, + trace); + } + + public static void logCreateAtomicAnimation( + @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) { + if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.createAtomicAnimation: " + + "fromState: %s, toState: %s, partial trace:\n%s", + fromState, + toState, + trace); + } + + public static void logOnStateTransitionStart(@NonNull Object state) { + if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionStart: state: %s", state); + } + + public static void logOnStateTransitionEnd(@NonNull Object state) { + if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionEnd: state: %s", state); + } + + public static void logCancelAnimation(boolean animationOngoing, @NonNull String trace) { + if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(LAUNCHER_STATE_MANAGER, + "StateManager.cancelAnimation: animation ongoing: %b, partial trace:\n%s", + animationOngoing, + trace); + } +} diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureErrorDetector.java similarity index 94% rename from quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java rename to quickstep/src_protolog/com/android/quickstep/util/ActiveGestureErrorDetector.java index cfa6b9891f0..ab109799273 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java +++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureErrorDetector.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 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. @@ -40,6 +40,7 @@ public enum GestureEvent { SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED, FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING, INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING, + QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED, NAVIGATION_MODE_SWITCHED, /** * These GestureEvents are specifically associated to state flags that get set in @@ -282,6 +283,29 @@ protected static void analyseAndDump( + " animation is still pending.", writer); break; + case QUICK_SWITCH_FROM_HOME_FALLBACK: + errorDetected |= printErrorIfTrue( + true, + prefix, + /* errorMessage= */ "Quick switch from home fallback case: the " + + "TaskView at the current page index was missing.", + writer); + break; + case QUICK_SWITCH_FROM_HOME_FAILED: + errorDetected |= printErrorIfTrue( + true, + prefix, + /* errorMessage= */ "Quick switch from home failed: the TaskViews at " + + "the current page index and index 0 were missing.", + writer); + break; + case NAVIGATION_MODE_SWITCHED: + errorDetected |= printErrorIfTrue( + true, + prefix, + /* errorMessage= */ "Navigation mode switched mid-gesture.", + writer); + break; case EXPECTING_TASK_APPEARED: case MOTION_DOWN: case SET_END_TARGET: diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java similarity index 77% rename from quickstep/src/com/android/quickstep/util/ActiveGestureLog.java rename to quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java index c54862aba38..23e245c51a0 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java +++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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. @@ -18,11 +18,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.util.Preconditions; - import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; @@ -71,14 +71,6 @@ public void addLog(@NonNull String event) { addLog(event, null); } - public void addLog(@NonNull String event, int extras) { - addLog(event, extras, null); - } - - public void addLog(@NonNull String event, boolean extras) { - addLog(event, extras, null); - } - /** * Adds a log to be printed at log-dump-time and track the associated event for error detection. * @@ -89,20 +81,6 @@ public void addLog( addLog(new CompoundString(event), gestureEvent); } - public void addLog( - @NonNull String event, - int extras, - @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { - addLog(new CompoundString(event).append(": ").append(extras), gestureEvent); - } - - public void addLog( - @NonNull String event, - boolean extras, - @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { - addLog(new CompoundString(event).append(": ").append(extras), gestureEvent); - } - public void addLog(@NonNull CompoundString compoundString) { addLog(compoundString, null); } @@ -237,7 +215,8 @@ public long getTime() { /** An entire log of entries associated with a single log ID */ protected static class EventLog { - protected final List eventEntries = new ArrayList<>(); + protected final List eventEntries = + Collections.synchronizedList(new ArrayList<>()); protected final int logId; protected final boolean mIsFullyGesturalNavMode; @@ -250,25 +229,27 @@ private EventLog(int logId, boolean isFullyGesturalNavMode) { /** A buildable string stored as an array for memory efficiency. */ public static class CompoundString { - public static final CompoundString NO_OP = new CompoundString(); + public static final CompoundString NO_OP = new CompoundString(true); private final List mSubstrings; private final List mArgs; private final boolean mIsNoOp; - private CompoundString() { - this(null); + public static CompoundString newEmptyString() { + return new CompoundString(false); } - public CompoundString(String substring) { - mIsNoOp = substring == null; + private CompoundString(boolean isNoOp) { + mIsNoOp = isNoOp; mSubstrings = mIsNoOp ? null : new ArrayList<>(); mArgs = mIsNoOp ? null : new ArrayList<>(); + } - if (!mIsNoOp) { - mSubstrings.add(substring); - } + public CompoundString(String substring, Object... args) { + this(substring == null); + + append(substring, args); } public CompoundString append(CompoundString substring) { @@ -281,80 +262,24 @@ public CompoundString append(CompoundString substring) { return this; } - public CompoundString append(String substring) { + public CompoundString append(String substring, Object... args) { if (mIsNoOp) { return this; } mSubstrings.add(substring); + mArgs.addAll(Arrays.stream(args).toList()); return this; } - public CompoundString append(int num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%d"); - } - - public CompoundString append(long num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%d"); - } - - public CompoundString append(float num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%.2f"); - } - - public CompoundString append(double num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%.2f"); - } - - public CompoundString append(boolean bool) { - if (mIsNoOp) { - return this; - } - mArgs.add(bool); - - return append("%b"); - } - - private Object[] getArgs() { - Preconditions.assertTrue(!mIsNoOp); - - return mArgs.toArray(); - } - @Override public String toString() { - return String.format(toUnformattedString(), getArgs()); - } - - private String toUnformattedString() { - Preconditions.assertTrue(!mIsNoOp); - + if (mIsNoOp) return null; StringBuilder sb = new StringBuilder(); for (String substring : mSubstrings) { sb.append(substring); } - - return sb.toString(); + return String.format(sb.toString(), mArgs.toArray()); } @Override @@ -364,10 +289,9 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (!(obj instanceof CompoundString)) { + if (!(obj instanceof CompoundString other)) { return false; } - CompoundString other = (CompoundString) obj; return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings) && Objects.equals(mArgs, other.mArgs); diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java new file mode 100644 index 00000000000..1c8656c54b1 --- /dev/null +++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import static android.view.MotionEvent.ACTION_DOWN; + +import static com.android.launcher3.Flags.enableActiveGestureProtoLog; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION; +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.MOTION_DOWN; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.NAVIGATION_MODE_SWITCHED; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_CANCEL_RECENTS_ANIMATION; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_FINISH_RECENTS_ANIMATION; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_START_RECENTS_ANIMATION; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FAILED; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FALLBACK; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION; +import static com.android.quickstep.util.QuickstepProtoLogGroup.ACTIVE_GESTURE_LOG; +import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized; + +import android.graphics.Point; +import android.graphics.RectF; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.protolog.ProtoLog; +import com.android.internal.protolog.common.IProtoLogGroup; + +/** + * Proxy class used for ActiveGestureLog ProtoLog support. + *

+ * This file will have all of its static strings in the + * {@link ProtoLog#d(IProtoLogGroup, String, Object...)} calls replaced by dynamic code/strings. + *

+ * When a new ActiveGestureLog entry needs to be added to the codebase (or and existing entry needs + * to be modified), add it here under a new unique method and make sure the ProtoLog entry matches + * to avoid confusion. + */ +public class ActiveGestureProtoLogProxy { + + public static void logLauncherDestroyed() { + ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED); + if (isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "Launcher destroyed"); + } + + public static void logAbsSwipeUpHandlerOnRecentsAnimationCanceled() { + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "AbsSwipeUpHandler.onRecentsAnimationCanceled", + /* gestureEvent= */ CANCEL_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onRecentsAnimationCanceled"); + } + + public static void logAbsSwipeUpHandlerOnRecentsAnimationFinished() { + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "RecentsAnimationCallbacks.onAnimationFinished", + ON_FINISH_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onAnimationFinished"); + } + + public static void logAbsSwipeUpHandlerCancelCurrentAnimation() { + ActiveGestureLog.INSTANCE.addLog( + "AbsSwipeUpHandler.cancelCurrentAnimation", + ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.cancelCurrentAnimation"); + } + + public static void logAbsSwipeUpHandlerOnTasksAppeared() { + ActiveGestureLog.INSTANCE.addLog("AbsSwipeUpHandler.onTasksAppeared: " + + "force finish recents animation complete; clearing state callback."); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onTasksAppeared: " + + "force finish recents animation complete; clearing state callback."); + } + + public static void logHandOffAnimation() { + ActiveGestureLog.INSTANCE.addLog("AbsSwipeUpHandler.handOffAnimation"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.handOffAnimation"); + } + + public static void logFinishRecentsAnimationOnTasksAppeared() { + ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimationOnTasksAppeared"); + } + + public static void logRecentsAnimationCallbacksOnAnimationCancelled() { + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "RecentsAnimationCallbacks.onAnimationCanceled", + /* gestureEvent= */ ON_CANCEL_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onAnimationCanceled"); + } + + public static void logRecentsAnimationCallbacksOnTasksAppeared() { + ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onTasksAppeared", + ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onTasksAppeared"); + } + + public static void logStartRecentsAnimation() { + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "TaskAnimationManager.startRecentsAnimation", + /* gestureEvent= */ START_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "TaskAnimationManager.startRecentsAnimation"); + } + + public static void logLaunchingSideTaskFailed() { + ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "Unable to launch side task (no recents)"); + } + + public static void logContinueRecentsAnimation() { + ActiveGestureLog.INSTANCE.addLog(/* event= */ "continueRecentsAnimation"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "continueRecentsAnimation"); + } + + public static void logCleanUpRecentsAnimationSkipped() { + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation skipped due to wrong callbacks"); + } + + public static void logCleanUpRecentsAnimation() { + ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation"); + } + + public static void logOnInputEventUserLocked(int displayId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TIS.onInputEvent(displayId=%d): Cannot process input event: user is locked", + displayId)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TIS.onInputEvent(displayId=%d): Cannot process input event: user is locked", + displayId); + } + + public static void logOnInputIgnoringFollowingEvents(int displayId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TIS.onMotionEvent(displayId=%d): A new gesture has been started, " + + "but a previously-requested recents animation hasn't started. " + + "Ignoring all following motion events.", displayId), + RECENTS_ANIMATION_START_PENDING); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TIS.onMotionEvent(displayId=%d): A new gesture has been started, " + + "but a previously-requested recents animation hasn't started. " + + "Ignoring all following motion events.", displayId); + } + + public static void logOnInputEventThreeButtonNav(int displayId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TIS.onInputEvent(displayId=%d): Cannot process input event: " + + "using 3-button nav and event is not a trackpad event", displayId)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TIS.onInputEvent(displayId=%d): Cannot process input event: " + + "using 3-button nav and event is not a trackpad event", displayId); + } + + public static void logPreloadRecentsAnimation() { + ActiveGestureLog.INSTANCE.addLog("preloadRecentsAnimation"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "preloadRecentsAnimation"); + } + + public static void logRecentTasksMissing() { + ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "Null mRecentTasks"); + } + + public static void logExecuteHomeCommand() { + ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(HOME)"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "OverviewCommandHelper.executeCommand(HOME)"); + } + + public static void logFinishRecentsAnimationCallback() { + ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback"); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimation-callback"); + } + + public static void logOnScrollerAnimationAborted() { + ActiveGestureLog.INSTANCE.addLog("scroller animation aborted", + ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "scroller animation aborted"); + } + + public static void logInputConsumerBecameActive(@NonNull String consumerName) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "%s became active", consumerName)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "%s became active", consumerName); + } + + public static void logTaskLaunchFailed(int launchedTaskId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Launch failed, task (id=%d) finished mid transition", launchedTaskId)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "Launch failed, task (id=%d) finished mid transition", launchedTaskId); + } + + public static void logOnPageEndTransition(int nextPageIndex) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "onPageEndTransition: current page index updated: %d", nextPageIndex)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "onPageEndTransition: current page index updated: %d", nextPageIndex); + } + + public static void logQuickSwitchFromHomeFallback(int taskIndex) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Quick switch from home fallback case: The TaskView at index %d is missing.", + taskIndex), + QUICK_SWITCH_FROM_HOME_FALLBACK); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "Quick switch from home fallback case: The TaskView at index %d is missing.", + taskIndex); + } + + public static void logQuickSwitchFromHomeFailed(int taskIndex) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Quick switch from home failed: TaskViews at indices %d and 0 are missing.", + taskIndex), + QUICK_SWITCH_FROM_HOME_FAILED); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "Quick switch from home failed: TaskViews at indices %d and 0 are missing.", + taskIndex); + } + + public static void logFinishRecentsAnimation(boolean toRecents) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "finishRecentsAnimation: %b", toRecents), + /* gestureEvent= */ FINISH_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimation: %b", toRecents); + } + + public static void logSetEndTarget(@NonNull String target) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "setEndTarget %s", target), /* gestureEvent= */ SET_END_TARGET); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "setEndTarget %s", target); + } + + public static void logStartHomeIntent(@NonNull String reason) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "OverviewComponentObserver.startHomeIntent: %s", reason)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "OverviewComponentObserver.startHomeIntent: %s", reason); + } + + public static void logRunningTaskPackage(@NonNull String packageName) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Current running task package name=%s", packageName)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "Current running task package name=%s", packageName); + } + + public static void logSysuiStateFlags(@NonNull String stateFlags) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Current SystemUi state flags=%s", stateFlags)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "Current SystemUi state flags=%s", stateFlags); + } + + public static void logSetInputConsumer(@NonNull String consumerName, @NonNull String reason) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "setInputConsumer: %s. reason(s):%s", consumerName, reason)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "setInputConsumer: %s. reason(s):%s", consumerName, reason); + } + + public static void logUpdateGestureStateRunningTask( + @NonNull String otherTaskPackage, @NonNull String runningTaskPackage) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Changing active task to %s because the previous task running on top of this " + + "one (%s) was excluded from recents", + otherTaskPackage, + runningTaskPackage)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "Changing active task to %s because the previous task running on top of this " + + "one (%s) was excluded from recents", + otherTaskPackage, + runningTaskPackage); + } + + public static void logOnInputEventActionUp( + int x, int y, int action, @NonNull String classification, int displayId) { + String actionString = MotionEvent.actionToString(action); + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "onMotionEvent(%d, %d): %s, %s, displayId=%d", + x, + y, + actionString, + classification, + displayId), + /* gestureEvent= */ action == ACTION_DOWN + ? MOTION_DOWN + : MOTION_UP); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "onMotionEvent(%d, %d): %s, %s, displayId=%d", + x, + y, + actionString, + classification, + displayId); + } + + public static void logOnInputEventActionMove( + @NonNull String action, + @NonNull String classification, + int pointerCount, + int displayId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "onMotionEvent: %s, %s, pointerCount: %d, displayId=%d", + action, + classification, + pointerCount, + displayId), + MOTION_MOVE); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "onMotionEvent: %s, %s, pointerCount: %d, displayId=%d", + action, + classification, + pointerCount, + displayId); + } + + public static void logOnInputEventGenericAction( + @NonNull String action, @NonNull String classification, int displayId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "onMotionEvent: %s, %s, displayId=%d", action, classification, displayId)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "onMotionEvent: %s, %s, displayId=%d", action, classification, displayId); + } + + public static void logOnInputEventNavModeSwitched( + int displayId, @NonNull String startNavMode, @NonNull String currentNavMode) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TIS.onInputEvent(displayId=%d): Navigation mode switched mid-gesture (%s -> %s); " + + "cancelling gesture.", + displayId, + startNavMode, + currentNavMode), + NAVIGATION_MODE_SWITCHED); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TIS.onInputEvent(displayId=%d): Navigation mode switched mid-gesture (%s -> %s); " + + "cancelling gesture.", + displayId, + startNavMode, + currentNavMode); + } + + public static void logUnknownInputEvent(int displayId, @NonNull String event) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TIS.onInputEvent(displayId=%d): Cannot process input event: " + + "received unknown event %s", displayId, event)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TIS.onInputEvent(displayId=%d): Cannot process input event: " + + "received unknown event %s", displayId, event); + } + + public static void logFinishRunningRecentsAnimation(boolean toHome) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "finishRunningRecentsAnimation: %b", toHome)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRunningRecentsAnimation: %b", toHome); + } + + public static void logOnRecentsAnimationStartCancelled() { + ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onAnimationStart (canceled): 0", + /* gestureEvent= */ ON_START_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onAnimationStart (canceled): 0"); + } + + public static void logOnRecentsAnimationStart(int appCount) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "RecentsAnimationCallbacks.onAnimationStart: %d", appCount), + /* gestureEvent= */ ON_START_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "RecentsAnimationCallbacks.onAnimationStart: %d", appCount); + } + + public static void logStartRecentsAnimationCallback(@NonNull String callback) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TaskAnimationManager.startRecentsAnimation(%s): " + + "Setting mRecentsAnimationStartPending = false", + callback)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TaskAnimationManager.startRecentsAnimation(%s): " + + "Setting mRecentsAnimationStartPending = false", + callback); + } + + public static void logSettingRecentsAnimationStartPending(boolean value) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TaskAnimationManager.startRecentsAnimation: " + + "Setting mRecentsAnimationStartPending = %b", + value)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TaskAnimationManager.startRecentsAnimation: " + + "Setting mRecentsAnimationStartPending = %b", + value); + } + + public static void logLaunchingSideTask(int taskId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Launching side task id=%d", taskId)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "Launching side task id=%d", taskId); + } + + public static void logOnInputEventActionDown( + int displayId, @NonNull ActiveGestureLog.CompoundString reason) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TIS.onMotionEvent(displayId=%d): ", displayId).append(reason)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "TIS.onMotionEvent(displayId=%d): %s", displayId, reason.toString()); + } + + public static void logStartNewTask(@NonNull ActiveGestureLog.CompoundString tasks) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Launching task: ").append(tasks)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: %s", tasks.toString()); + } + + public static void logMotionPauseDetectorEvent(@NonNull ActiveGestureLog.CompoundString event) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "MotionPauseDetector: ").append(event)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "MotionPauseDetector: %s", event.toString()); + } + + public static void logHandleTaskAppearedFailed( + @NonNull ActiveGestureLog.CompoundString reason) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "handleTaskAppeared check failed: ").append(reason)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "handleTaskAppeared check failed: %s", reason.toString()); + } + + /** + * This is for special cases where the string is purely dynamic and therefore has no format that + * can be extracted. Do not use in any other case. + */ + public static void logDynamicString( + @NonNull String string, + @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { + ActiveGestureLog.INSTANCE.addLog(string, gestureEvent); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "%s", string); + } + + public static void logOnSettledOnEndTarget(@NonNull String endTarget) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "onSettledOnEndTarget %s", endTarget), + /* gestureEvent= */ ON_SETTLED_ON_END_TARGET); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "onSettledOnEndTarget %s", endTarget); + } + + public static void logOnCalculateEndTarget(float velocityX, float velocityY, double angle) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "calculateEndTarget: velocities=(x=%fdp/ms, y=%fdp/ms), angle=%f", + velocityX, + velocityY, + angle), + velocityX == 0 && velocityY == 0 ? INVALID_VELOCITY_ON_SWIPE_UP : null); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "calculateEndTarget: velocities=(x=%fdp/ms, y=%fdp/ms), angle=%f", + velocityX, + velocityY, + angle); + } + + public static void logUnexpectedTaskAppeared(int taskId, @NonNull String packageName) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "Forcefully finishing recents animation: Unexpected task appeared id=%d, pkg=%s", + taskId, + packageName)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "Forcefully finishing recents animation: Unexpected task appeared id=%d, pkg=%s", + taskId, + packageName); + } + + public static void logCreateTouchRegionForDisplay(int displayRotation, + @NonNull Point displaySize, @NonNull RectF swipeRegion, @NonNull RectF ohmRegion, + int gesturalHeight, int largerGesturalHeight, @NonNull String reason) { + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "OrientationTouchTransformer.createRegionForDisplay: " + + "dispRot=%d, dispSize=%s, swipeRegion=%s, ohmRegion=%s, " + + "gesturalHeight=%d, largerGesturalHeight=%d, reason=%s", + displayRotation, displaySize.flattenToString(), swipeRegion.toShortString(), + ohmRegion.toShortString(), gesturalHeight, largerGesturalHeight, reason); + } + + public static void logOnTaskAnimationManagerNotAvailable(int displayId) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "TaskAnimationManager not available for displayId=%d", + displayId)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "TaskAnimationManager not available for displayId=%d", + displayId); + } + + public static void logGestureStartSwipeHandler(@NonNull String interactionHandler) { + ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString( + "OtherActivityInputConsumer.startTouchTrackingForWindowAnimation: " + + "interactionHandler=%s", interactionHandler)); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, + "OtherActivityInputConsumer.startTouchTrackingForWindowAnimation: " + + "interactionHandler=%s", interactionHandler); + } + + public static void logQueuingForceFinishRecentsAnimation() { + ActiveGestureLog.INSTANCE.addLog("Launcher destroyed while mRecentsAnimationStartPending ==" + + " true, queuing a callback to clean the pending animation up on start", + /* gestureEvent= */ ON_START_RECENTS_ANIMATION); + if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return; + ProtoLog.d(ACTIVE_GESTURE_LOG, "Launcher destroyed while mRecentsAnimationStartPending ==" + + " true, queuing a callback to clean the pending animation up on start"); + } +} diff --git a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java new file mode 100644 index 00000000000..2327cfcf1d8 --- /dev/null +++ b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java @@ -0,0 +1,133 @@ +/* + * 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.util.Log; + +import androidx.annotation.NonNull; + +import com.android.internal.protolog.ProtoLog; +import com.android.internal.protolog.common.IProtoLogGroup; + +import java.util.UUID; + +/** Enums used to interface with the ProtoLog API. */ +public enum QuickstepProtoLogGroup implements IProtoLogGroup { + + ACTIVE_GESTURE_LOG(true, true, Constants.DEBUG_ACTIVE_GESTURE, "ActiveGestureLog"), + RECENTS_WINDOW(true, true, Constants.DEBUG_RECENTS_WINDOW, "RecentsWindow"), + LAUNCHER_STATE_MANAGER(true, true, Constants.DEBUG_STATE_MANAGER, "LauncherStateManager"); + + private final boolean mEnabled; + private volatile boolean mLogToProto; + private volatile boolean mLogToLogcat; + private final @NonNull String mTag; + + public static boolean isProtoLogInitialized() { + if (!Variables.sIsInitialized) { + Log.w(Constants.TAG, + "Attempting to log to ProtoLog before initializing it.", + new IllegalStateException()); + } + return Variables.sIsInitialized; + } + + public static void initProtoLog() { + if (Variables.sIsInitialized) { + Log.e(Constants.TAG, + "Attempting to re-initialize ProtoLog.", new IllegalStateException()); + return; + } + Log.i(Constants.TAG, "Initializing ProtoLog."); + Variables.sIsInitialized = true; + ProtoLog.init(QuickstepProtoLogGroup.values()); + } + + /** + * @param enabled set to false to exclude all log statements for this group from + * compilation, + * they will not be available in runtime. + * @param logToProto enable binary logging for the group + * @param logToLogcat enable text logging for the group + * @param tag name of the source of the logged message + */ + QuickstepProtoLogGroup( + boolean enabled, boolean logToProto, boolean logToLogcat, @NonNull String tag) { + this.mEnabled = enabled; + this.mLogToProto = logToProto; + this.mLogToLogcat = logToLogcat; + this.mTag = tag; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public boolean isLogToProto() { + return mLogToProto; + } + + @Override + public boolean isLogToLogcat() { + return mLogToLogcat; + } + + @Override + public boolean isLogToAny() { + return mLogToLogcat || mLogToProto; + } + + @Override + public int getId() { + return Constants.LOG_START_ID + this.ordinal(); + } + + @Override + public @NonNull String getTag() { + return mTag; + } + + @Override + public void setLogToProto(boolean logToProto) { + this.mLogToProto = logToProto; + } + + @Override + public void setLogToLogcat(boolean logToLogcat) { + this.mLogToLogcat = logToLogcat; + } + + private static final class Variables { + + private static boolean sIsInitialized = false; + } + + private static final class Constants { + + private static final String TAG = "QuickstepProtoLogGroup"; + + private static final boolean DEBUG_ACTIVE_GESTURE = false; + private static final boolean DEBUG_RECENTS_WINDOW = false; + private static final boolean DEBUG_STATE_MANAGER = true; // b/279059025, b/325463989 + + private static final int LOG_START_ID = + (int) (UUID.nameUUIDFromBytes(QuickstepProtoLogGroup.class.getName().getBytes()) + .getMostSignificantBits() % Integer.MAX_VALUE); + } +} diff --git a/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java new file mode 100644 index 00000000000..99888fb2a31 --- /dev/null +++ b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.quickstep.util.QuickstepProtoLogGroup.RECENTS_WINDOW; +import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized; + +import android.window.DesktopModeFlags; + +import androidx.annotation.NonNull; + +import com.android.internal.protolog.ProtoLog; +import com.android.internal.protolog.common.IProtoLogGroup; +import com.android.launcher3.Flags; + +/** + * Proxy class used for Recents Window ProtoLog support. + *

+ * This file will have all of its static strings in the + * {@link ProtoLog#d(IProtoLogGroup, String, Object...)} calls replaced by dynamic code/strings. + *

+ * When a new Recents Window log needs to be added to the codebase, add it here under a new unique + * method. Or, if an existing entry needs to be modified, simply update it here. + */ +public class RecentsWindowProtoLogProxy { + private static final DesktopModeFlags.DesktopModeFlag ENABLE_RECENTS_WINDOW_PROTO_LOG = + new DesktopModeFlags.DesktopModeFlag(Flags::enableRecentsWindowProtoLog, true); + public static void logOnStateSetStart(@NonNull String stateName) { + if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(RECENTS_WINDOW, "onStateSetStart: %s", stateName); + } + + public static void logOnStateSetEnd(@NonNull String stateName) { + if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(RECENTS_WINDOW, "onStateSetEnd: %s", stateName); + } + + public static void logStartRecentsWindow(boolean isShown, boolean windowViewIsNull) { + if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(RECENTS_WINDOW, + "Starting recents window: isShow= %b, windowViewIsNull=%b", + isShown, + windowViewIsNull); + } + + public static void logCleanup(boolean isShown) { + if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return; + ProtoLog.d(RECENTS_WINDOW, "Cleaning up recents window: isShow= %b", isShown); + } +} diff --git a/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt b/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt new file mode 100644 index 00000000000..2f1f0b5f6f7 --- /dev/null +++ b/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.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.taskbar.bubbles.testing + +import android.app.Notification +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.PathParser +import android.view.LayoutInflater +import android.view.ViewGroup +import com.android.launcher3.R +import com.android.launcher3.taskbar.bubbles.BubbleBarBubble +import com.android.launcher3.taskbar.bubbles.BubbleView +import com.android.wm.shell.shared.bubbles.BubbleInfo + +object FakeBubbleViewFactory { + + /** Inflates a [BubbleView] and adds it to the [parent] view if it is present. */ + fun createBubble( + context: Context, + key: String, + parent: ViewGroup?, + iconSize: Int = 50, + iconColor: Int, + badgeColor: Int = Color.RED, + dotColor: Int = Color.BLUE, + suppressNotification: Boolean = false, + ): BubbleView { + val inflater = LayoutInflater.from(context) + // BubbleView uses launcher's badge to icon ratio and expects the badge image to already + // have the right size + val badgeToIconRatio = 0.444f + val badgeRadius = iconSize * badgeToIconRatio / 2 + val icon = createCircleBitmap(radius = iconSize / 2, color = iconColor) + val badge = createCircleBitmap(radius = badgeRadius.toInt(), color = badgeColor) + + val flags = + if (suppressNotification) Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION else 0 + val bubbleInfo = + BubbleInfo( + key, + flags, + null, + null, + 0, + context.packageName, + null, + null, + false, + true, + null, + ) + val bubbleView = inflater.inflate(R.layout.bubblebar_item_view, parent, false) as BubbleView + val dotPath = + PathParser.createPathFromPathData( + context.resources.getString(com.android.internal.R.string.config_icon_mask) + ) + val bubble = + BubbleBarBubble( + bubbleInfo, + bubbleView, + badge, + icon, + dotColor, + dotPath, + "test app", + null, + ) + bubbleView.setBubble(bubble) + return bubbleView + } + + private fun createCircleBitmap(radius: Int, color: Int): Bitmap { + val bitmap = Bitmap.createBitmap(radius * 2, radius * 2, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + canvas.drawARGB(0, 0, 0, 0) + val paint = Paint() + paint.color = color + canvas.drawCircle(radius.toFloat(), radius.toFloat(), radius.toFloat(), paint) + return bitmap + } +} diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewScreenshotTest.kt new file mode 100644 index 00000000000..b5a418b04de --- /dev/null +++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewScreenshotTest.kt @@ -0,0 +1,143 @@ +/* + * 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.content.Context +import android.graphics.Color +import android.platform.test.rule.ScreenRecordRule +import android.view.View +import android.widget.FrameLayout +import android.widget.FrameLayout.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout.LayoutParams.WRAP_CONTENT +import androidx.activity.ComponentActivity +import androidx.test.core.app.ApplicationProvider +import com.android.launcher3.R +import com.android.launcher3.taskbar.bubbles.testing.FakeBubbleViewFactory +import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.Displays +import platform.test.screenshot.ViewScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +/** Screenshot tests for [BubbleBarView]. */ +@RunWith(ParameterizedAndroidJunit4::class) +@ScreenRecordRule.ScreenRecord +class BubbleBarViewScreenshotTest(emulationSpec: DeviceEmulationSpec) { + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var bubbleBarView: BubbleBarView + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = + DeviceEmulationSpec.forDisplays( + Displays.Phone, + isDarkTheme = false, + isLandscape = false, + ) + } + + @get:Rule + val screenshotRule = + ViewScreenshotTestRule( + emulationSpec, + ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)), + ) + + @Test + fun bubbleBarView_collapsed_oneBubble() { + screenshotRule.screenshotTest("bubbleBarView_collapsed_oneBubble") { activity -> + activity.actionBar?.hide() + setupBubbleBarView() + bubbleBarView.addBubble(createBubble("key1", Color.GREEN)) + val container = FrameLayout(context) + val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + container.layoutParams = lp + container.addView(bubbleBarView) + container + } + } + + @Test + fun bubbleBarView_collapsed_twoBubbles() { + screenshotRule.screenshotTest("bubbleBarView_collapsed_twoBubbles") { activity -> + activity.actionBar?.hide() + setupBubbleBarView() + bubbleBarView.addBubble(createBubble("key1", Color.GREEN)) + bubbleBarView.addBubble(createBubble("key2", Color.CYAN)) + val container = FrameLayout(context) + val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + container.layoutParams = lp + container.addView(bubbleBarView) + container + } + } + + @Test + fun bubbleBarView_expanded_threeBubbles() { + // if we're still expanding, wait with taking a screenshot + val shouldWait: (ComponentActivity, View) -> Boolean = { _, _ -> bubbleBarView.isExpanding } + // increase the frame limit to allow the animation to end before taking the screenshot + screenshotRule.frameLimit = 500 + screenshotRule.screenshotTest( + "bubbleBarView_expanded_threeBubbles", + checkView = shouldWait, + ) { activity -> + activity.actionBar?.hide() + setupBubbleBarView() + bubbleBarView.addBubble(createBubble("key1", Color.GREEN)) + bubbleBarView.addBubble(createBubble("key2", Color.CYAN)) + bubbleBarView.addBubble(createBubble("key3", Color.MAGENTA)) + val container = FrameLayout(context) + val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + container.layoutParams = lp + container.addView(bubbleBarView) + bubbleBarView.isExpanded = true + container + } + } + + private fun setupBubbleBarView() { + bubbleBarView = BubbleBarView(context) + val lp = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT) + bubbleBarView.layoutParams = lp + val paddingTop = + context.resources.getDimensionPixelSize(R.dimen.bubblebar_pointer_visible_size) + bubbleBarView.setPadding(0, paddingTop, 0, 0) + bubbleBarView.visibility = View.VISIBLE + bubbleBarView.alpha = 1f + } + + private fun createBubble(key: String, color: Int): BubbleView { + val bubbleView = + FakeBubbleViewFactory.createBubble( + context, + key, + parent = bubbleBarView, + iconColor = color, + ) + bubbleView.showDotIfNeeded(1f) + bubbleBarView.setSelectedBubble(bubbleView) + return bubbleView + } +} diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewScreenshotTest.kt new file mode 100644 index 00000000000..47f393ffc16 --- /dev/null +++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewScreenshotTest.kt @@ -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.taskbar.bubbles + +import android.content.Context +import android.graphics.Color +import androidx.test.core.app.ApplicationProvider +import com.android.launcher3.taskbar.bubbles.testing.FakeBubbleViewFactory +import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.Displays +import platform.test.screenshot.ViewScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +/** Screenshot tests for [BubbleView]. */ +@RunWith(ParameterizedAndroidJunit4::class) +class BubbleViewScreenshotTest(emulationSpec: DeviceEmulationSpec) { + + private val context = ApplicationProvider.getApplicationContext() + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = + DeviceEmulationSpec.forDisplays( + Displays.Phone, + isDarkTheme = false, + isLandscape = false, + ) + } + + @get:Rule + val screenshotRule = + ViewScreenshotTestRule( + emulationSpec, + ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)), + ) + + @Test + fun bubbleView_hasUnseenContent() { + screenshotRule.screenshotTest("bubbleView_hasUnseenContent") { activity -> + activity.actionBar?.hide() + setupBubbleView() + } + } + + @Test + fun bubbleView_seen() { + screenshotRule.screenshotTest("bubbleView_seen") { activity -> + activity.actionBar?.hide() + setupBubbleView(suppressNotification = true) + } + } + + @Test + fun bubbleView_badgeHidden() { + screenshotRule.screenshotTest("bubbleView_badgeHidden") { activity -> + activity.actionBar?.hide() + setupBubbleView().apply { setBadgeScale(0f) } + } + } + + private fun setupBubbleView(suppressNotification: Boolean = false): BubbleView { + val bubbleView = + FakeBubbleViewFactory.createBubble( + context, + key = "key", + parent = null, + iconSize = 100, + iconColor = Color.LTGRAY, + suppressNotification = suppressNotification, + ) + bubbleView.showDotIfNeeded(1f) + return bubbleView + } +} diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/OWNERS new file mode 100644 index 00000000000..63c14984b90 --- /dev/null +++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/OWNERS @@ -0,0 +1,4 @@ +atsjenk@google.com +liranb@google.com +madym@google.com +mpodolian@google.com diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt new file mode 100644 index 00000000000..11c7fe98237 --- /dev/null +++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.flyout + +import android.content.Context +import android.graphics.Color +import android.graphics.PointF +import android.graphics.drawable.ColorDrawable +import androidx.test.core.app.ApplicationProvider +import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.Displays +import platform.test.screenshot.ViewScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +/** Screenshot tests for [BubbleBarFlyoutView]. */ +@RunWith(ParameterizedAndroidJunit4::class) +class BubbleBarFlyoutViewScreenshotTest(emulationSpec: DeviceEmulationSpec) { + + private val context = ApplicationProvider.getApplicationContext() + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = + DeviceEmulationSpec.forDisplays( + Displays.Phone, + isDarkTheme = false, + isLandscape = false, + ) + } + + @get:Rule + val screenshotRule = + ViewScreenshotTestRule( + emulationSpec, + ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)), + ) + + @Test + fun bubbleBarFlyoutView_noAvatar_onRight() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onRight") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage(icon = null, title = "sender", message = "message") + ) {} + flyout.updateExpansionProgress(1f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_noAvatar_onLeft() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onLeft") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage(icon = null, title = "sender", message = "message") + ) {} + flyout.updateExpansionProgress(1f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_noAvatar_longMessage() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_longMessage") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = null, + title = "sender", + message = "really, really, really, really, really long message. like really.", + ) + ) {} + flyout.updateExpansionProgress(1f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_avatar_onRight() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onRight") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = ColorDrawable(Color.RED), + title = "sender", + message = "message", + ) + ) {} + flyout.updateExpansionProgress(1f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_avatar_onLeft() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onLeft") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = ColorDrawable(Color.RED), + title = "sender", + message = "message", + ) + ) {} + flyout.updateExpansionProgress(1f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_avatar_longMessage() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_longMessage") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = ColorDrawable(Color.RED), + title = "sender", + message = "really, really, really, really, really long message. like really.", + ) + ) {} + flyout.updateExpansionProgress(1f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_collapsed_onLeft() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_collapsed_onLeft") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = ColorDrawable(Color.RED), + title = "sender", + message = "collapsed on left", + ) + ) {} + flyout.updateExpansionProgress(0f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_collapsed_onRight() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_collapsed_onRight") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false)) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = ColorDrawable(Color.RED), + title = "sender", + message = "collapsed on right", + ) + ) {} + flyout.updateExpansionProgress(0f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_90p_onLeft() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_90p_onLeft") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView( + context, + FakeBubbleBarFlyoutPositioner( + isOnLeft = true, + distanceToCollapsedPosition = PointF(100f, 100f), + ), + ) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = ColorDrawable(Color.RED), + title = "sender", + message = "expanded 90% on left", + ) + ) {} + flyout.updateExpansionProgress(0.9f) + flyout + } + } + + @Test + fun bubbleBarFlyoutView_80p_onRight() { + screenshotRule.screenshotTest("bubbleBarFlyoutView_80p_onRight") { activity -> + activity.actionBar?.hide() + val flyout = + BubbleBarFlyoutView( + context, + FakeBubbleBarFlyoutPositioner( + isOnLeft = false, + distanceToCollapsedPosition = PointF(200f, 100f), + ), + ) + flyout.showFromCollapsed( + BubbleBarFlyoutMessage( + icon = ColorDrawable(Color.RED), + title = "sender", + message = "expanded 80% on right", + ) + ) {} + flyout.updateExpansionProgress(0.8f) + flyout + } + } + + private class FakeBubbleBarFlyoutPositioner( + override val isOnLeft: Boolean, + override val distanceToCollapsedPosition: PointF = PointF(0f, 0f), + ) : BubbleBarFlyoutPositioner { + override val targetTy = 0f + override val collapsedSize = 30f + override val collapsedColor = Color.BLUE + override val collapsedElevation = 1f + override val distanceToRevealTriangle = 10f + } +} diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt new file mode 100644 index 00000000000..80b2c16e103 --- /dev/null +++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.task.thumbnail + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.drawable.BitmapDrawable +import android.view.LayoutInflater +import android.view.Surface.ROTATION_0 +import androidx.core.graphics.set +import com.android.launcher3.R +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized +import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.Displays +import platform.test.screenshot.ViewScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +/** Screenshot tests for [TaskThumbnailView]. */ +@RunWith(ParameterizedAndroidJunit4::class) +class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) { + + @get:Rule + val screenshotRule = + ViewScreenshotTestRule( + emulationSpec, + ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)), + ) + + @Test + fun taskThumbnailView_uninitializedByDefault() { + screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity) + } + } + + @Test + fun taskThumbnailView_resetsToUninitialized() { + screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity -> + activity.actionBar?.hide() + val taskThumbnailView = createTaskThumbnailView(activity) + taskThumbnailView.setState(Uninitialized) + taskThumbnailView + } + } + + @Test + fun taskThumbnailView_recyclesToUninitialized() { + screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity -> + activity.actionBar?.hide() + val taskThumbnailView = createTaskThumbnailView(activity) + taskThumbnailView.setState(BackgroundOnly(Color.YELLOW)) + taskThumbnailView.onRecycle() + taskThumbnailView + } + } + + @Test + fun taskThumbnailView_backgroundOnly() { + screenshotRule.screenshotTest("taskThumbnailView_backgroundOnly") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { setState(BackgroundOnly(Color.YELLOW)) } + } + } + + @Test + fun taskThumbnailView_liveTile_withoutHeader() { + screenshotRule.screenshotTest("taskThumbnailView_liveTile") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + setState(TaskThumbnailUiState.LiveTile.WithoutHeader) + } + } + } + + @Test + fun taskThumbnailView_image_withoutHeader() { + screenshotRule.screenshotTest("taskThumbnailView_image") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + setState( + SnapshotSplash( + Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY), + null, + ) + ) + } + } + } + + @Test + fun taskThumbnailView_image_withoutHeader_withImageMatrix() { + screenshotRule.screenshotTest("taskThumbnailView_image_withMatrix") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200 + setState( + SnapshotSplash( + Snapshot.WithoutHeader( + createBitmap( + width = VIEW_ENV_WIDTH / 2, + height = lessThanHeightMatchingAspectRatio, + ), + ROTATION_0, + Color.DKGRAY, + ), + null, + ) + ) + setImageMatrix(Matrix().apply { postScale(2f, 2f) }) + } + } + } + + @Test + fun taskThumbnailView_splash_withoutHeader() { + screenshotRule.screenshotTest("taskThumbnailView_partial_splash") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + setState( + SnapshotSplash( + Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY), + BitmapDrawable(activity.resources, createSplash()), + ) + ) + updateSplashAlpha(0.5f) + } + } + } + + @Test + fun taskThumbnailView_splash_withoutHeader_withImageMatrix() { + screenshotRule.screenshotTest("taskThumbnailView_partial_splash_withMatrix") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200 + setState( + SnapshotSplash( + Snapshot.WithoutHeader( + createBitmap( + width = VIEW_ENV_WIDTH / 2, + height = lessThanHeightMatchingAspectRatio, + ), + ROTATION_0, + Color.DKGRAY, + ), + BitmapDrawable(activity.resources, createSplash()), + ) + ) + setImageMatrix(Matrix().apply { postScale(2f, 2f) }) + updateSplashAlpha(0.5f) + } + } + } + + @Test + fun taskThumbnailView_dimmed_tintAmount() { + screenshotRule.screenshotTest("taskThumbnailView_dimmed_40") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + setState(BackgroundOnly(Color.YELLOW)) + updateTintAmount(.4f) + } + } + } + + @Test + fun taskThumbnailView_dimmed_menuOpen() { + screenshotRule.screenshotTest("taskThumbnailView_dimmed_40") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + setState(BackgroundOnly(Color.YELLOW)) + updateMenuOpenProgress(1f) + } + } + } + + @Test + fun taskThumbnailView_dimmed_tintAmountAndMenuOpen() { + screenshotRule.screenshotTest("taskThumbnailView_dimmed_80") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + setState(BackgroundOnly(Color.YELLOW)) + updateTintAmount(.8f) + updateMenuOpenProgress(1f) + } + } + } + + @Test + fun taskThumbnailView_scaled_roundRoundedCorners() { + screenshotRule.screenshotTest("taskThumbnailView_scaledRoundedCorners") { activity -> + activity.actionBar?.hide() + createTaskThumbnailView(activity).apply { + scaleX = 0.75f + scaleY = 0.3f + setState(BackgroundOnly(Color.YELLOW)) + } + } + } + + private fun createTaskThumbnailView(context: Context): TaskThumbnailView { + val taskThumbnailView = + LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false) + as TaskThumbnailView + taskThumbnailView.cornerRadius = CORNER_RADIUS + return taskThumbnailView + } + + private fun createSplash() = createBitmap(width = 20, height = 20, rectColorRotation = 1) + + private fun createBitmap( + width: Int = VIEW_ENV_WIDTH, + height: Int = VIEW_ENV_HEIGHT, + rectColorRotation: Int = 0, + ) = + Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply { + Canvas(this).apply { + val paint = Paint() + paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4] + drawRect(0f, 0f, width / 2f, height / 2f, paint) + paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4] + drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint) + paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4] + drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint) + paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4] + drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint) + } + } + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = + DeviceEmulationSpec.forDisplays( + Displays.Phone, + isDarkTheme = false, + isLandscape = false, + ) + + const val CORNER_RADIUS = 56f + val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN) + const val VIEW_ENV_WIDTH = 1440 + const val VIEW_ENV_HEIGHT = 3120 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt new file mode 100644 index 00000000000..7ebef45d275 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt @@ -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.desktop + +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.Context +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_TO_FRONT +import android.window.TransitionFilter +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.quickstep.SystemUiProxy +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DesktopAppLaunchTransitionManagerTest { + + @get:Rule val mSetFlagsRule = SetFlagsRule() + + private val context = mock() + private val systemUiProxy = mock() + private lateinit var transitionManager: DesktopAppLaunchTransitionManager + + @Before + fun setUp() { + whenever(context.resources).thenReturn(mock()) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + transitionManager = DesktopAppLaunchTransitionManager(context, systemUiProxy) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + fun registerTransitions_appLaunchFlagEnabled_registersTransition() { + transitionManager.registerTransitions() + + verify(systemUiProxy, times(1)).registerRemoteTransition(any(), any()) + } + + @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + fun registerTransitions_appLaunchFlagDisabled_doesntRegisterTransition() { + transitionManager.registerTransitions() + + verify(systemUiProxy, times(0)).registerRemoteTransition(any(), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + fun registerTransitions_usesCorrectFilter() { + transitionManager.registerTransitions() + val filterArgumentCaptor = argumentCaptor() + + verify(systemUiProxy, times(1)) + .registerRemoteTransition(any(), filterArgumentCaptor.capture()) + + assertThat(filterArgumentCaptor.lastValue).isNotNull() + assertThat(filterArgumentCaptor.lastValue.mTypeSet) + .isEqualTo(intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)) + assertThat(filterArgumentCaptor.lastValue.mRequirements).hasLength(1) + val launchRequirement = filterArgumentCaptor.lastValue.mRequirements!![0] + assertThat(launchRequirement.mModes).isEqualTo(intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)) + assertThat(launchRequirement.mActivityType).isEqualTo(ACTIVITY_TYPE_STANDARD) + assertThat(launchRequirement.mWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java index d4dd58040ac..91f9e53eaaa 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java @@ -35,10 +35,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; +import com.android.launcher3.util.AllModulesForTest; +import com.android.launcher3.util.SandboxContext; import com.android.launcher3.util.UserIconInfo; import com.android.systemui.shared.system.SysUiStatsLog; @@ -51,6 +54,9 @@ import java.util.Arrays; +import dagger.BindsInstance; +import dagger.Component; + @SmallTest @RunWith(AndroidJUnit4.class) public class AppEventProducerTest { @@ -72,7 +78,9 @@ public class AppEventProducerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = new SandboxContext(getApplicationContext()); - mContext.putObject(UserCache.INSTANCE, mUserCache); + mContext.initDaggerComponent( + DaggerAppEventProducerTest_TestComponent.builder().bindUserCache(mUserCache) + ); mAppEventProducer = new AppEventProducer(mContext, null); } @@ -129,4 +137,15 @@ private LauncherAtom.ItemInfo buildItemInfoProtoForAppInfo(AppInfo appInfo) { .build()); return itemBuilder.build(); } + + @LauncherAppSingleton + @Component(modules = { AllModulesForTest.class }) + interface TestComponent extends LauncherAppComponent { + @Component.Builder + interface Builder extends LauncherAppComponent.Builder { + @BindsInstance + AppEventProducerTest.TestComponent.Builder bindUserCache(UserCache userCache); + @Override LauncherAppComponent build(); + } + } } diff --git a/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt similarity index 73% rename from quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt rename to quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt index a5327628d16..1c7af146675 100644 --- a/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt @@ -1,10 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.launcher3.model import android.app.prediction.AppPredictor import android.app.prediction.AppTarget import android.app.prediction.AppTargetEvent import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS @@ -19,7 +33,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations /** Unit tests for [QuickstepModelDelegate]. */ @@ -39,12 +53,18 @@ class QuickstepModelDelegateTest { fun setUp() { MockitoAnnotations.initMocks(this) modelHelper = LauncherModelHelper() - underTest = QuickstepModelDelegate(modelHelper.sandboxContext) + underTest = + QuickstepModelDelegate( + modelHelper.sandboxContext, + modelHelper.sandboxContext.appComponent.idp, + modelHelper.sandboxContext.appComponent.packageManagerHelper, + "", /* dbFileName */ + ) underTest.mAllAppsState.predictor = allAppsPredictor underTest.mHotseatState.predictor = hotseatPredictor underTest.mWidgetsRecommendationState.predictor = widgetRecommendationPredictor - underTest.mApp = LauncherAppState.getInstance(modelHelper.sandboxContext) - underTest.mDataModel = BgDataModel() + underTest.mModel = modelHelper.model + underTest.mDataModel = BgDataModel(WidgetsModel(modelHelper.sandboxContext)) } @After @@ -57,25 +77,25 @@ class QuickstepModelDelegateTest { underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_PREDICTION) verify(allAppsPredictor).notifyAppTargetEvent(mockedAppTargetEvent) - verifyZeroInteractions(hotseatPredictor) - verifyZeroInteractions(widgetRecommendationPredictor) + verifyNoMoreInteractions(hotseatPredictor) + verifyNoMoreInteractions(widgetRecommendationPredictor) } @Test fun onWidgetPrediction_notifyWidgetRecommendationPredictor() { underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WIDGETS_PREDICTION) - verifyZeroInteractions(allAppsPredictor) + verifyNoMoreInteractions(allAppsPredictor) verify(widgetRecommendationPredictor).notifyAppTargetEvent(mockedAppTargetEvent) - verifyZeroInteractions(hotseatPredictor) + verifyNoMoreInteractions(hotseatPredictor) } @Test fun onHotseatPrediction_notifyHotseatPredictor() { underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_HOTSEAT_PREDICTION) - verifyZeroInteractions(allAppsPredictor) - verifyZeroInteractions(widgetRecommendationPredictor) + verifyNoMoreInteractions(allAppsPredictor) + verifyNoMoreInteractions(widgetRecommendationPredictor) verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent) } @@ -83,8 +103,8 @@ class QuickstepModelDelegateTest { fun onOtherClient_notifyHotseatPredictor() { underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WALLPAPERS) - verifyZeroInteractions(allAppsPredictor) - verifyZeroInteractions(widgetRecommendationPredictor) + verifyNoMoreInteractions(allAppsPredictor) + verifyNoMoreInteractions(widgetRecommendationPredictor) verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent) } diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt similarity index 64% rename from quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt rename to quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt index 5c7b4aba4da..d445189039a 100644 --- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt @@ -16,6 +16,8 @@ package com.android.launcher3.model +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 @@ -30,14 +32,21 @@ 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.LAUNCH_LOCATION 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.ComponentKey +import com.android.launcher3.util.Executors +import com.android.launcher3.util.Executors.MODEL_EXECUTOR +import com.android.launcher3.util.TestUtil import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo import com.android.launcher3.widget.LauncherAppWidgetProviderInfo +import com.android.launcher3.widget.PendingAddWidgetInfo import com.google.common.truth.Truth.assertThat +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import java.util.function.Predicate import junit.framework.Assert.assertNotNull import org.junit.Before @@ -45,6 +54,9 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class WidgetsPredictionsRequesterTest { @@ -62,15 +74,30 @@ class WidgetsPredictionsRequesterTest { private lateinit var widgetItem1b: WidgetItem private lateinit var widgetItem2: WidgetItem - private lateinit var allWidgets: Map> + private lateinit var allWidgets: Map @Mock private lateinit var iconCache: IconCache + @Mock private lateinit var apmMock: AppPredictionManager + + @Mock private lateinit var predictorMock: AppPredictor + @Before fun setUp() { MockitoAnnotations.initMocks(this) mUserHandle = myUserHandle() - context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) + + whenever(apmMock.createAppPredictionSession(any())).thenReturn(predictorMock) + + context = + object : ActivityContextWrapper(ApplicationProvider.getApplicationContext()) { + override fun getSystemService(name: String): Any? { + if (name == "app_prediction") { + return apmMock + } + return super.getSystemService(name) + } + } testInvariantProfile = LauncherAppState.getIDP(context) deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context) @@ -93,9 +120,9 @@ class WidgetsPredictionsRequesterTest { allWidgets = mapOf( - PackageUserKey(APP_1_PACKAGE_NAME, mUserHandle) to - listOf(widgetItem1a, widgetItem1b), - PackageUserKey(APP_2_PACKAGE_NAME, mUserHandle) to listOf(widgetItem2), + ComponentKey(widgetItem1a.componentName, widgetItem1a.user) to widgetItem1a, + ComponentKey(widgetItem1b.componentName, widgetItem1b.user) to widgetItem1b, + ComponentKey(widgetItem2.componentName, widgetItem2.user) to widgetItem2, ) } @@ -103,7 +130,7 @@ class WidgetsPredictionsRequesterTest { fun buildBundleForPredictionSession_includesAddedAppWidgets() { val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info) - val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE) + val bundle = buildBundleForPredictionSession(existingWidgets) val addedWidgetsBundleExtra = bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java) @@ -113,21 +140,67 @@ class WidgetsPredictionsRequesterTest { buildExpectedAppTargetEvent( /*pkg=*/ APP_1_PACKAGE_NAME, /*providerClassName=*/ APP_1_PROVIDER_A_CLASS_NAME, - /*user=*/ mUserHandle + /*user=*/ mUserHandle, ), buildExpectedAppTargetEvent( /*pkg=*/ APP_1_PACKAGE_NAME, /*providerClassName=*/ APP_1_PROVIDER_B_CLASS_NAME, - /*user=*/ mUserHandle + /*user=*/ mUserHandle, ), buildExpectedAppTargetEvent( /*pkg=*/ APP_2_PACKAGE_NAME, /*providerClassName=*/ APP_2_PROVIDER_1_CLASS_NAME, - /*user=*/ mUserHandle - ) + /*user=*/ mUserHandle, + ), ) } + @Test + fun request_invokesCallbackWithPredictedItems() { + TestUtil.runOnExecutorSync(MODEL_EXECUTOR) { + val underTest = WidgetPredictionsRequester(context, TEST_UI_SURFACE, allWidgets) + val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo) + val predictions = + listOf( + // (existing) 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, + ), + ) + doAnswer { + underTest.onTargetsAvailable(predictions) + null + } + .whenever(predictorMock) + .requestPredictionUpdate() + val testCountDownLatch = CountDownLatch(1) + val listener = + WidgetPredictionsRequester.WidgetPredictionsListener { itemInfos -> + if (itemInfos.size == 1 && itemInfos[0] is PendingAddWidgetInfo) { + // only one item was eligible. + testCountDownLatch.countDown() + } else { + println("Unexpected prediction items found: ${itemInfos.size}") + } + } + + underTest.request(existingWidgets, listener) + TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {} + + assertThat(testCountDownLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue() + } + } + @Test fun filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictions() { val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo) @@ -140,15 +213,15 @@ class WidgetsPredictionsRequesterTest { AppTargetId(APP_1_PACKAGE_NAME), APP_1_PACKAGE_NAME, APP_1_PROVIDER_B_CLASS_NAME, - mUserHandle + mUserHandle, ), // eligible AppTarget( AppTargetId(APP_2_PACKAGE_NAME), APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME, - mUserHandle - ) + mUserHandle, + ), ) // only 2 was eligible @@ -156,7 +229,7 @@ class WidgetsPredictionsRequesterTest { } @Test - fun filterPredictions_appPredictions_returnsWidgetFromPackage() { + fun filterPredictions_appPredictions_returnsEmptyList() { val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo) val filter: Predicate = notOnUiSurfaceFilter(widgetsAlreadyOnSurface) @@ -166,28 +239,27 @@ class WidgetsPredictionsRequesterTest { AppTargetId(APP_1_PACKAGE_NAME), APP_1_PACKAGE_NAME, "$APP_1_PACKAGE_NAME.SomeActivity", - mUserHandle + mUserHandle, ), AppTarget( AppTargetId(APP_2_PACKAGE_NAME), APP_2_PACKAGE_NAME, "$APP_2_PACKAGE_NAME.SomeActivity2", - mUserHandle + mUserHandle, ), ) - assertThat(filterPredictions(predictions, allWidgets, filter)) - .containsExactly(widgetItem1a, widgetItem2) + assertThat(filterPredictions(predictions, allWidgets, filter)).isEmpty() } - private fun createWidgetItem( - providerInfo: AppWidgetProviderInfo, - ): WidgetItem { + private fun createWidgetItem(providerInfo: AppWidgetProviderInfo): WidgetItem { val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo) return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context) } companion object { + const val TEST_TIMEOUT = 3L + const val TEST_UI_SURFACE = "widgets_test" const val BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets" @@ -203,18 +275,18 @@ class WidgetsPredictionsRequesterTest { private fun buildExpectedAppTargetEvent( pkg: String, providerClassName: String, - userHandle: UserHandle + userHandle: UserHandle, ): AppTargetEvent { val appTarget = AppTarget.Builder( /*id=*/ AppTargetId("widget:$pkg"), /*packageName=*/ pkg, - /*user=*/ userHandle + /*user=*/ userHandle, ) .setClassName(providerClassName) .build() return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN) - .setLaunchLocation(TEST_UI_SURFACE) + .setLaunchLocation(LAUNCH_LOCATION) .build() } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt new file mode 100644 index 00000000000..42adfec82c2 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt @@ -0,0 +1,228 @@ +/* + * 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.ComponentName +import android.content.Intent +import android.os.Process +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.Flags.enableRefactorTaskThumbnail +import com.android.launcher3.dagger.LauncherAppComponent +import com.android.launcher3.dagger.LauncherAppSingleton +import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE +import com.android.launcher3.model.data.TaskViewItemInfo.Companion.createTaskViewAtom +import com.android.launcher3.pm.UserCache +import com.android.launcher3.util.AllModulesForTest +import com.android.launcher3.util.SandboxContext +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.launcher3.util.TransformingTouchDelegate +import com.android.launcher3.util.UserIconInfo +import com.android.quickstep.TaskOverlayFactory +import com.android.quickstep.TaskOverlayFactory.TaskOverlay +import com.android.quickstep.recents.di.RecentsDependencies +import com.android.quickstep.task.thumbnail.TaskThumbnailView +import com.android.quickstep.views.RecentsView +import com.android.quickstep.views.TaskContainer +import com.android.quickstep.views.TaskThumbnailViewDeprecated +import com.android.quickstep.views.TaskView +import com.android.quickstep.views.TaskViewIcon +import com.android.quickstep.views.TaskViewType +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.Task.TaskKey +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import org.junit.After +import org.junit.Before +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 [TaskViewItemInfo] */ +@RunWith(AndroidJUnit4::class) +class TaskViewItemInfoTest { + private val context = SandboxContext(InstrumentationRegistry.getInstrumentation().targetContext) + private val taskView = mock() + private val recentsView = mock>() + private val overlayFactory = mock() + private val userCache = mock() + private val userInfo = mock() + + @Before + fun setUp() { + whenever(overlayFactory.createOverlay(any())).thenReturn(mock>()) + whenever(taskView.context).thenReturn(context) + whenever(taskView.recentsView).thenReturn(recentsView) + whenever(recentsView.indexOfChild(taskView)).thenReturn(TASK_VIEW_INDEX) + whenever(userInfo.isPrivate).thenReturn(false) + whenever(userCache.getUserInfo(any())).thenReturn(userInfo) + context.initDaggerComponent( + DaggerTaskViewItemInfoTest_TestComponent.builder().bindUserCache(userCache) + ) + RecentsDependencies.maybeInitialize(context) + } + + @After + fun tearDown() { + RecentsDependencies.destroy(context) + } + + @Test + fun singleTask() { + val taskContainers = listOf(createTaskContainer(createTask(1))) + whenever(taskView.type).thenReturn(TaskViewType.SINGLE) + whenever(taskView.taskContainers).thenReturn(taskContainers) + + val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0]) + + assertThat(taskViewItemInfo.taskViewAtom) + .isEqualTo( + createTaskViewAtom( + type = 0, + index = TASK_VIEW_INDEX, + componentName = "${PACKAGE}/${CLASS}", + cardinality = 1, + ) + ) + assertThat(taskViewItemInfo.runtimeStatusFlags and FLAG_NOT_PINNABLE).isEqualTo(0) + } + + @Test + fun splitTask() { + val taskContainers = + listOf(createTaskContainer(createTask(1)), createTaskContainer(createTask(2))) + whenever(taskView.type).thenReturn(TaskViewType.GROUPED) + whenever(taskView.taskContainers).thenReturn(taskContainers) + + val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0]) + + assertThat(taskViewItemInfo.taskViewAtom) + .isEqualTo( + createTaskViewAtom( + type = 1, + index = TASK_VIEW_INDEX, + componentName = "${PACKAGE}/${CLASS}", + cardinality = 2, + ) + ) + assertThat(taskViewItemInfo.runtimeStatusFlags and FLAG_NOT_PINNABLE).isEqualTo(0) + } + + @Test + fun desktopTask() { + val taskContainers = + listOf( + createTaskContainer(createTask(1)), + createTaskContainer(createTask(2)), + createTaskContainer(createTask(3)), + ) + whenever(taskView.type).thenReturn(TaskViewType.DESKTOP) + whenever(taskView.taskContainers).thenReturn(taskContainers) + + val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0]) + + assertThat(taskViewItemInfo.taskViewAtom) + .isEqualTo( + createTaskViewAtom( + type = 2, + index = TASK_VIEW_INDEX, + componentName = "${PACKAGE}/${CLASS}", + cardinality = 3, + ) + ) + assertThat(taskViewItemInfo.runtimeStatusFlags and FLAG_NOT_PINNABLE).isEqualTo(0) + } + + @Test + fun privateTask() { + val taskContainers = listOf(createTaskContainer(createTask(1))) + whenever(taskView.type).thenReturn(TaskViewType.SINGLE) + whenever(taskView.taskContainers).thenReturn(taskContainers) + whenever(userInfo.isPrivate).thenReturn(true) + + val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0]) + + assertThat(taskViewItemInfo.taskViewAtom) + .isEqualTo( + createTaskViewAtom( + type = 0, + index = TASK_VIEW_INDEX, + componentName = "${PACKAGE}/${CLASS}", + cardinality = 1, + ) + ) + assertThat(taskViewItemInfo.runtimeStatusFlags and FLAG_NOT_PINNABLE) + .isEqualTo(FLAG_NOT_PINNABLE) + } + + @Test + fun emptyDesktopTask() { + whenever(taskView.type).thenReturn(TaskViewType.DESKTOP) + + val taskViewItemInfo = TaskViewItemInfo(taskView = taskView, taskContainer = null) + + assertThat(taskViewItemInfo.taskViewAtom) + .isEqualTo( + createTaskViewAtom( + type = 2, + index = TASK_VIEW_INDEX, + componentName = "", + cardinality = 0, + ) + ) + assertThat(taskViewItemInfo.user).isEqualTo(Process.myUserHandle()) + assertThat(taskViewItemInfo.intent).isNotNull() + } + + private fun createTask(id: Int) = + Task(TaskKey(id, 0, Intent(), ComponentName(PACKAGE, CLASS), 0, 2000)) + + private fun createTaskContainer(task: Task): TaskContainer { + return TaskContainer( + taskView, + task, + if (enableRefactorTaskThumbnail()) mock() + else mock(), + mock(), + mock(), + SplitConfigurationOptions.STAGE_POSITION_UNDEFINED, + digitalWellBeingToast = null, + showWindowsView = null, + overlayFactory, + ) + } + + @LauncherAppSingleton + @Component(modules = [AllModulesForTest::class]) + interface TestComponent : LauncherAppComponent { + @Component.Builder + interface Builder : LauncherAppComponent.Builder { + @BindsInstance fun bindUserCache(userCache: UserCache): Builder + + override fun build(): TestComponent + } + } + + companion object { + const val PACKAGE = "package" + const val CLASS = "class" + const val TASK_VIEW_INDEX = 4 + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt similarity index 97% rename from quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt index 04012c027df..b39c3f1e357 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt @@ -17,7 +17,7 @@ package com.android.launcher3.taskbar -import androidx.test.runner.AndroidJUnit4 +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.statemanager.StateManager import com.android.quickstep.RecentsActivity import com.android.quickstep.fallback.RecentsState @@ -33,7 +33,7 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class FallbackTaskbarUIControllerTest : TaskbarBaseTestCase() { - lateinit var fallbackTaskbarUIController: FallbackTaskbarUIController + lateinit var fallbackTaskbarUIController: FallbackTaskbarUIController lateinit var stateListener: StateManager.StateListener private val recentsActivity: RecentsActivity = mock() diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt new file mode 100644 index 00000000000..50d6affe4be --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.animation.AnimatorTestRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING +import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER +import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING +import com.android.launcher3.taskbar.rules.SandboxParams +import com.android.launcher3.taskbar.rules.TaskbarModeRule +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT +import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.quickstep.SystemUiProxy +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelTablet2023"]) +class TaskbarAutohideSuspendControllerTest { + + @get:Rule(order = 0) + val context = + TaskbarWindowSandboxContext.create( + SandboxParams({ + spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy -> + doAnswer { latestSuspendNotification = it.getArgument(0) } + .whenever(proxy) + .notifyTaskbarAutohideSuspend(anyOrNull()) + } + }) + ) + @get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this) + @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context) + @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController + @InjectController lateinit var stashController: TaskbarStashController + + private var latestSuspendNotification: Boolean? = null + + @Test + fun testUpdateFlag_suspendInLauncher_notifiesSuspend() { + getInstrumentation().runOnMainSync { + autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, true) + } + assertThat(latestSuspendNotification).isTrue() + } + + @Test + fun testUpdateFlag_toggleSuspendDraggingTwice_notifiesUnsuspend() { + getInstrumentation().runOnMainSync { + autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_DRAGGING, true) + autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_DRAGGING, false) + } + assertThat(latestSuspendNotification).isFalse() + } + + @Test + fun testUpdateFlag_resetsAlreadyUnsetFlag_noNotifyUnsuspend() { + getInstrumentation().runOnMainSync { + autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_DRAGGING, false) + } + assertThat(latestSuspendNotification).isNull() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateFlag_suspendTransientTaskbarForTouch_cancelsAutoStashTimeout() { + // Unstash and verify alarm. + getInstrumentation().runOnMainSync { + stashController.updateAndAnimateTransientTaskbar(false) + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(stashController.timeoutAlarm.alarmPending()).isTrue() + + // EDU opens while unstashed. + getInstrumentation().runOnMainSync { + autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_TOUCHING, true) + } + assertThat(stashController.timeoutAlarm.alarmPending()).isFalse() + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt similarity index 94% rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt index 15b1e532bd2..52d288a7237 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt @@ -17,6 +17,7 @@ package com.android.launcher3.taskbar import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController import com.android.launcher3.taskbar.bubbles.BubbleControllers +import com.android.launcher3.taskbar.growth.NudgeController import com.android.launcher3.taskbar.overlay.TaskbarOverlayController import com.android.systemui.shared.rotation.RotationButtonController import java.util.Optional @@ -57,6 +58,8 @@ abstract class TaskbarBaseTestCase { @Mock lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController @Mock lateinit var taskbarPinningController: TaskbarPinningController @Mock lateinit var optionalBubbleControllers: Optional + @Mock lateinit var taskbarDesktopModeController: TaskbarDesktopModeController + @Mock lateinit var nudgeController: NudgeController lateinit var taskbarControllers: TaskbarControllers @@ -98,6 +101,8 @@ abstract class TaskbarBaseTestCase { keyboardQuickSwitchController, taskbarPinningController, optionalBubbleControllers, + taskbarDesktopModeController, + nudgeController, ) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt new file mode 100644 index 00000000000..0e066cd13b6 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.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.taskbar + +import android.content.Context +import com.android.launcher3.ConstantItem +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.launcher3.util.TestUtil +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +object TaskbarControllerTestUtil { + inline fun runOnMainSync(crossinline runTest: () -> Unit) { + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) { runTest() } + } + + /** Returns a property to read/write the value of a [ConstantItem]. */ + fun ConstantItem.asProperty(context: Context): ReadWriteProperty { + return TaskbarItemProperty(context, this) + } + + private class TaskbarItemProperty( + private val context: Context, + private val item: ConstantItem, + ) : ReadWriteProperty { + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return LauncherPrefs.get(context).get(item) + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + runOnMainSync { LauncherPrefs.get(context).put(item, value) } + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt new file mode 100644 index 00000000000..455b6c5b985 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt @@ -0,0 +1,48 @@ +/* + * 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 com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +@LauncherMultivalentJUnit.EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) +class TaskbarDesktopModeControllerTest { + + @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @TaskbarUnitTestRule.InjectController + lateinit var taskbarDesktopModeController: TaskbarDesktopModeController + + @Test + fun whenTaskbarRequiresCornerRoundness_shouldReturnDefaultCornerRoundness() { + assertThat(taskbarDesktopModeController.getTaskbarCornerRoundness(true)) + .isEqualTo(MAX_ROUNDNESS) + } + + @Test + fun whenTaskbarRequiresCornerRoundness_shouldReturnZeroAsCornerRoundness() { + assertThat(taskbarDesktopModeController.getTaskbarCornerRoundness(false)).isEqualTo(0f) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt new file mode 100644 index 00000000000..3c803523297 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt @@ -0,0 +1,187 @@ +/* + * 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 com.android.launcher3.Utilities +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.asProperty +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.rules.TaskbarModeRule +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT +import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.launcher3.util.OnboardingPrefs +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) +class TaskbarEduTooltipControllerTest { + + @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() + + @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context) + + @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var taskbarEduTooltipController: TaskbarEduTooltipController + + private val taskbarContext: TaskbarActivityContext + get() = taskbarUnitTestRule.activityContext + + private val wasInTestHarness = Utilities.isRunningInTestHarness() + + private var tooltipStep by OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.prefItem.asProperty(context) + private var searchEduSeen by OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN.asProperty(context) + + @Before + fun setUp() { + Utilities.disableRunningInTestHarnessForTests() + } + + @After + fun tearDown() { + if (wasInTestHarness) { + Utilities.enableRunningInTestHarnessForTests() + } + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testMaybeShowSwipeEdu_whenTaskbarIsInThreeButtonMode_doesNotShowSwipeEdu() { + tooltipStep = TOOLTIP_STEP_SWIPE + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE) + runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() } + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE) + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testMaybeShowSwipeEdu_whenSwipeEduAlreadyShown_doesNotShowSwipeEdu() { + tooltipStep = TOOLTIP_STEP_FEATURES + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES) + runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() } + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES) + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testMaybeShowSwipeEdu_whenUserHasNotSeen_doesShowSwipeEdu() { + tooltipStep = TOOLTIP_STEP_SWIPE + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE) + runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() } + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES) + assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testMaybeShowFeaturesEdu_whenFeatureEduAlreadyShown_doesNotShowFeatureEdu() { + tooltipStep = TOOLTIP_STEP_NONE + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE) + runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() } + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE) + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testMaybeShowFeaturesEdu_whenUserHasNotSeen_doesShowFeatureEdu() { + tooltipStep = TOOLTIP_STEP_FEATURES + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES) + runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() } + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE) + assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue() + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testMaybeShowPinningEdu_whenTaskbarIsInThreeButtonMode_doesNotShowPinningEdu() { + tooltipStep = TOOLTIP_STEP_PINNING + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING) + runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() } + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING) + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testMaybeShowPinningEdu_whenUserHasNotSeen_doesShowPinningEdu() { + // Test standalone pinning edu, where user has seen taskbar edu before, but not pinning edu. + tooltipStep = TOOLTIP_STEP_PINNING + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING) + runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() } + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE) + assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testIsBeforeTooltipFeaturesStep_whenUserHasNotSeenFeatureEdu_shouldReturnTrue() { + tooltipStep = TOOLTIP_STEP_SWIPE + assertThat(taskbarEduTooltipController.isBeforeTooltipFeaturesStep).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testIsBeforeTooltipFeaturesStep_whenUserHasSeenFeatureEdu_shouldReturnFalse() { + tooltipStep = TOOLTIP_STEP_NONE + assertThat(taskbarEduTooltipController.isBeforeTooltipFeaturesStep).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testHide_whenTooltipIsOpen_shouldCloseTooltip() { + tooltipStep = TOOLTIP_STEP_SWIPE + assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE) + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() } + assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue() + runOnMainSync { taskbarEduTooltipController.hide() } + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testMaybeShowSearchEdu_whenTaskbarIsTransient_shouldNotShowSearchEdu() { + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + runOnMainSync { taskbarEduTooltipController.init(taskbarContext.controllers) } + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + } + + @Test + @TaskbarMode(PINNED) + fun testMaybeShowSearchEdu_whenTaskbarIsPinnedAndUserHasSeenSearchEdu_shouldNotShowSearchEdu() { + searchEduSeen = true + assertThat(taskbarEduTooltipController.userHasSeenSearchEdu).isTrue() + runOnMainSync { taskbarEduTooltipController.hide() } + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + runOnMainSync { taskbarEduTooltipController.init(taskbarContext.controllers) } + assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse() + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.kt new file mode 100644 index 00000000000..d2b9fcf2c54 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_HOVER_ENTER +import android.view.MotionEvent.ACTION_HOVER_EXIT +import com.android.launcher3.AbstractFloatingView +import com.android.launcher3.BubbleTextView +import com.android.launcher3.R +import com.android.launcher3.apppairs.AppPairIcon +import com.android.launcher3.folder.FolderIcon +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatAppPairsItem +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatFolderItem +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatWorkspaceItem +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) +class TaskbarHoverToolTipControllerTest { + + @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController + @InjectController lateinit var popupController: TaskbarPopupController + + private val taskbarContext: TaskbarActivityContext by taskbarUnitTestRule::activityContext + + private lateinit var taskbarView: TaskbarView + private lateinit var iconView: BubbleTextView + private lateinit var appPairIcon: AppPairIcon + private lateinit var folderIcon: FolderIcon + + private val isHoverToolTipOpen: Boolean + get() { + // TaskbarHoverToolTip uses ArrowTipView which is type TYPE_ON_BOARD_POPUP. + return AbstractFloatingView.hasOpenView( + taskbarContext, + AbstractFloatingView.TYPE_ON_BOARD_POPUP, + ) + } + + @Before + fun setup() { + runOnMainSync { taskbarView = taskbarContext.dragLayer.findViewById(R.id.taskbar_view) } + + val hotseatItems = + arrayOf( + createHotseatWorkspaceItem(), + createHotseatAppPairsItem(), + createHotseatFolderItem(), + ) + runOnMainSync { + taskbarView.updateItems(hotseatItems, emptyList()) + iconView = + taskbarView.iconViews.filterIsInstance().first { + it.tag is WorkspaceItemInfo + } + appPairIcon = taskbarView.iconViews.filterIsInstance().first() + folderIcon = taskbarView.iconViews.filterIsInstance().first() + } + } + + @Test + fun onHover_hoverEnterIcon_revealToolTip_hoverExitIcon_closeToolTip() { + runOnMainSync { iconView.dispatchGenericMotionEvent(HOVER_ENTER) } + assertThat(isHoverToolTipOpen).isTrue() + assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isTrue() + runOnMainSync { iconView.dispatchGenericMotionEvent(HOVER_EXIT) } + assertThat(isHoverToolTipOpen).isFalse() + assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isFalse() + } + + @Test + fun onHover_hoverEnterFolderIcon_revealToolTip_hoverExitFolderIcon_closeToolTip() { + runOnMainSync { folderIcon.dispatchGenericMotionEvent(HOVER_ENTER) } + assertThat(isHoverToolTipOpen).isTrue() + assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isTrue() + runOnMainSync { folderIcon.dispatchGenericMotionEvent(HOVER_EXIT) } + assertThat(isHoverToolTipOpen).isFalse() + assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isFalse() + } + + @Test + fun onHover_hoverEnterAppPair_revealToolTip_hoverExitAppPair_closeToolTip() { + runOnMainSync { appPairIcon.dispatchGenericMotionEvent(HOVER_ENTER) } + assertThat(isHoverToolTipOpen).isTrue() + assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isTrue() + runOnMainSync { appPairIcon.dispatchGenericMotionEvent(HOVER_EXIT) } + assertThat(isHoverToolTipOpen).isFalse() + assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isFalse() + } + + @Test + fun onHover_hoverEnterIconAlignedWithHotseat_noToolTip() { + taskbarContext.setUIController( + object : TaskbarUIController() { + override fun isIconAlignedWithHotseat(): Boolean = true + } + ) + + runOnMainSync { iconView.dispatchGenericMotionEvent(HOVER_ENTER) } + assertThat(isHoverToolTipOpen).isFalse() + } + + @Test + fun onHover_hoverEnterFolderOpen_noToolTip() { + runOnMainSync { + folderIcon.folder.animateOpen() + iconView.dispatchGenericMotionEvent(HOVER_ENTER) + } + assertThat(isHoverToolTipOpen).isFalse() + } + + @Test + fun onHover_hoverEnterPopupOpen_noToolTip() { + runOnMainSync { + popupController.showForIcon(iconView) + iconView.dispatchGenericMotionEvent(HOVER_ENTER) + } + assertThat(isHoverToolTipOpen).isFalse() + } + + companion object { + private val HOVER_EXIT = MotionEvent.obtain(0, 0, ACTION_HOVER_EXIT, 0f, 0f, 0) + private val HOVER_ENTER = MotionEvent.obtain(0, 0, ACTION_HOVER_ENTER, 0f, 0f, 0) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt similarity index 97% rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt index e619e7cd381..24310202ca4 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar import android.app.KeyguardManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING @@ -23,6 +24,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -30,6 +32,7 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +@RunWith(AndroidJUnit4::class) class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() { private val baseDragLayer: TaskbarDragLayer = mock() diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java index 0f06d98740c..a8f3500a72b 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java @@ -4,6 +4,8 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y; @@ -13,11 +15,14 @@ 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.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; +import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -25,7 +30,12 @@ import static org.mockito.Mockito.when; import android.os.Handler; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.KeyEvent; import android.view.View; +import android.view.inputmethod.Flags; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -34,11 +44,14 @@ import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TouchInteractionService; -import com.android.quickstep.util.AssistUtils; +import com.android.quickstep.util.ContextualSearchInvoker; +import com.android.systemui.contextualeducation.GestureType; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -49,12 +62,13 @@ public class TaskbarNavButtonControllerTest { @Mock SystemUiProxy mockSystemUiProxy; + @Mock TouchInteractionService mockService; @Mock Handler mockHandler; @Mock - AssistUtils mockAssistUtils; + ContextualSearchInvoker mockContextualSearchInvoker; @Mock StatsLogManager mockStatsLogManager; @Mock @@ -66,6 +80,9 @@ public class TaskbarNavButtonControllerTest { @Mock View mockView; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private int mHomePressCount; private int mOverviewToggleCount; private final TaskbarNavButtonCallbacks mCallbacks = new TaskbarNavButtonCallbacks() { @@ -98,19 +115,45 @@ public void setup() { mCallbacks, mockSystemUiProxy, mockHandler, - mockAssistUtils); + mockContextualSearchInvoker); } @Test public void testPressBack() { mNavButtonController.onButtonClick(BUTTON_BACK, mockView); - verify(mockSystemUiProxy, times(1)).onBackPressed(); + verify(mockSystemUiProxy, times(1)).onBackEvent(null); + } + + @Test + public void testPressBack_updateContextualEduData() { + mNavButtonController.onButtonClick(BUTTON_BACK, mockView); + verify(mockSystemUiProxy, times(1)) + .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK)); } @Test public void testPressImeSwitcher() { + mNavButtonController.init(mockTaskbarControllers); mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView); + verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP); + verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS); verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed(); + verify(mockSystemUiProxy, never()).onImeSwitcherLongPress(); + } + + @Test + public void testLongPressImeSwitcher() { + mNavButtonController.init(mockTaskbarControllers); + mNavButtonController.onButtonLongClick(BUTTON_IME_SWITCH, mockView); + verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP); + verify(mockSystemUiProxy, never()).onImeSwitcherPressed(); + if (Flags.imeSwitcherRevamp()) { + verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS); + verify(mockSystemUiProxy, times(1)).onImeSwitcherLongPress(); + } else { + verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS); + verify(mockSystemUiProxy, never()).onImeSwitcherLongPress(); + } } @Test @@ -129,40 +172,40 @@ public void testPressA11yLongClick() { @Test public void testLongPressHome_enabled_withoutOverride() { mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/); - when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false); + when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(false); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt()); + verify(mockContextualSearchInvoker, times(1)).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, times(1)).startAssistant(any()); } @Test public void testLongPressHome_enabled_withOverride() { mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/); - when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true); + when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(true); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt()); + verify(mockContextualSearchInvoker, times(1)).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, never()).startAssistant(any()); } @Test public void testLongPressHome_disabled_withoutOverride() { mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/); - when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false); + when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(false); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt()); + verify(mockContextualSearchInvoker, never()).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, never()).startAssistant(any()); } @Test public void testLongPressHome_disabled_withOverride() { mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/); - when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true); + when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(true); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt()); + verify(mockContextualSearchInvoker, never()).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, never()).startAssistant(any()); } @@ -172,12 +215,26 @@ public void testPressHome() { assertThat(mHomePressCount).isEqualTo(1); } + @Test + public void testPressHome_updateContextualEduData() { + mNavButtonController.onButtonClick(BUTTON_HOME, mockView); + verify(mockSystemUiProxy, times(1)) + .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME)); + } + @Test public void testPressRecents() { mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); assertThat(mOverviewToggleCount).isEqualTo(1); } + @Test + public void testPressRecents_updateContextualEduData() { + mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); + verify(mockSystemUiProxy, times(1)) + .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW)); + } + @Test public void testPressRecentsWithScreenPinned_noNavigationToOverview() { mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); @@ -282,4 +339,46 @@ public void testBackOverviewLogOnLongpress() { verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS); verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_BACK_BUTTON_TAP); } + + @Test + @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV) + public void testPredictiveBackInvoked() { + ArgumentCaptor keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class); + mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false); + mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false); + verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture()); + verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false); + verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, false); + } + + @Test + @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV) + public void testPredictiveBackCancelled() { + ArgumentCaptor keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class); + mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false); + mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, true); + verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture()); + verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false); + verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, true); + } + + @Test + @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV) + public void testButtonsDisabledWhileBackPressed() { + mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false); + mNavButtonController.onButtonClick(BUTTON_HOME, mockView); + mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); + mNavButtonController.onButtonLongClick(BUTTON_A11Y, mockView); + mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView); + mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false); + assertThat(mHomePressCount).isEqualTo(0); + verify(mockSystemUiProxy, never()).notifyAccessibilityButtonLongClicked(); + assertThat(mOverviewToggleCount).isEqualTo(0); + verify(mockSystemUiProxy, never()).onImeSwitcherPressed(); + } + + private void verifyKeyEvent(KeyEvent keyEvent, int action, boolean isCancelled) { + assertEquals(isCancelled, keyEvent.isCanceled()); + assertEquals(action, KeyEvent.ACTION_DOWN, keyEvent.getAction()); + } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt new file mode 100644 index 00000000000..c589415196b --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.animation.AnimatorTestRule +import android.content.ComponentName +import android.content.Intent +import android.os.Process +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.core.app.ApplicationProvider +import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR +import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW +import com.android.launcher3.R +import com.android.launcher3.dagger.LauncherAppSingleton +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController +import com.android.launcher3.taskbar.rules.DisplayControllerModule +import com.android.launcher3.taskbar.rules.MockedRecentsModelHelper +import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule +import com.android.launcher3.taskbar.rules.SandboxParams +import com.android.launcher3.taskbar.rules.TaskbarModeRule +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT +import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode +import com.android.launcher3.taskbar.rules.TaskbarSandboxComponent +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.AllModulesForTest +import com.android.launcher3.util.FakePrefsModule +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.launcher3.util.TestUtil.getOnUiThread +import com.android.quickstep.RecentsModel +import com.android.quickstep.SystemUiProxy +import com.android.quickstep.util.DesktopTask +import com.android.systemui.shared.recents.model.Task +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS +import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR +import com.android.wm.shell.desktopmode.IDesktopTaskListener +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelTablet2023"]) +@EnableFlags( + FLAG_TASKBAR_OVERFLOW, + FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, + FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + FLAG_ENABLE_BUBBLE_BAR, +) +@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR) +class TaskbarOverflowTest { + @get:Rule(order = 0) val setFlagsRule = SetFlagsRule() + + val mockRecentsModelHelper: MockedRecentsModelHelper = MockedRecentsModelHelper() + + @get:Rule(order = 1) + val context = + TaskbarWindowSandboxContext.create( + SandboxParams( + { + spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy -> + doAnswer { desktopTaskListener = it.getArgument(0) } + .whenever(proxy) + .setDesktopTaskListener(anyOrNull()) + } + }, + DaggerTaskbarOverflowComponent.builder() + .bindRecentsModel(mockRecentsModelHelper.mockRecentsModel), + ) + ) + + @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(mockRecentsModelHelper) + + @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context) + + @get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this) + + @get:Rule(order = 5) + val taskbarUnitTestRule = TaskbarUnitTestRule(this, context, this::onControllersInitialized) + + @InjectController lateinit var taskbarViewController: TaskbarViewController + @InjectController lateinit var recentAppsController: TaskbarRecentAppsController + @InjectController lateinit var bubbleBarViewController: BubbleBarViewController + @InjectController lateinit var bubbleStashController: BubbleStashController + @InjectController lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController + + private var desktopTaskListener: IDesktopTaskListener? = null + + private var currentControllerInitCallback: () -> Unit = {} + set(value) { + runOnMainSync { value.invoke() } + field = value + } + + private fun onControllersInitialized() { + runOnMainSync { + if (!recentAppsController.canShowRunningApps) { + recentAppsController.onDestroy() + recentAppsController.canShowRunningApps = true + recentAppsController.init(taskbarUnitTestRule.activityContext.controllers) + } + + currentControllerInitCallback.invoke() + } + } + + @Before + fun ensureRunningAppsShowing() { + runOnMainSync { recentsModel.resolvePendingTaskRequests() } + } + + @Test + @TaskbarMode(PINNED) + fun testTaskbarWithMaxNumIcons_pinned() { + addRunningAppsAndVerifyOverflowState(0) + + assertThat(taskbarIconsCentered).isTrue() + assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testTaskbarWithMaxNumIcons_transient() { + addRunningAppsAndVerifyOverflowState(0) + + assertThat(taskbarIconsCentered).isTrue() + assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing) + } + + @Test + @TaskbarMode(PINNED) + fun testOverflownTaskbar_pinned() { + addRunningAppsAndVerifyOverflowState(5) + + assertThat(taskbarIconsCentered).isTrue() + assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testOverflownTaskbar_transient() { + addRunningAppsAndVerifyOverflowState(5) + + assertThat(taskbarIconsCentered).isTrue() + assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing) + } + + @Test + @TaskbarMode(PINNED) + fun testOverflownTaskbarWithNoSpaceForRecentApps_pinned() { + val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2) + + // Create two "recent" desktop tasks, and then add enough hotseat items so the taskbar + // reaches max number of items with hotseat item icons, all apps and divider icons only. + // I.e. so all desktop tasks are in taskbar overflow. + createDesktopTask(2) + runOnMainSync { + val taskbarView: TaskbarView = + taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view) + taskbarView.updateItems( + createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount), + recentAppsController.shownTasks, + ) + } + + // Verify that taskbar overflow view is shown (eventhough it exceeds max taskbar icons). + assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1) + assertThat(taskbarOverflowIconIndex).isEqualTo(maxNumberOfTaskbarIcons) + assertThat(overflowItems).containsExactlyElementsIn(0..1) + } + + @Test + @TaskbarMode(PINNED) + fun testOverflownTaskbarWithNoSpaceForRecentApps_singleRecent_pinned() { + val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2) + + // Create a "recent" desktop task, and then add enough hotseat items so the taskbar + // reaches max number of items with hotseat item icons, all apps and divider icons only. + // I.e. so the single desktop tasks is in taskbar overflow. + createDesktopTask(1) + runOnMainSync { + val taskbarView: TaskbarView = + taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view) + val hotseatItems = createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount) + + taskbarView.updateItems( + recentAppsController.updateHotseatItemInfos(hotseatItems as Array), + recentAppsController.shownTasks, + ) + } + + // Verify that recent task is shown (eventhough it exceeds max taskbar icons), and that + // the taskbar overflow view is not added for the single recent app. + assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1) + assertThat(taskbarOverflowIconIndex).isEqualTo(-1) + } + + @Test + @TaskbarMode(PINNED) + fun testBubbleBarReducesTaskbarMaxNumIcons_pinned() { + var initialMaxNumIconViews = maxNumberOfTaskbarIcons + assertThat(initialMaxNumIconViews).isGreaterThan(0) + + currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) } + + val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2) + assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews) + + assertThat(taskbarIconsCentered).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testBubbleBarReducesTaskbarMaxNumIcons_transient() { + var initialMaxNumIconViews = maxNumberOfTaskbarIcons + assertThat(initialMaxNumIconViews).isGreaterThan(0) + + currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) } + + val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2) + assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews) + + assertThat(taskbarIconsCentered).isTrue() + assertThat(taskbarEndMargin) + .isAtLeast( + navButtonEndSpacing + + bubbleBarViewController.collapsedWidthWithMaxVisibleBubbles.toInt() + ) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testBubbleBarReducesTaskbarMaxNumIcons_transientBubbleInitiallyStashed() { + var initialMaxNumIconViews = maxNumberOfTaskbarIcons + assertThat(initialMaxNumIconViews).isGreaterThan(0) + currentControllerInitCallback = { + bubbleStashController.stashBubbleBarImmediate() + bubbleBarViewController.setHiddenForBubbles(false) + } + + val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2) + assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews) + + assertThat(taskbarIconsCentered).isTrue() + assertThat(taskbarEndMargin) + .isAtLeast( + navButtonEndSpacing + + bubbleBarViewController.collapsedWidthWithMaxVisibleBubbles.toInt() + ) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testStashingBubbleBarMaintainsMaxNumIcons_transient() { + currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) } + + val initialNumIcons = currentNumberOfTaskbarIcons + val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2) + + runOnMainSync { bubbleStashController.stashBubbleBarImmediate() } + assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2)) + } + + @Test + @TaskbarMode(PINNED) + fun testHidingBubbleBarIncreasesMaxNumIcons_pinned() { + currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) } + + val initialNumIcons = currentNumberOfTaskbarIcons + val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5) + + currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(true) } + runOnMainSync { animatorTestRule.advanceTimeBy(150) } + + val maxNumIconViews = maxNumberOfTaskbarIcons + assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews) + assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2)) + + assertThat(taskbarIconsCentered).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testHidingBubbleBarIncreasesMaxNumIcons_transient() { + currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) } + + val initialNumIcons = currentNumberOfTaskbarIcons + val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5) + + currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(true) } + runOnMainSync { animatorTestRule.advanceTimeBy(150) } + + val maxNumIconViews = maxNumberOfTaskbarIcons + assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews) + assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2)) + + assertThat(taskbarIconsCentered).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testPressingOverflowButtonOpensKeyboardQuickSwitch() { + val maxNumIconViews = maxNumberOfTaskbarIcons + // Assume there are at least all apps and divider icon, as they would appear once running + // apps are added, even if not present initially. + val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2) + + val targetOverflowSize = 5 + val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize + createDesktopTask(createdTasks) + + assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount) + tapOverflowIcon() + // Keyboard quick switch view is shown only after list of recent task is asynchronously + // retrieved from the recents model. + runOnMainSync { recentsModel.resolvePendingTaskRequests() } + + assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue() + assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() }) + .containsExactlyElementsIn(0..), + recentAppsController.shownTasks, + ) + } + + assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount + hotseatItems.size) + assertThat(overflowItems) + .containsExactlyElementsIn(listOf(0) + (2..targetOverflowSize + 1).toList()) + } + + @Test + @TaskbarMode(PINNED) + fun testHotseatItemTasksNotShownInKQS() { + val maxNumIconViews = maxNumberOfTaskbarIcons + // Assume there are at least all apps and divider icon, as they would appear once running + // apps are added, even if not present initially. + val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2) + val hotseatItems = createHotseatItems(1) + + val targetOverflowSize = 5 + val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize + createDesktopTaskWithTasksFromPackages( + listOf("fake") + + listOf(hotseatItems[0]?.targetPackage ?: "") + + List(createdTasks - 2) { "fake" } + ) + + runOnMainSync { + val taskbarView: TaskbarView = + taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view) + taskbarView.updateItems( + recentAppsController.updateHotseatItemInfos(hotseatItems as Array), + recentAppsController.shownTasks, + ) + } + + tapOverflowIcon() + // Keyboard quick switch view is shown only after list of recent task is asynchronously + // retrieved from the recents model. + runOnMainSync { recentsModel.resolvePendingTaskRequests() } + + assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue() + assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() }) + .containsExactlyElementsIn(listOf(0) + (2..) { + val tasks = + packages.mapIndexed({ index, p -> + Task( + Task.TaskKey( + index, + 0, + Intent().apply { `package` = p }, + ComponentName(p, ""), + Process.myUserHandle().identifier, + 2000, + ) + ) + }) + + recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, DEFAULT_DISPLAY, tasks))) + for (task in 1..tasks.size) { + desktopTaskListener?.onTasksVisibilityChanged( + context.virtualDisplay.display.displayId, + task, + ) + } + runOnMainSync { recentsModel.resolvePendingTaskRequests() } + } + + private val navButtonEndSpacing: Int + get() { + return taskbarUnitTestRule.activityContext.resources.getDimensionPixelSize( + taskbarUnitTestRule.activityContext.deviceProfile.inv.inlineNavButtonsEndSpacing + ) + } + + private val taskbarOverflowIconIndex: Int + get() { + return getOnUiThread { + taskbarViewController.iconViews.indexOfFirst { it is TaskbarOverflowView } + } + } + + private val maxNumberOfTaskbarIcons: Int + get() = getOnUiThread { taskbarViewController.maxNumIconViews } + + private val currentNumberOfTaskbarIcons: Int + get() = getOnUiThread { taskbarViewController.iconViews.size } + + private val taskbarIconsCentered: Boolean + get() { + return getOnUiThread { + val iconLayoutBounds = + taskbarViewController.transientTaskbarIconLayoutBoundsInParent + val availableWidth = taskbarUnitTestRule.activityContext.deviceProfile.widthPx + iconLayoutBounds.left - (availableWidth - iconLayoutBounds.right) < 2 + } + } + + private val taskbarEndMargin: Int + get() { + return getOnUiThread { + taskbarUnitTestRule.activityContext.deviceProfile.widthPx - + taskbarViewController.transientTaskbarIconLayoutBoundsInParent.right + } + } + + private val overflowItems: List + get() { + return getOnUiThread { + val overflowIcon = + taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView } + + if (overflowIcon is TaskbarOverflowView) { + overflowIcon.itemIds + } else { + emptyList() + } + } + } + + private fun tapOverflowIcon() { + runOnMainSync { + val overflowIcon = + taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView } + assertThat(overflowIcon?.callOnClick()).isTrue() + } + } + + /** + * Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies + * * max number of icons in the taskbar remains unchanged + * * number of icons in the taskbar is at most max number of icons + * * whether the taskbar overflow icon is shown, and its position in taskbar. + * + * Returns max number of icons. + */ + private fun addRunningAppsAndVerifyOverflowState(targetOverflowSize: Int): Int { + val maxNumIconViews = maxNumberOfTaskbarIcons + assertThat(maxNumIconViews).isGreaterThan(0) + // Assume there are at least all apps and divider icon, as they would appear once running + // apps are added, even if not present initially. + val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2) + assertThat(initialIconCount).isLessThan(maxNumIconViews) + + createDesktopTask(maxNumIconViews - initialIconCount + targetOverflowSize) + + assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews) + assertThat(taskbarOverflowIconIndex) + .isEqualTo(if (targetOverflowSize > 0) initialIconCount else -1) + if (targetOverflowSize > 0) { + assertThat(overflowItems).containsExactlyElementsIn(0..targetOverflowSize) + } + return maxNumIconViews + } +} + +/** TaskbarOverflowComponent used to bind the RecentsModel. */ +@LauncherAppSingleton +@Component( + modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class] +) +interface TaskbarOverflowComponent : TaskbarSandboxComponent { + + @Component.Builder + interface Builder : TaskbarSandboxComponent.Builder { + @BindsInstance fun bindRecentsModel(model: RecentsModel): Builder + + override fun build(): TaskbarOverflowComponent + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarPopupControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarPopupControllerTest.kt new file mode 100644 index 00000000000..6bb32050073 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarPopupControllerTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.platform.test.annotations.DisableFlags +import android.platform.test.flag.junit.SetFlagsRule +import com.android.launcher3.AbstractFloatingView +import com.android.launcher3.BubbleTextView +import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR +import com.android.launcher3.Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU +import com.android.launcher3.R +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatWorkspaceItem +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.quickstep.util.GroupTask +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) +@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU) +class TaskbarPopupControllerTest { + @get:Rule(order = 0) val setFlagsRule = SetFlagsRule() + + @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create() + + @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var popupController: TaskbarPopupController + + private val taskbarContext: TaskbarActivityContext + get() = taskbarUnitTestRule.activityContext + + private lateinit var taskbarView: TaskbarView + private lateinit var hotseatIcon: BubbleTextView + private lateinit var recentTaskIcon: BubbleTextView + + @Before + fun setup() { + taskbarContext.controllers.uiController.init(taskbarContext.controllers) + runOnMainSync { taskbarView = taskbarContext.dragLayer.findViewById(R.id.taskbar_view) } + + val hotseatItems = arrayOf(createHotseatWorkspaceItem()) + val recentItems = createRecents(2) + runOnMainSync { + taskbarView.updateItems(hotseatItems, recentItems) + hotseatIcon = + taskbarView.iconViews.filterIsInstance().first { + it.tag is WorkspaceItemInfo + } + recentTaskIcon = + taskbarView.iconViews.filterIsInstance().first { + it.tag is GroupTask + } + } + } + + @Test + fun showForIcon_hotseatItem() { + assertThat(hasPopupMenu()).isFalse() + runOnMainSync { popupController.showForIcon(hotseatIcon) } + assertThat(hasPopupMenu()).isTrue() + } + + @Test + fun showForIcon_recentTask() { + assertThat(hasPopupMenu()).isFalse() + runOnMainSync { popupController.showForIcon(recentTaskIcon) } + assertThat(hasPopupMenu()).isTrue() + } + + private fun hasPopupMenu(): Boolean { + return AbstractFloatingView.hasOpenView( + taskbarContext, + AbstractFloatingView.TYPE_ACTION_POPUP, + ) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt new file mode 100644 index 00000000000..334d8ab0613 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt @@ -0,0 +1,994 @@ +/* + * 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.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.graphics.Rect +import android.os.Process +import android.os.UserHandle +import android.platform.test.annotations.EnableFlags +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.annotation.UiThreadTest +import com.android.internal.R +import com.android.launcher3.BubbleTextView.RunningAppState +import com.android.launcher3.Flags +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION +import com.android.launcher3.model.data.AppInfo +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.model.data.TaskItemInfo +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.taskbar.TaskbarRecentAppsController.TaskState +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.quickstep.RecentsModel +import com.android.quickstep.RecentsModel.RecentTasksChangedListener +import com.android.quickstep.TaskIconCache +import com.android.quickstep.util.DesktopTask +import com.android.quickstep.util.GroupTask +import com.android.quickstep.util.SingleTask +import com.android.quickstep.util.SplitTask +import com.android.systemui.shared.recents.model.Task +import com.android.wm.shell.shared.split.SplitScreenConstants +import com.google.common.truth.Truth.assertThat +import java.util.function.Consumer +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@UiThreadTest +@RunWith(LauncherMultivalentJUnit::class) +@EnableFlags(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR) +class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { + + @get:Rule val mockitoRule = MockitoJUnit.rule() + @get:Rule + val disableControllerForCertainTestsWatcher = + object : TestWatcher() { + override fun starting(description: Description) { + // Update canShowRunningAndRecentAppsAtInit before setUp() is called for each test. + canShowRunningAndRecentAppsAtInit = + description.methodName !in + listOf("canShowRunningAndRecentAppsAtInitIsFalse_getTasksNeverCalled") + } + } + + @Mock private lateinit var mockIconCache: TaskIconCache + @Mock private lateinit var mockRecentsModel: RecentsModel + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockResources: Resources + + private var taskListChangeId: Int = 1 + + private lateinit var recentAppsController: TaskbarRecentAppsController + private lateinit var myUserHandle: UserHandle + private val USER_HANDLE_1 = UserHandle.of(1) + private val USER_HANDLE_2 = UserHandle.of(2) + + private var canShowRunningAndRecentAppsAtInit = true + private var recentTasksChangedListener: RecentTasksChangedListener? = null + + val recentShownTasks: List + get() = recentAppsController.shownTasks.flatMap { it.tasks } + + @Before + fun setUp() { + super.setup() + myUserHandle = Process.myUserHandle() + + // Set desktop mode supported + whenever(mockContext.getResources()).thenReturn(mockResources) + whenever(mockResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true) + + whenever(mockRecentsModel.iconCache).thenReturn(mockIconCache) + whenever(mockRecentsModel.unregisterRecentTasksChangedListener()).then { + recentTasksChangedListener = null + it + } + recentAppsController = TaskbarRecentAppsController(mockContext, mockRecentsModel) + recentAppsController.canShowRunningApps = canShowRunningAndRecentAppsAtInit + recentAppsController.canShowRecentApps = canShowRunningAndRecentAppsAtInit + recentAppsController.init(taskbarControllers) + taskbarControllers.onPostInit() + + recentTasksChangedListener = + if (canShowRunningAndRecentAppsAtInit) { + val listenerCaptor = ArgumentCaptor.forClass(RecentTasksChangedListener::class.java) + verify(mockRecentsModel) + .registerRecentTasksChangedListener(listenerCaptor.capture()) + listenerCaptor.value + } else { + verify(mockRecentsModel, never()).registerRecentTasksChangedListener(any()) + null + } + + // Make sure updateHotseatItemInfos() is called after commitRunningAppsToUI() + whenever(taskbarViewController.commitRunningAppsToUI()).then { + recentAppsController.updateHotseatItemInfos( + recentAppsController.shownHotseatItems.toTypedArray() + ) + } + } + + // See the TestWatcher rule at the top which sets canShowRunningAndRecentAppsAtInit = false. + @Test + fun canShowRunningAndRecentAppsAtInitIsFalse_getTasksNeverCalled() { + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2), + runningTasks = listOf(createTask(1, RUNNING_APP_PACKAGE_1)), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + verify(mockRecentsModel, never()).getTasks(any>>()) + } + + @Test + fun canShowRunningAndRecentAppsIsFalseAfterInit_getTasksOnlyCalledInInit() { + // getTasks() should have been called once from init(). + verify(mockRecentsModel, times(1)).getTasks(any>>(), any()) + recentAppsController.canShowRunningApps = false + recentAppsController.canShowRecentApps = false + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2), + runningTasks = listOf(createTask(1, RUNNING_APP_PACKAGE_1)), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + // Verify that getTasks() was not called again after the init(). + verify(mockRecentsModel, times(1)).getTasks(any>>(), any()) + } + + @Test + fun getDesktopItemState_nullItemInfo_returnsNotRunning() { + setInDesktopMode(true) + val taskState = recentAppsController.getDesktopItemState(/* itemInfo= */ null) + assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING)) + } + + @Test + fun getDesktopItemState_noItemPackage_returnsNotRunning() { + setInDesktopMode(true) + val taskState = recentAppsController.getDesktopItemState(ItemInfo()) + assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING)) + } + + @Test + fun getDesktopItemState_noMatchingTasks_returnsNotRunning() { + setInDesktopMode(true) + val taskState = recentAppsController.getDesktopItemState(createItemInfo("package")) + assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING)) + } + + @Test + fun getDesktopItemState_matchingVisibleTask_returnsVisible() { + setInDesktopMode(true) + val visibleTask = createTask(id = 1, "visiblePackage", isVisible = true) + updateRecentTasks(runningTasks = listOf(visibleTask), recentTaskPackages = emptyList()) + + val taskState = recentAppsController.getDesktopItemState(createItemInfo("visiblePackage")) + + assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 1)) + } + + @Test + fun getDesktopItemState_matchingMinimizedTask_returnsMinimized() { + setInDesktopMode(true) + val minimizedTask = createTask(id = 1, "minimizedPackage", isVisible = false) + updateRecentTasks(runningTasks = listOf(minimizedTask), recentTaskPackages = emptyList()) + + val taskState = recentAppsController.getDesktopItemState(createItemInfo("minimizedPackage")) + + assertThat(taskState).isEqualTo(TaskState(RunningAppState.MINIMIZED, taskId = 1)) + } + + @Test + fun getDesktopItemState_matchingMinimizedAndRunningTask_returnsVisible() { + setInDesktopMode(true) + updateRecentTasks( + runningTasks = + listOf( + createTask(id = 1, "package", isVisible = false), + createTask(id = 2, "package", isVisible = true), + ), + recentTaskPackages = emptyList(), + ) + + val taskState = recentAppsController.getDesktopItemState(createItemInfo("package")) + + assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 2)) + } + + @Test + fun getDesktopItemState_noMatchingUserId_returnsNotRunning() { + setInDesktopMode(true) + updateRecentTasks( + runningTasks = + listOf( + createTask(id = 1, "package", isVisible = false, USER_HANDLE_1), + createTask(id = 2, "package", isVisible = true, USER_HANDLE_1), + ), + recentTaskPackages = emptyList(), + ) + + val taskState = + recentAppsController.getDesktopItemState(createItemInfo("package", USER_HANDLE_2)) + + assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING)) + } + + @Test + fun getRunningAppState_taskNotRunningOrMinimized_returnsNotRunning() { + setInDesktopMode(true) + updateRecentTasks(runningTasks = emptyList(), recentTaskPackages = emptyList()) + + assertThat(recentAppsController.getRunningAppState(taskId = 1)) + .isEqualTo(RunningAppState.NOT_RUNNING) + } + + @Test + fun getRunningAppState_taskNotVisible_returnsMinimized() { + setInDesktopMode(true) + val task1 = createTask(id = 1, packageName = RUNNING_APP_PACKAGE_1, isVisible = false) + val task2 = createTask(id = 2, packageName = RUNNING_APP_PACKAGE_1, isVisible = true) + updateRecentTasks(runningTasks = listOf(task1, task2), recentTaskPackages = emptyList()) + + assertThat(recentAppsController.getRunningAppState(taskId = 1)) + .isEqualTo(RunningAppState.MINIMIZED) + } + + @Test + fun getRunningAppState_taskVisible_returnsRunning() { + setInDesktopMode(true) + val task1 = createTask(id = 1, packageName = RUNNING_APP_PACKAGE_1, isVisible = false) + val task2 = createTask(id = 2, packageName = RUNNING_APP_PACKAGE_1, isVisible = true) + updateRecentTasks(runningTasks = listOf(task1, task2), recentTaskPackages = emptyList()) + + assertThat(recentAppsController.getRunningAppState(taskId = 2)) + .isEqualTo(RunningAppState.RUNNING) + } + + @Test + fun updateHotseatItemInfos_cantShowRunning_inDesktopMode_returnsAllHotseatItems() { + recentAppsController.canShowRunningApps = false + setInDesktopMode(true) + val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1) + val newHotseatItems = + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = hotseatPackages, + runningTasks = emptyList(), + recentTaskPackages = emptyList(), + ) + assertThat(newHotseatItems.map { it?.targetPackage }) + .containsExactlyElementsIn(hotseatPackages) + } + + @Test + fun updateHotseatItemInfos_cantShowRecent_notInDesktopMode_returnsAllHotseatItems() { + recentAppsController.canShowRecentApps = false + setInDesktopMode(false) + val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1) + val newHotseatItems = + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = hotseatPackages, + runningTasks = emptyList(), + recentTaskPackages = emptyList(), + ) + assertThat(newHotseatItems.map { it?.targetPackage }) + .containsExactlyElementsIn(hotseatPackages) + } + + @Test + fun updateHotseatItemInfos_canShowRunning_inDesktopMode_returnsNonPredictedHotseatItems() { + recentAppsController.canShowRunningApps = true + setInDesktopMode(true) + val newHotseatItems = + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1), + runningTasks = emptyList(), + recentTaskPackages = emptyList(), + ) + val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2) + assertThat(newHotseatItems.map { it?.targetPackage }) + .containsExactlyElementsIn(expectedPackages) + } + + @Test + fun updateHotseatItemInfos_inDesktopMode_hotseatPackageHasRunningTask_hotseatItemLinksToTask() { + setInDesktopMode(true) + + val newHotseatItems = + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2), + runningTasks = listOf(createTask(id = 1, HOTSEAT_PACKAGE_1)), + recentTaskPackages = emptyList(), + ) + + assertThat(newHotseatItems).hasLength(2) + assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java) + assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java) + val hotseatItem1 = newHotseatItems[0] as TaskItemInfo + assertThat(hotseatItem1.taskId).isEqualTo(1) + } + + /** + * Tests that in desktop mode, when two tasks have the same package name and one is in the + * hotseat, only the hotseat item represents the app, and no duplicate is shown in recent apps. + */ + @Test + fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_onlyHotseatCoversTask() { + setInDesktopMode(true) + + val newHotseatItems = + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2), + runningTasks = + listOf( + createTask(id = 1, HOTSEAT_PACKAGE_1), + createTask(id = 2, HOTSEAT_PACKAGE_1), + ), + recentTaskPackages = emptyList(), + ) + + // The task is in Hotseat Items + assertThat(newHotseatItems).hasLength(2) + assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java) + assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java) + val hotseatItem1 = newHotseatItems[0] as TaskItemInfo + assertThat(hotseatItem1.targetPackage).isEqualTo(HOTSEAT_PACKAGE_1) + + // The other task of the same package is not in recentShownTasks + assertThat(recentShownTasks).isEmpty() + } + + @Test + fun updateHotseatItemInfos_canShowRecent_notInDesktopMode_returnsNonPredictedHotseatItems() { + recentAppsController.canShowRecentApps = true + setInDesktopMode(false) + val newHotseatItems = + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1), + runningTasks = emptyList(), + recentTaskPackages = emptyList(), + ) + val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2) + assertThat(newHotseatItems.map { it?.targetPackage }) + .containsExactlyElementsIn(expectedPackages) + } + + @Test + fun onRecentTasksChanged_cantShowRunning_inDesktopMode_shownTasks_returnsEmptyList() { + recentAppsController.canShowRunningApps = false + setInDesktopMode(true) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1), + runningTasks = + listOf( + createTask(id = 1, RUNNING_APP_PACKAGE_1), + createTask(id = 2, RUNNING_APP_PACKAGE_2), + ), + recentTaskPackages = emptyList(), + ) + assertThat(recentAppsController.shownTasks).isEmpty() + } + + @Test + fun onRecentTasksChanged_cantShowRecent_notInDesktopMode_shownTasks_returnsEmptyList() { + recentAppsController.canShowRecentApps = false + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + assertThat(recentAppsController.shownTasks).isEmpty() + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_noRecentTasks_shownTasks_returnsEmptyList() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = + listOf( + createTask(id = 1, RUNNING_APP_PACKAGE_1), + createTask(id = 2, RUNNING_APP_PACKAGE_2), + ), + recentTaskPackages = emptyList(), + ) + assertThat(recentAppsController.shownTasks).isEmpty() + assertThat(recentAppsController.minimizedTaskIds).isEmpty() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_noRunningApps_shownTasks_returnsEmptyList() { + setInDesktopMode(true) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + assertThat(recentAppsController.shownTasks).isEmpty() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_shownTasks_returnsRunningTasks() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2), + recentTaskPackages = emptyList(), + ) + assertThat(recentShownTasks).containsExactlyElementsIn(listOf(task1, task2)) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_getRunningApps_returnsEmptySet() { + setInDesktopMode(false) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2), + recentTaskPackages = emptyList(), + ) + assertThat(recentAppsController.runningTaskIds).isEmpty() + assertThat(recentAppsController.minimizedTaskIds).isEmpty() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2), + recentTaskPackages = emptyList(), + ) + assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2)) + assertThat(recentAppsController.minimizedTaskIds).isEmpty() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_getRunningApps_includesHotseat() { + setInDesktopMode(true) + val runningTasks = + listOf( + createTask(id = 1, HOTSEAT_PACKAGE_1), + createTask(id = 2, RUNNING_APP_PACKAGE_1), + createTask(id = 3, RUNNING_APP_PACKAGE_2), + ) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2), + runningTasks = runningTasks, + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2, 3)) + assertThat(recentAppsController.minimizedTaskIds).isEmpty() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_allAppsRunningAndInvisibleAppsMinimized() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + val task3Minimized = createTask(id = 3, RUNNING_APP_PACKAGE_3, isVisible = false) + val runningTasks = listOf(task1, task2, task3Minimized) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = runningTasks, + recentTaskPackages = emptyList(), + ) + assertThat(recentAppsController.runningTaskIds).containsExactly(1, 2, 3) + assertThat(recentAppsController.minimizedTaskIds).containsExactly(3) + } + + @Test + fun onRecentTasksChanged_inDesktopMode_samePackage_differentTasks_severalRunningTasks() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2), + recentTaskPackages = emptyList(), + ) + assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2)) + } + + @Test + fun onRecentTasksChanged_inDesktopMode_shownTasks_maintainsOrder() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2), + recentTaskPackages = emptyList(), + ) + + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task2, task1), + recentTaskPackages = emptyList(), + ) + + assertThat(recentShownTasks).isEqualTo(listOf(task1, task2)) + } + + /** + * Tests that when multiple instances of the same app are running in desktop mode and the app is + * not in the hotseat, only one instance is shown in the recent apps section. + */ + @Test + fun onRecentTasksChanged_inDesktopMode_multiInstance_noHotseat_shownTasksHasOneInstance() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2), + recentTaskPackages = emptyList(), + ) + + // Assert that recentShownTasks contains only one instance of the app + assertThat(recentShownTasks).hasSize(1) + assertThat(recentShownTasks[0].key.packageName).isEqualTo(RUNNING_APP_PACKAGE_1) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_shownTasks_maintainsRecency() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3), + ) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1), + ) + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + // Most recent packages, minus the currently running one (RECENT_PACKAGE_1). + assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)) + } + + @Test + fun onRecentTasksChanged_inDesktopMode_addTask_shownTasks_maintainsOrder() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2), + recentTaskPackages = emptyList(), + ) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task2, task1, task3), + recentTaskPackages = emptyList(), + ) + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + val expectedOrder = + listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3) + assertThat(shownPackages).isEqualTo(expectedOrder) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_addTask_shownTasks_maintainsRecency() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2), + ) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1), + ) + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + // Most recent packages, minus the currently running one (RECENT_PACKAGE_1). + assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)) + } + + @Test + fun onRecentTasksChanged_inDesktopMode_removeTask_shownTasks_maintainsOrder() { + setInDesktopMode(true) + val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1, task2, task3), + recentTaskPackages = emptyList(), + ) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task2, task1), + recentTaskPackages = emptyList(), + ) + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + assertThat(shownPackages).isEqualTo(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_removeTask_shownTasks_maintainsRecency() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3), + ) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3), + ) + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + // Most recent packages, minus the currently running one (RECENT_PACKAGE_3). + assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2)) + } + + @Test + fun onRecentTasksChanged_enterDesktopMode_shownTasks_onlyIncludesRunningTasks() { + setInDesktopMode(false) + val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) + + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(runningTask1, runningTask2), + recentTaskPackages = recentTaskPackages, + ) + + setInDesktopMode(true) + recentTasksChangedListener!!.onRecentTasksChanged() + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + assertThat(shownPackages).containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2) + } + + @Test + fun onRecentTasksChanged_exitDesktopMode_shownTasks_onlyIncludesRecentTasks() { + setInDesktopMode(true) + val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(runningTask1, runningTask2), + recentTaskPackages = recentTaskPackages, + ) + setInDesktopMode(false) + recentTasksChangedListener!!.onRecentTasksChanged() + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + // Don't expect RECENT_PACKAGE_3 because it is currently running. + val expectedPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) + assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_hasRecentTasks_shownTasks_returnsRecentTasks() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3), + ) + val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } + // RECENT_PACKAGE_3 is the top task (visible to user) so should be excluded. + val expectedPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) + assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_hasRecentAndRunningTasks_shownTasks_returnsRecentTaskAndDesktopTile() { + setInDesktopMode(false) + val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(runningTask1, runningTask2), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + val shownPackages = recentAppsController.shownTasks.map { it.packageNames } + // Only 2 recent tasks shown: Desktop Tile + 1 Recent Task + val desktopTilePackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2) + val recentTaskPackages = listOf(RECENT_PACKAGE_1) + val expectedPackages = listOf(desktopTilePackages, recentTaskPackages) + assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_hasRecentAndSplitTasks_shownTasks_returnsRecentTaskAndPair() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + val shownPackages = recentAppsController.shownTasks.map { it.packageNames } + // Only 2 recent tasks shown: Pair + 1 Recent Task + val pairPackages = RECENT_SPLIT_PACKAGES_1.split("_") + val recentTaskPackages = listOf(RECENT_PACKAGE_1) + val expectedPackages = listOf(pairPackages, recentTaskPackages) + assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) + } + + @Test + fun onRecentTasksChanged_notInDesktopMode_noActualChangeToRecents_commitRunningAppsToUI_notCalled() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_noActualChangeToRunning_commitRunningAppsToUI_notCalled() { + setInDesktopMode(true) + val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) + val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(runningTask1, runningTask2), + recentTaskPackages = emptyList(), + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(runningTask1, runningTask2), + recentTaskPackages = emptyList(), + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + } + + @Test + fun onRecentTasksChanged_onlyMinimizedChanges_commitRunningAppsToUI_isCalled() { + setInDesktopMode(true) + val task1Minimized = createTask(id = 1, RUNNING_APP_PACKAGE_1, isVisible = false) + val task2Visible = createTask(id = 2, RUNNING_APP_PACKAGE_2) + val task2Minimized = createTask(id = 2, RUNNING_APP_PACKAGE_2, isVisible = false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1Minimized, task2Visible), + recentTaskPackages = emptyList(), + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + + // Call onRecentTasksChanged() again with a new minimized app, verify we update UI. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTasks = listOf(task1Minimized, task2Minimized), + recentTaskPackages = emptyList(), + ) + + verify(taskbarViewController, times(2)).commitRunningAppsToUI() + } + + @Test + fun onRecentTasksChanged_hotseatAppStartsRunning_commitRunningAppsToUI_isCalled() { + setInDesktopMode(true) + val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2) + val originalTasks = listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1)) + val newTasks = + listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1), createTask(id = 2, HOTSEAT_PACKAGE_1)) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = hotseatPackages, + runningTasks = originalTasks, + recentTaskPackages = emptyList(), + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + + // Call onRecentTasksChanged() again with a new running app, verify we update UI. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = hotseatPackages, + runningTasks = newTasks, + recentTaskPackages = emptyList(), + ) + + verify(taskbarViewController, times(2)).commitRunningAppsToUI() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_sameHotseatPackage_differentUser_isInShownTasks() { + setInDesktopMode(true) + val hotseatPackageUser = PackageUser(HOTSEAT_PACKAGE_1, USER_HANDLE_2) + val hotseatPackageUsers = listOf(hotseatPackageUser) + val runningTask = createTask(id = 1, HOTSEAT_PACKAGE_1, localUserHandle = USER_HANDLE_1) + val runningTasks = listOf(runningTask) + prepareHotseatAndRunningAndRecentAppsInternal( + hotseatPackageUsers = hotseatPackageUsers, + runningTasks = runningTasks, + recentTaskPackages = emptyList(), + ) + assertThat(recentShownTasks).contains(runningTask) + assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1)) + } + + private fun prepareHotseatAndRunningAndRecentApps( + hotseatPackages: List, + runningTasks: List, + recentTaskPackages: List, + ): Array { + val hotseatPackageUsers = hotseatPackages.map { PackageUser(it, myUserHandle) } + return prepareHotseatAndRunningAndRecentAppsInternal( + hotseatPackageUsers, + runningTasks, + recentTaskPackages, + ) + } + + private fun prepareHotseatAndRunningAndRecentAppsInternal( + hotseatPackageUsers: List, + runningTasks: List, + recentTaskPackages: List, + ): Array { + val hotseatItems = createHotseatItemsFromPackageUsers(hotseatPackageUsers) + recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()) + updateRecentTasks(runningTasks, recentTaskPackages) + return recentAppsController.shownHotseatItems.toTypedArray() + } + + private fun updateRecentTasks(runningTasks: List, recentTaskPackages: List) { + val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages) + val allTasks = + ArrayList().apply { + if (!runningTasks.isEmpty()) { + add(DesktopTask(deskId = 0, DEFAULT_DISPLAY, ArrayList(runningTasks))) + } + addAll(recentTasks) + } + doAnswer { + val callback: Consumer> = it.getArgument(0) + callback.accept(allTasks) + taskListChangeId + } + .whenever(mockRecentsModel) + .getTasks(any>>(), any()) + recentTasksChangedListener?.onRecentTasksChanged() + } + + private fun createHotseatItemsFromPackageUsers( + packageUsers: List + ): List { + return packageUsers + .map { + createTestAppInfo(packageName = it.packageName, userHandle = it.userHandle).apply { + container = + if (it.packageName.startsWith("predicted")) { + CONTAINER_HOTSEAT_PREDICTION + } else { + CONTAINER_HOTSEAT + } + } + } + .map { it.makeWorkspaceItem(taskbarActivityContext) } + } + + private fun createTestAppInfo( + packageName: String = "testPackageName", + className: String = "testClassName", + userHandle: UserHandle, + ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent()) + + private fun createRecentTasksFromPackageNames(packageNames: List): List { + return packageNames.map { packageName -> + if (packageName.startsWith("split")) { + val splitPackages = packageName.split("_") + SplitTask( + createTask(100, splitPackages[0]), + createTask(101, splitPackages[1]), + SplitConfigurationOptions.SplitBounds( + /* leftTopBounds = */ Rect(), + /* rightBottomBounds = */ Rect(), + /* leftTopTaskId = */ -1, + /* rightBottomTaskId = */ -1, + /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50, + ), + ) + } else { + // Use the number at the end of the test packageName as the id. + val id = 1000 + packageName[packageName.length - 1].code + SingleTask(createTask(id, packageName)) + } + } + } + + private fun createTask( + id: Int, + packageName: String, + isVisible: Boolean = true, + localUserHandle: UserHandle? = null, + ): Task { + return Task( + Task.TaskKey( + id, + WINDOWING_MODE_FREEFORM, + Intent().apply { `package` = packageName }, + ComponentName(packageName, "TestActivity"), + localUserHandle?.identifier ?: myUserHandle.identifier, + 0, + ) + ) + .apply { this.isVisible = isVisible } + } + + private fun setInDesktopMode(inDesktopMode: Boolean) { + whenever(taskbarControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) + .thenReturn(inDesktopMode) + whenever(taskbarControllers.taskbarDesktopModeController.isInDesktopMode(DEFAULT_DISPLAY)) + .thenReturn(inDesktopMode) + } + + private fun createItemInfo( + packageName: String, + userHandle: UserHandle = myUserHandle, + ): ItemInfo { + val appInfo = AppInfo() + appInfo.intent = Intent().setComponent(ComponentName(packageName, "className")) + appInfo.user = userHandle + return WorkspaceItemInfo(appInfo) + } + + private val GroupTask.packageNames: List + get() = tasks.map { task -> task.key.packageName } + + private companion object { + const val HOTSEAT_PACKAGE_1 = "hotseat1" + const val HOTSEAT_PACKAGE_2 = "hotseat2" + const val PREDICTED_PACKAGE_1 = "predicted1" + const val RUNNING_APP_PACKAGE_1 = "running1" + const val RUNNING_APP_PACKAGE_2 = "running2" + const val RUNNING_APP_PACKAGE_3 = "running3" + const val RECENT_PACKAGE_1 = "recent1" + const val RECENT_PACKAGE_2 = "recent2" + const val RECENT_PACKAGE_3 = "recent3" + const val RECENT_SPLIT_PACKAGES_1 = "split1_split2" + } + + data class PackageUser(val packageName: String, val userHandle: UserHandle) +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt new file mode 100644 index 00000000000..ba53dcdc2b8 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.animation.AnimatorTestRule +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.View.GONE +import android.view.View.VISIBLE +import androidx.test.core.app.ApplicationProvider +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController +import com.android.launcher3.taskbar.rules.SandboxParams +import com.android.launcher3.taskbar.rules.TaskbarModeRule +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT +import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.quickstep.SystemUiProxy +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE +import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR +import com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelTablet2023"]) +class TaskbarScrimViewControllerTest { + @get:Rule(order = 0) val setFlagsRule = SetFlagsRule() + @get:Rule(order = 1) + val context = + TaskbarWindowSandboxContext.create( + SandboxParams({ + spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { + doAnswer { backPressed = true }.whenever(it).onBackEvent(anyOrNull()) + } + }) + ) + + @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context) + @get:Rule(order = 3) val animatorTestRule = AnimatorTestRule(this) + @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var scrimViewController: TaskbarScrimViewController + + // Default animation duration. + private val animationDuration = + context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong() + + private var backPressed = false + + @Test + @TaskbarMode(PINNED) + fun testOnTaskbarVisibleChanged_onlyTaskbarVisible_noScrim() { + getInstrumentation().runOnMainSync { + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + scrimViewController.updateStateForSysuiFlags(0, true) + } + assertThat(scrimViewController.scrimAlpha).isEqualTo(0) + } + + @Test + @TaskbarMode(PINNED) + fun testOnTaskbarVisibilityChanged_pinnedTaskbarVisibleWithBubblesExpanded_showsScrim() { + getInstrumentation().runOnMainSync { + scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true) + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + animatorTestRule.advanceTimeBy(animationDuration) + } + + assertThat(scrimViewController.scrimAlpha).isEqualTo(BUBBLE_EXPANDED_SCRIM_ALPHA) + } + + @Test + @DisableFlags(FLAG_ENABLE_BUBBLE_BAR) + @TaskbarMode(PINNED) + fun testOnTaskbarVisibilityChanged_pinnedTaskbarHiddenDuringScrim_hidesScrim() { + getInstrumentation().runOnMainSync { + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true) + } + assertThat(scrimViewController.scrimAlpha).isEqualTo(BUBBLE_EXPANDED_SCRIM_ALPHA) + + getInstrumentation().runOnMainSync { + scrimViewController.onTaskbarVisibilityChanged(GONE) + animatorTestRule.advanceTimeBy(animationDuration) + } + assertThat(scrimViewController.scrimAlpha).isEqualTo(0) + } + + @Test + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @TaskbarMode(PINNED) + fun testOnTaskbarVisibilityChanged_pinnedTaskbarOnHomeHiddenDuringScrim_hidesScrim() { + getInstrumentation().runOnMainSync { + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + taskbarUnitTestRule.activityContext.bubbleControllers!! + .bubbleStashController + .launcherState = BubbleStashController.BubbleLauncherState.HOME + scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true) + } + assertThat(scrimViewController.scrimAlpha).isEqualTo(BUBBLE_EXPANDED_SCRIM_ALPHA) + + getInstrumentation().runOnMainSync { + scrimViewController.onTaskbarVisibilityChanged(GONE) + animatorTestRule.advanceTimeBy(animationDuration) + } + assertThat(scrimViewController.scrimAlpha).isEqualTo(0) + } + + @Test + @TaskbarMode(PINNED) + fun testOnTaskbarVisibilityChanged_notificationsOverPinnedTaskbarAndBubbles_noScrim() { + getInstrumentation().runOnMainSync { + scrimViewController.updateStateForSysuiFlags( + SYSUI_STATE_BUBBLES_EXPANDED or SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, + true, + ) + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + } + assertThat(scrimViewController.scrimAlpha).isEqualTo(0) + } + + @Test + @TaskbarMode(PINNED) + fun testOnTaskbarVisibilityChanged_pinnedTaskbarWithBubbleMenu_darkerScrim() { + getInstrumentation().runOnMainSync { + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + scrimViewController.updateStateForSysuiFlags( + SYSUI_STATE_BUBBLES_EXPANDED or SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, + true, + ) + } + assertThat(scrimViewController.scrimAlpha).isGreaterThan(BUBBLE_EXPANDED_SCRIM_ALPHA) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testOnTaskbarVisibilityChanged_stashedTaskbarWithBubbles_noScrim() { + getInstrumentation().runOnMainSync { + scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true) + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + } + assertThat(scrimViewController.scrimAlpha).isEqualTo(0) + } + + @Test + @TaskbarMode(PINNED) + fun testOnClick_scrimShown_performsSystemBack() { + getInstrumentation().runOnMainSync { + scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true) + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + } + assertThat(scrimViewController.scrimView.isClickable).isTrue() + + getInstrumentation().runOnMainSync { scrimViewController.scrimView.performClick() } + assertThat(backPressed).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testOnClick_scrimHidden_notClickable() { + getInstrumentation().runOnMainSync { + scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true) + scrimViewController.onTaskbarVisibilityChanged(VISIBLE) + } + assertThat(scrimViewController.scrimView.isClickable).isFalse() + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt new file mode 100644 index 00000000000..021e1e417c4 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.animation.AnimatorTestRule +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING +import com.android.launcher3.QuickstepTransitionManager.PINNED_TASKBAR_TRANSITION_DURATION +import com.android.launcher3.R +import com.android.launcher3.taskbar.StashedHandleViewController.ALPHA_INDEX_STASHED +import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.asProperty +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_DEVICE_LOCKED +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IME +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_AUTO +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_SMALL_SCREEN +import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_SYSUI +import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION +import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION_FOR_IME +import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_ALPHA_DURATION +import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_DURATION +import com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_STASH +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController +import com.android.launcher3.taskbar.rules.TaskbarModeRule +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT +import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE +import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.TruthJUnit.assume +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +@EnableFlags(FLAG_ENABLE_BUBBLE_BAR) +@EmulatedDevices(["pixelTablet2023"]) +class TaskbarStashControllerTest { + @get:Rule(order = 0) val setFlagsRule = SetFlagsRule() + @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context) + @get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this) + @get:Rule(order = 5) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var stashController: TaskbarStashController + @InjectController lateinit var viewController: TaskbarViewController + @InjectController lateinit var stashedHandleViewController: StashedHandleViewController + @InjectController lateinit var dragLayerController: TaskbarDragLayerController + @InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController + @InjectController lateinit var bubbleBarViewController: BubbleBarViewController + @InjectController lateinit var bubbleStashController: BubbleStashController + + private val activityContext by taskbarUnitTestRule::activityContext + + @After fun cancelTimeoutIfExists() = stashController.cancelTimeoutIfExists() + + @Test + @TaskbarMode(TRANSIENT) + fun testInit_transientMode_stashedInApp() { + assertThat(stashController.isStashedInApp).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testInit_pinnedMode_unstashedInApp() { + assertThat(stashController.isStashedInApp).isFalse() + } + + @Test + @UserSetupMode + @TaskbarMode(PINNED) + fun testInit_userSetupWithPinnedMode_stashedInApp() { + assertThat(stashController.isStashedInApp).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testSetSetupUiVisible_true_stashedInApp() { + getInstrumentation().runOnMainSync { stashController.setSetupUIVisible(true) } + assertThat(stashController.isStashedInApp).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testSetSetupUiVisible_false_unstashedInApp() { + getInstrumentation().runOnMainSync { stashController.setSetupUIVisible(false) } + assertThat(stashController.isStashedInApp).isFalse() + } + + @Test + fun testRecreateAsTransient_timeoutStarted() { + var isPinned by TASKBAR_PINNING.asProperty(context) + isPinned = true + activityContext.controllers.sharedState?.taskbarWasPinned = true + + isPinned = false + assertThat(stashController.timeoutAlarm.alarmPending()).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testSupportsVisualStashing_transientMode_supported() { + assertThat(stashController.supportsVisualStashing()).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testSupportsVisualStashing_pinnedMode_supported() { + assertThat(stashController.supportsVisualStashing()).isTrue() + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testSupportsVisualStashing_threeButtonsMode_unsupported() { + assertThat(stashController.supportsVisualStashing()).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testGetStashDuration_transientMode() { + assertThat(stashController.stashDuration).isEqualTo(TRANSIENT_TASKBAR_STASH_DURATION) + } + + @Test + @TaskbarMode(PINNED) + fun testGetStashDuration_pinnedMode() { + assertThat(stashController.stashDuration).isEqualTo(PINNED_TASKBAR_TRANSITION_DURATION) + } + + @Test + @TaskbarMode(PINNED) + fun testIsStashed_pinnedInApp_isUnstashed() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, true) + stashController.applyState(0) + } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testIsStashed_transientInApp_isStashed() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, true) + stashController.applyState(0) + } + assertThat(stashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testIsStashed_transientNotInApp_isUnstashed() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, false) + stashController.applyState(0) + } + assertThat(stashController.isStashed).isFalse() + } + + @Test + fun testIsStashed_stashedInLauncherState_isStashed() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, false) + stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, true) + stashController.applyState(0) + } + assertThat(stashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testIsStashed_transientInOverview_isUnstashed() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, false) + stashController.updateStateForFlag(FLAG_IN_OVERVIEW, true) + stashController.applyState(0) + } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(PINNED) + fun testIsStashed_pinnedInOverviewWithIme_isStashed() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, false) + stashController.updateStateForFlag(FLAG_IN_OVERVIEW, true) + stashController.updateStateForFlag(FLAG_STASHED_IME, true) + stashController.applyState(0) + } + assertThat(stashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testIsStashed_pinnedTaskbarWithPinnedApp_isStashed() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, true) + stashController.updateStateForFlag(FLAG_STASHED_SYSUI, true) // App pinned. + stashController.applyState(0) + } + assertThat(stashController.isStashed).isTrue() + } + + @Test + fun testIsInStashedLauncherState_flagUnset_false() { + stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, false) + assertThat(stashController.isInStashedLauncherState).isFalse() + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testIsInStashedLauncherState_flagSetInThreeButtonsMode_false() { + stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, true) + assertThat(stashController.isInStashedLauncherState).isFalse() + } + + @Test + @TaskbarMode(PINNED) + fun testIsInStashedLauncherState_flagSetInPinnedMode_true() { + stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, true) + assertThat(stashController.isInStashedLauncherState).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testIsTaskbarVisibleAndNotStashing_pinnedButNotVisible_false() { + getInstrumentation().runOnMainSync { + viewController.taskbarIconAlpha.get(ALPHA_INDEX_STASH).value = 0f + } + assertThat(stashController.isTaskbarVisibleAndNotStashing).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testIsTaskbarVisibleAndNotStashing_visibleButStashed_false() { + getInstrumentation().runOnMainSync { + viewController.taskbarIconAlpha.get(ALPHA_INDEX_STASH).value = 1f + } + assertThat(stashController.isTaskbarVisibleAndNotStashing).isFalse() + } + + @Test + @TaskbarMode(PINNED) + fun testIsTaskbarVisibleAndNotStashing_pinnedAndVisible_true() { + getInstrumentation().runOnMainSync { + viewController.taskbarIconAlpha.get(ALPHA_INDEX_STASH).value = 1f + } + assertThat(stashController.isTaskbarVisibleAndNotStashing).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testGetTouchableHeight_isStashed_stashedHeight() { + assertThat(stashController.touchableHeight).isEqualTo(stashController.stashedHeight) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testGetTouchableHeight_unstashedTransientMode_heightAndBottomMargin() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, false) + stashController.applyState(0) + } + + val expectedHeight = + activityContext.deviceProfile.run { taskbarHeight + taskbarBottomMargin } + assertThat(stashController.touchableHeight).isEqualTo(expectedHeight) + } + + @Test + @TaskbarMode(PINNED) + fun testGetTouchableHeight_pinnedMode_taskbarHeight() { + assertThat(stashController.touchableHeight) + .isEqualTo(activityContext.deviceProfile.taskbarHeight) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testGetContentHeightToReportToApps_transientMode_stashedHeight() { + assertThat(stashController.contentHeightToReportToApps) + .isEqualTo(stashController.stashedHeight) + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testGetContentHeightToReportToApps_threeButtonsMode_taskbarHeight() { + assertThat(stashController.contentHeightToReportToApps) + .isEqualTo(activityContext.deviceProfile.taskbarHeight) + } + + @Test + @TaskbarMode(PINNED) + fun testGetContentHeightToReportToApps_pinnedMode_taskbarHeight() { + assertThat(stashController.contentHeightToReportToApps) + .isEqualTo(activityContext.deviceProfile.taskbarHeight) + } + + @Test + @TaskbarMode(PINNED) + @UserSetupMode + fun testGetContentHeightToReportToApps_pinnedInSetupMode_setupWizardInsets() { + assertThat(stashController.contentHeightToReportToApps) + .isEqualTo(context.resources.getDimensionPixelSize(R.dimen.taskbar_suw_insets)) + } + + @Test + @TaskbarMode(PINNED) + fun testGetContentHeightToReportToApps_pinnedModeButFolded_stashedHeight() { + getInstrumentation().runOnMainSync { + stashedHandleViewController.stashedHandleAlpha.get(ALPHA_INDEX_STASHED).value = 1f + stashController.updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, true) + } + assertThat(stashController.contentHeightToReportToApps) + .isEqualTo(stashController.stashedHeight) + } + + @Test + @TaskbarMode(PINNED) + fun testGetContentHeightToReportToApps_homeDisabledWhenFolded_zeroHeight() { + getInstrumentation().runOnMainSync { + stashedHandleViewController.stashedHandleAlpha.get(ALPHA_INDEX_STASHED).value = 1f + stashedHandleViewController.setIsHomeButtonDisabled(true) + stashController.updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, true) + } + assertThat(stashController.contentHeightToReportToApps).isEqualTo(0) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testGetTappableHeightToReportToApps_transientMode_zeroHeight() { + assertThat(stashController.tappableHeightToReportToApps).isEqualTo(0) + } + + @Test + @TaskbarMode(PINNED) + fun testGetTappableHeightToReportToApps_pinnedMode_taskbarHeight() { + assertThat(stashController.tappableHeightToReportToApps) + .isEqualTo(activityContext.deviceProfile.taskbarHeight) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_unstashTaskbar_updatesState() { + getInstrumentation().runOnMainSync { + stashController.updateAndAnimateTransientTaskbar(false) + } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_runUnstashAnimation_startsTaskbarTimeout() { + getInstrumentation().runOnMainSync { + stashController.updateAndAnimateTransientTaskbar(false) + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(stashController.timeoutAlarm.alarmPending()).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_finishTaskbarTimeout_taskbarStashes() { + getInstrumentation().runOnMainSync { + stashController.updateAndAnimateTransientTaskbar(false) + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(stashController.timeoutAlarm.alarmPending()).isTrue() + + getInstrumentation().runOnMainSync { + stashController.timeoutAlarm.finishAlarm() + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(stashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_autoHideSuspendedForEdu_remainsUnstashed() { + getInstrumentation().runOnMainSync { + stashController.updateAndAnimateTransientTaskbar(false) + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + + getInstrumentation().runOnMainSync { + autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_EDU_OPEN, true) + stashController.updateAndAnimateTransientTaskbar(true) + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_unstashTaskbarWithBubbles_bubbleBarUnstashes() { + getInstrumentation().runOnMainSync { + bubbleBarViewController.setHiddenForBubbles(false) + bubbleStashController.stashBubbleBarImmediate() + stashController.updateAndAnimateTransientTaskbar(false, true) + } + assertThat(bubbleStashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_unstashTaskbarWithoutBubbles_bubbleBarStashed() { + getInstrumentation().runOnMainSync { + bubbleBarViewController.setHiddenForBubbles(false) + bubbleStashController.stashBubbleBarImmediate() + stashController.updateAndAnimateTransientTaskbar(false, false) + } + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_stashTaskbarWithBubbles_bubbleBarStashes() { + getInstrumentation().runOnMainSync { + bubbleBarViewController.setHiddenForBubbles(false) + bubbleStashController.showBubbleBarImmediate() + stashController.updateAndAnimateTransientTaskbar(true, true) + } + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_stashTaskbarWithoutBubbles_bubbleBarUnstashed() { + getInstrumentation().runOnMainSync { + bubbleBarViewController.setHiddenForBubbles(false) + bubbleStashController.showBubbleBarImmediate() + stashController.updateAndAnimateTransientTaskbar(true, false) + } + assertThat(bubbleStashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUpdateAndAnimateTransientTaskbar_bubbleBarExpandedBeforeTimeout_expandedAfterwards() { + getInstrumentation().runOnMainSync { + bubbleBarViewController.setHiddenForBubbles(false) + bubbleBarViewController.isExpanded = true + stashController.updateAndAnimateTransientTaskbar(false) + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(stashController.timeoutAlarm.alarmPending()).isTrue() + + getInstrumentation().runOnMainSync { + stashController.timeoutAlarm.finishAlarm() + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(bubbleBarViewController.isExpanded).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testToggleTaskbarStash_pinnedMode_doesNothing() { + getInstrumentation().runOnMainSync { stashController.toggleTaskbarStash() } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testToggleTaskbarStash_transientMode_unstashesTaskbar() { + getInstrumentation().runOnMainSync { stashController.toggleTaskbarStash() } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testToggleTaskbarStash_twiceInTransientMode_stashesTaskbar() { + getInstrumentation().runOnMainSync { + stashController.toggleTaskbarStash() + stashController.toggleTaskbarStash() + } + assertThat(stashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testToggleTaskbarStash_notInAppWithTransientMode_doesNothing() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, false) + stashController.applyState(0) + stashController.toggleTaskbarStash() + } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testAnimateTransientTaskbar_bubblesShownInOverview_stashesTaskbar() { + // Start in Overview. Should unstash Taskbar. + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, false) + stashController.updateStateForFlag(FLAG_IN_APP, false) + stashController.updateStateForFlag(FLAG_IN_OVERVIEW, true) + stashController.applyState(0) + } + assertThat(stashController.isStashed).isFalse() + + // Expand bubbles. Should stash Taskbar. + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, false) + animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION) + } + assertThat(stashController.isStashed).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testAnimatePinnedTaskbar_imeShown_replacesIconsWithHandle() { + assume().that(activityContext.isHardwareKeyboard).isFalse() + + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false) + animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME) + } + assertThat(viewController.areIconsVisible()).isFalse() + assertThat(stashedHandleViewController.isStashedHandleVisible).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testAnimatePinnedTaskbar_imeHidden_replacesHandleWithIcons() { + assume().that(activityContext.isHardwareKeyboard).isFalse() + + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true) + animatorTestRule.advanceTimeBy(0) + } + + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(0, true) + animatorTestRule.advanceTimeBy(0) + } + assertThat(stashedHandleViewController.isStashedHandleVisible).isFalse() + assertThat(viewController.areIconsVisible()).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testAnimatePinnedTaskbar_imeHidden_verifyAnimationDuration() { + assume().that(activityContext.isHardwareKeyboard).isFalse() + + // Start with IME shown. + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true) + animatorTestRule.advanceTimeBy(0) + } + + // Hide IME with animation. + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(0, false) + // Fast forward without start delay. + animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME) + } + // Icons should not be visible yet due to start delay. + assertThat(viewController.areIconsVisible()).isFalse() + + // Advance by start delay retroactively. Animation should complete. + getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(stashController.taskbarStashStartDelayForIme) + } + assertThat(viewController.areIconsVisible()).isTrue() + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testAnimateThreeButtonsTaskbar_imeShown_hidesIconsAndBg() { + assume().that(activityContext.isHardwareKeyboard).isFalse() + + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false) + animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME) + } + assertThat(viewController.areIconsVisible()).isFalse() + assertThat(dragLayerController.imeBgTaskbar.value).isEqualTo(0) + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testAnimateThreeButtonsTaskbar_imeHidden_showsIconsAndBg() { + assume().that(activityContext.isHardwareKeyboard).isFalse() + + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false) + animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME) + } + + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(0, false) + animatorTestRule.advanceTimeBy( + TASKBAR_STASH_DURATION_FOR_IME + stashController.taskbarStashStartDelayForIme + ) + } + assertThat(viewController.areIconsVisible()).isTrue() + assertThat(dragLayerController.imeBgTaskbar.value).isEqualTo(1) + } + + @Test + @TaskbarMode(PINNED) + fun testSetSystemGestureInProgress_whileImeShown_unstashesTaskbar() { + assume().that(activityContext.isHardwareKeyboard).isFalse() + + getInstrumentation().runOnMainSync { + stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true) + animatorTestRule.advanceTimeBy(0) + } + + getInstrumentation().runOnMainSync { + stashController.setSystemGestureInProgress(true) + animatorTestRule.advanceTimeBy( + TASKBAR_STASH_DURATION_FOR_IME + stashController.taskbarStashStartDelayForIme + ) + } + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(PINNED) + fun testSysuiStateImeShowingInApp_hardwareKeyboardWithPinnedMode_notStashedForIme() { + assume().that(activityContext.isHardwareKeyboard).isTrue() + + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, true) + stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true) + } + + assertThat(stashController.isStashed).isFalse() + } + + @Test + @TaskbarMode(PINNED) + fun testUnlockTransition_pinnedMode_fadesOutHandle() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, true) + stashController.applyState(0) + } + assertThat(stashedHandleViewController.isStashedHandleVisible).isTrue() + + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, false) + stashController.applyState() + animatorTestRule.advanceTimeBy(stashController.stashDuration) + } + assertThat(stashedHandleViewController.isStashedHandleVisible).isFalse() + } + + @Test + @TaskbarMode(TRANSIENT) + fun testUnlockTransition_transientMode_fadesOutHandleEarly() { + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_IN_APP, false) + stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, true) + stashController.applyState(0) + } + assertThat(stashedHandleViewController.isStashedHandleVisible).isTrue() + + getInstrumentation().runOnMainSync { + stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, false) + stashController.applyState() + // Time it takes for just the handle to hide (full stash animation is longer). + animatorTestRule.advanceTimeBy(TRANSIENT_TASKBAR_STASH_ALPHA_DURATION) + } + assertThat(stashedHandleViewController.isStashedHandleVisible).isFalse() + } +} + +private fun TaskbarStashController.updateStateForFlag(flag: Int, value: Boolean) { + updateStateForFlag(flag.toLong(), value) +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt deleted file mode 100644 index a999e7f7de8..00000000000 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.taskbar - -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() { - this@TaskbarUnitTestRule.target = 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/TaskbarViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt new file mode 100644 index 00000000000..b13eafe5bb1 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt @@ -0,0 +1,236 @@ +/* + * 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.view.View +import com.android.launcher3.taskbar.TaskbarViewController.DIVIDER_VIEW_POSITION_OFFSET +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +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"]) +/** + * Legend for the comments below: + * ``` + * A: All Apps Button + * H: Hotseat item + * |: Divider + * R: Recent item + * ``` + * + * The comments are formatted in two lines: + * ``` + * // Items in taskbar, e.g. A | HHHHHH + * // Index of items relative to Hotseat: -1 -.5 012345 + * ``` + */ +class TaskbarViewControllerTest { + + @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var taskbarViewController: TaskbarViewController + + @Test + fun testGetPositionInHotseat_allAppsButton_nonRtl() { + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ 6, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ true, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1, + ) + // [>A<] | [HHHHHH] + // -1 -.5 012345 + assertThat(position).isEqualTo(-1) + } + + @Test + fun testGetPositionInHotseat_allAppsButton_rtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ true, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1, + ) + // [HHHHHH] | [>A<] + // 012345 5.5 6 + assertThat(position).isEqualTo(numShownHotseatIcons) + } + + @Test + fun testGetPositionInHotseat_dividerView_notForRecents_nonRtl() { + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ 6, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1, + ) + // [A] >|< [HHHHHH] + // -1 -.5 012345 + assertThat(position).isEqualTo(-DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_dividerView_forRecents_nonRtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ true, + /* recentTaskIndex = */ -1, + ) + // [A] [HHHHHH] >|< [RR] + // -1 012345 5.5 67 + assertThat(position).isEqualTo(numShownHotseatIcons - DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_dividerView_notForRecents_rtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1, + ) + // [HHHHHH] >|< [A] + // 012345 5.5 6 + assertThat(position).isEqualTo(numShownHotseatIcons - DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_dividerView_forRecents_rtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ true, + /* recentTaskIndex = */ -1, + ) + // [HHHHHH][A] >|< [RR] + // 012345 6 6.5 78 + assertThat(position).isEqualTo(numShownHotseatIcons + DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_recentTasks_firstRecentIndex_nonRtl() { + val numShownHotseatIcons = 6 + val recentTaskIndex = 0 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ recentTaskIndex, + ) + // [A][HHHHHH] | [>RR<] + // -1 012345 5.5 6 7 + assertThat(position).isEqualTo(numShownHotseatIcons + recentTaskIndex) + } + + @Test + fun testGetPositionInHotseat_recentTasks_firstRecentIndex_rtl() { + val numShownHotseatIcons = 6 + val recentTaskIndex = 0 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ recentTaskIndex, + ) + // [HHHHHH][A] | [>RR<] + // 012345 6 6.5 7 8 + assertThat(position).isEqualTo(numShownHotseatIcons + 1 + recentTaskIndex) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt new file mode 100644 index 00000000000..24ed81f4fa3 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt @@ -0,0 +1,166 @@ +/* + * 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.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf +import android.platform.test.flag.junit.SetFlagsRule +import com.android.launcher3.R +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS +import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER +import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT +import com.android.launcher3.taskbar.TaskbarIconType.RECENT +import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents +import com.android.launcher3.taskbar.rules.TaskbarDeviceEmulationRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric +import com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@RunWith(ParameterizedAndroidJunit4::class) +class TaskbarViewTest(deviceName: String, flags: FlagsParameterization) { + + companion object { + @JvmStatic + @Parameters(name = "{0},{1}") + fun getParams(): List> { + val devices = + if (isRunningInRobolectric) { + listOf("pixelFoldable2023", "pixelTablet2023") + } else { + listOf("onDevice") // Unused. + } + val flags = allCombinationsOf(FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION) + return devices.flatMap { d -> flags.map { f -> arrayOf(d, f) } } // Cartesian product. + } + } + + @get:Rule(order = 0) val setFlagsRule = SetFlagsRule(flags) + @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 2) val deviceEmulationRule = TaskbarDeviceEmulationRule(context, deviceName) + @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + private lateinit var taskbarView: TaskbarView + + @Before + fun obtainView() { + taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view) + } + + @Test + fun testUpdateItems_noItems_hasOnlyAllApps() { + runOnMainSync { taskbarView.updateItems(emptyArray(), emptyList()) } + assertThat(taskbarView).hasIconTypes(ALL_APPS) + } + + @Test + fun testUpdateItems_hotseatItems_hasDividerBetweenAllAppsAndHotseat() { + runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) } + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, HOTSEAT, HOTSEAT) + } + + @Test + @ForceRtl + fun testUpdateItems_rtlWithHotseatItems_hasDividerBetweenHotseatAndAllApps() { + runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) } + assertThat(taskbarView).hasIconTypes(HOTSEAT, HOTSEAT, DIVIDER, ALL_APPS) + } + + @Test + fun testUpdateItems_withNullHotseatItem_filtersNullItem() { + runOnMainSync { + taskbarView.updateItems(arrayOf(*createHotseatItems(2), null), emptyList()) + } + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, HOTSEAT, HOTSEAT) + } + + @Test + @ForceRtl + fun testUpdateItems_rtlWithNullHotseatItem_filtersNullItem() { + runOnMainSync { + taskbarView.updateItems(arrayOf(*createHotseatItems(2), null), emptyList()) + } + assertThat(taskbarView).hasIconTypes(HOTSEAT, HOTSEAT, DIVIDER, ALL_APPS) + } + + @Test + fun testUpdateItems_recentsItems_hasDividerBetweenAllAppsAndRecents() { + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) } + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, *RECENT * 4) + } + + @Test + fun testUpdateItems_hotseatItemsAndRecents_hasDividerBetweenHotseatAndRecents() { + runOnMainSync { taskbarView.updateItems(createHotseatItems(3), createRecents(2)) } + assertThat(taskbarView).hasIconTypes(ALL_APPS, *HOTSEAT * 3, DIVIDER, *RECENT * 2) + } + + @Test + fun testUpdateItems_addHotseatItem_updatesHotseat() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + taskbarView.updateItems(createHotseatItems(2), createRecents(1)) + } + assertThat(taskbarView).hasIconTypes(ALL_APPS, *HOTSEAT * 2, DIVIDER, RECENT) + } + + @Test + fun testUpdateItems_removeHotseatItem_updatesHotseat() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(2), createRecents(1)) + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + } + assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT) + } + + @Test + fun testUpdateItems_addRecentsItem_updatesRecents() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + taskbarView.updateItems(createHotseatItems(1), createRecents(2)) + } + assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, *RECENT * 2) + } + + @Test + fun testUpdateItems_removeRecentsItem_updatesRecents() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(1), createRecents(2)) + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + } + assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT) + } + + @Test + fun testUpdateItem_addHotseatItemAfterRecentsItem_hotseatItemBeforeDivider() { + runOnMainSync { + taskbarView.updateItems(emptyArray(), createRecents(1)) + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + } + assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt new file mode 100644 index 00000000000..92abbbaa0a1 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt @@ -0,0 +1,151 @@ +/* + * 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.content.ComponentName +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Bitmap.createBitmap +import android.os.Process +import com.android.launcher3.icons.BitmapInfo +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 +import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS +import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER +import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT +import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW +import com.android.launcher3.taskbar.TaskbarIconType.RECENT +import com.android.quickstep.util.GroupTask +import com.android.quickstep.util.SingleTask +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.Task.TaskKey +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Truth.assertAbout +import com.google.common.truth.Truth.assertThat + +/** Common utilities for testing [TaskbarView]. */ +object TaskbarViewTestUtil { + + /** Begins an assertion about a [TaskbarView]. */ + fun assertThat(view: TaskbarView): TaskbarViewSubject { + return assertAbout(::TaskbarViewSubject).that(view) + } + + /** Creates an array of fake hotseat items. */ + fun createHotseatItems(size: Int): Array { + return Array(size) { createHotseatWorkspaceItem(it) } + } + + fun createHotseatWorkspaceItem(id: Int = 0): WorkspaceItemInfo { + return WorkspaceItemInfo( + AppInfo(TEST_COMPONENT, "Test App $id", Process.myUserHandle(), Intent()) + ) + .apply { + this.id = id + // Create a placeholder icon so that the test doesn't try to load a high-res icon. + this.bitmap = BitmapInfo.fromBitmap(createBitmap(1, 1, Bitmap.Config.ALPHA_8)) + } + } + + fun createHotseatAppPairsItem(): AppPairInfo { + return AppPairInfo().apply { + add(createHotseatWorkspaceItem(1)) + add(createHotseatWorkspaceItem(2)) + } + } + + fun createHotseatFolderItem(): FolderInfo { + return FolderInfo().apply { + title = "Test Folder" + add(createHotseatWorkspaceItem(1)) + add(createHotseatWorkspaceItem(2)) + add(createHotseatWorkspaceItem(3)) + } + } + + /** Creates a list of fake recent tasks. */ + fun createRecents(size: Int): List { + return List(size) { + SingleTask( + Task().apply { + key = + TaskKey( + it, + 5, + TEST_INTENT, + TEST_COMPONENT, + Process.myUserHandle().identifier, + System.currentTimeMillis(), + ) + } + ) + } + } +} + +/** A `Truth` [Subject] with extensions for verifying [TaskbarView]. */ +class TaskbarViewSubject(failureMetadata: FailureMetadata, private val view: TaskbarView?) : + Subject(failureMetadata, view) { + + /** Verifies that the types of icons match [expectedTypes] in order. */ + fun hasIconTypes(vararg expectedTypes: TaskbarIconType) { + val actualTypes = + view?.iconViews?.map { + when (it) { + view.allAppsButtonContainer -> ALL_APPS + view.taskbarDividerViewContainer -> DIVIDER + view.taskbarOverflowView -> OVERFLOW + else -> + when (it.tag) { + is ItemInfo -> HOTSEAT + is GroupTask -> RECENT + else -> throw IllegalStateException("Unknown type for $it") + } + } + } + assertThat(actualTypes).containsExactly(*expectedTypes).inOrder() + } + + /** Verifies that recents from [startIndex] have IDs that match [expectedIds] in order. */ + fun hasRecentsOrder(startIndex: Int, expectedIds: List) { + val actualIds = + view?.iconViews?.slice(startIndex.. task.key.id } + } + assertThat(actualIds).containsExactlyElementsIn(expectedIds).inOrder() + } +} + +/** Types of icons in the [TaskbarView]. */ +enum class TaskbarIconType { + ALL_APPS, + DIVIDER, + HOTSEAT, + RECENT, + OVERFLOW; + + operator fun times(size: Int) = Array(size) { this } +} + +private const val TEST_PACKAGE = "com.android.launcher3.taskbar" +private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "Activity") +private val TEST_INTENT = Intent().apply { `package` = TEST_PACKAGE } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt new file mode 100644 index 00000000000..2df4fab4578 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt @@ -0,0 +1,352 @@ +/* + * 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.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.View +import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW +import com.android.launcher3.R +import com.android.launcher3.statehandlers.DesktopVisibilityController +import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS +import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER +import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT +import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW +import com.android.launcher3.taskbar.TaskbarIconType.RECENT +import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems +import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION +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.kotlin.whenever + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) +@EnableFlags(FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, FLAG_TASKBAR_OVERFLOW) +class TaskbarViewWithLayoutTransitionTest { + + @get:Rule(order = 0) val setFlagsRule = SetFlagsRule() + @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + private lateinit var taskbarView: TaskbarView + + private val iconViews: Array + get() = taskbarView.iconViews + + private val desktopVisibilityController: DesktopVisibilityController + get() = DesktopVisibilityController.INSTANCE[context] + + private val maxShownRecents: Int + get() = taskbarView.maxNumIconViews - 2 // Account for All Apps and Divider. + + @Before + fun obtainView() { + taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_hotseatItems_hasDividerBetweenHotseatAndAllApps() { + runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) } + assertThat(taskbarView).hasIconTypes(*HOTSEAT * 2, DIVIDER, ALL_APPS) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_recentsItems_hasDividerBetweenRecentsAndAllApps() { + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) } + assertThat(taskbarView).hasIconTypes(*RECENT * 4, DIVIDER, ALL_APPS) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_recentsItems_recentsAreReversed() { + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) } + assertThat(taskbarView).hasRecentsOrder(startIndex = 0, expectedIds = listOf(3, 2, 1, 0)) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_hotseatItemsAndRecents_hasDividerBetweenRecentsAndHotseat() { + runOnMainSync { taskbarView.updateItems(createHotseatItems(3), createRecents(2)) } + assertThat(taskbarView).hasIconTypes(*RECENT * 2, DIVIDER, *HOTSEAT * 3, ALL_APPS) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_addHotseatItemWithoutRecents_updatesHotseat() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(1), emptyList()) + taskbarView.updateItems(createHotseatItems(2), emptyList()) + } + assertThat(taskbarView).hasIconTypes(*HOTSEAT * 2, DIVIDER, ALL_APPS) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_addHotseatItemWithRecents_updatesHotseat() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + taskbarView.updateItems(createHotseatItems(2), createRecents(1)) + } + assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, *HOTSEAT * 2, ALL_APPS) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_removeHotseatItem_updatesHotseat() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(2), createRecents(1)) + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + } + assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_addRecentsItem_updatesRecents() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + taskbarView.updateItems(createHotseatItems(1), createRecents(2)) + } + assertThat(taskbarView).hasIconTypes(*RECENT * 2, DIVIDER, HOTSEAT, ALL_APPS) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_removeRecentsItem_updatesRecents() { + runOnMainSync { + taskbarView.updateItems(createHotseatItems(1), createRecents(2)) + taskbarView.updateItems(createHotseatItems(1), createRecents(1)) + } + assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS) + } + + @Test + fun testUpdateItems_addRecentsItem_viewAddedOnRight() { + runOnMainSync { + taskbarView.updateItems(emptyArray(), createRecents(1)) + val prevIconViews = iconViews + + val newRecents = createRecents(2) + taskbarView.updateItems(emptyArray(), newRecents) + + assertThat(taskbarView).hasRecentsOrder(startIndex = 2, expectedIds = listOf(0, 1)) + assertThat(iconViews[2]).isSameInstanceAs(prevIconViews[2]) + assertThat(iconViews.last() in prevIconViews).isFalse() + } + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_addRecentsItem_viewAddedOnLeft() { + runOnMainSync { + taskbarView.updateItems(emptyArray(), createRecents(1)) + val prevIconViews = iconViews + + val newRecents = createRecents(2) + taskbarView.updateItems(emptyArray(), newRecents) + + assertThat(taskbarView).hasRecentsOrder(startIndex = 0, expectedIds = listOf(1, 0)) + assertThat(iconViews[1]).isSameInstanceAs(prevIconViews.first()) + assertThat(iconViews.first() in prevIconViews).isFalse() + } + } + + @Test + fun testUpdateItems_removeFirstRecentsItem_correspondingViewRemoved() { + runOnMainSync { + val recents = createRecents(2) + taskbarView.updateItems(emptyArray(), recents) + + val expectedViewToRemove = iconViews[2] + assertThat(expectedViewToRemove.tag).isEqualTo(recents.first()) + + taskbarView.updateItems(emptyArray(), listOf(recents.last())) + assertThat(expectedViewToRemove in iconViews).isFalse() + } + } + + @Test + fun testUpdateItems_removeLastRecentsItem_correspondingViewRemoved() { + runOnMainSync { + val recents = createRecents(2) + taskbarView.updateItems(emptyArray(), recents) + + val expectedViewToRemove = iconViews[3] + assertThat(expectedViewToRemove.tag).isEqualTo(recents.last()) + + taskbarView.updateItems(emptyArray(), listOf(recents.first())) + assertThat(expectedViewToRemove in iconViews).isFalse() + } + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_removeFirstRecentsItem_correspondingViewRemoved() { + runOnMainSync { + val recents = createRecents(2) + taskbarView.updateItems(emptyArray(), recents) + + val expectedViewToRemove = iconViews[1] + assertThat(expectedViewToRemove.tag).isEqualTo(recents.first()) + + taskbarView.updateItems(emptyArray(), listOf(recents.last())) + assertThat(expectedViewToRemove in iconViews).isFalse() + } + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_removeLastRecentsItem_correspondingViewRemoved() { + runOnMainSync { + val recents = createRecents(2) + taskbarView.updateItems(emptyArray(), recents) + + val expectedViewToRemove = iconViews[0] + assertThat(expectedViewToRemove.tag).isEqualTo(recents.last()) + + taskbarView.updateItems(emptyArray(), listOf(recents.first())) + assertThat(expectedViewToRemove in iconViews).isFalse() + } + } + + @Test + fun testUpdateItems_desktopMode_hotseatItem_noDivider() { + whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true) + runOnMainSync { taskbarView.updateItems(createHotseatItems(1), emptyList()) } + assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT) + } + + @Test + @ForceRtl + fun testUpdateItems_rtlAndDesktopMode_hotseatItem_noDivider() { + whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true) + runOnMainSync { taskbarView.updateItems(createHotseatItems(1), emptyList()) } + assertThat(taskbarView).hasIconTypes(HOTSEAT, ALL_APPS) + } + + @Test + fun testUpdateItems_desktopMode_recentItem_hasDivider() { + whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true) + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(1)) } + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, RECENT) + } + + @Test + @ForceRtl + fun testUpdateItems_rtlAndDesktopMode_recentItem_hasDivider() { + whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true) + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(1)) } + assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, ALL_APPS) + } + + @Test + fun testUpdateItems_maxRecents_noOverflow() { + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(maxShownRecents)) } + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, *RECENT * maxShownRecents) + } + + @Test + fun testUpdateItems_moreThanMaxRecents_overflowShownBeforeRecents() { + val recentsSize = maxShownRecents + 2 + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) } + + val expectedNumRecents = RECENT * getExpectedNumRecentsWithOverflow() + assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, OVERFLOW, *expectedNumRecents) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_moreThanMaxRecents_overflowShownAfterRecents() { + val recentsSize = maxShownRecents + 2 + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) } + + val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow() + assertThat(taskbarView).hasIconTypes(*expectedRecents, OVERFLOW, DIVIDER, ALL_APPS) + } + + @Test + fun testUpdateItems_moreThanMaxRecentsWithHotseat_fewerRecentsShown() { + val hotseatSize = 4 + val recentsSize = maxShownRecents + 2 + runOnMainSync { + taskbarView.updateItems(createHotseatItems(hotseatSize), createRecents(recentsSize)) + } + + val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow(hotseatSize) + assertThat(taskbarView) + .hasIconTypes(ALL_APPS, *HOTSEAT * hotseatSize, DIVIDER, OVERFLOW, *expectedRecents) + } + + @Test + @ForceRtl + fun testUpdateItems_rtl_moreThanMaxRecentsWithHotseat_fewerRecentsShown() { + val hotseatSize = 4 + val recentsSize = maxShownRecents + 2 + runOnMainSync { + taskbarView.updateItems(createHotseatItems(hotseatSize), createRecents(recentsSize)) + } + + val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow(hotseatSize) + assertThat(taskbarView) + .hasIconTypes(*expectedRecents, OVERFLOW, DIVIDER, *HOTSEAT * hotseatSize, ALL_APPS) + } + + @Test + fun testUpdateItems_moreThanMaxRecents_verifyShownRecentsOrder() { + val recentsSize = maxShownRecents + 2 + runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) } + + val expectedNumRecents = getExpectedNumRecentsWithOverflow() + assertThat(taskbarView) + .hasRecentsOrder( + startIndex = iconViews.size - expectedNumRecents, + expectedIds = ((recentsSize - expectedNumRecents)..() + + @get:Rule(order = 0) val mockitoRule: MockitoRule = MockitoJUnit.rule() + @get:Rule(order = 1) val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) + + private lateinit var bubbleBarSwipeController: BubbleBarSwipeController + + @Mock private lateinit var bubbleBarController: BubbleBarController + @Mock private lateinit var bubbleBarViewController: BubbleBarViewController + @Mock private lateinit var bubbleStashController: BubbleStashController + @Mock private lateinit var bubbleStashedHandleViewController: BubbleStashedHandleViewController + @Mock private lateinit var bubbleDragController: BubbleDragController + @Mock private lateinit var bubbleDismissController: BubbleDismissController + @Mock private lateinit var bubbleBarPinController: BubbleBarPinController + @Mock private lateinit var bubblePinController: BubblePinController + @Mock private lateinit var bubbleCreator: BubbleCreator + + @Before + fun setUp() { + val dimensionProvider = + object : BubbleBarSwipeController.DimensionProvider { + override val unstashThreshold: Int + get() = UNSTASH_THRESHOLD + + override val maxOverscroll: Int + get() = MAX_OVERSCROLL + } + bubbleBarSwipeController = BubbleBarSwipeController(context, dimensionProvider) + + val bubbleControllers = + BubbleControllers( + bubbleBarController, + bubbleBarViewController, + bubbleStashController, + Optional.of(bubbleStashedHandleViewController), + bubbleDragController, + bubbleDismissController, + bubbleBarPinController, + bubblePinController, + Optional.of(bubbleBarSwipeController), + bubbleCreator, + ) + + bubbleBarSwipeController.init(bubbleControllers) + } + + // region Test that views have damped translation on swipe + + private fun testViewsHaveDampedTranslationOnSwipe(swipe: Float) { + val isUp = swipe < 0 + val damped = OverScroll.dampedScroll(abs(swipe), MAX_OVERSCROLL).toFloat() + val dampedTranslation = if (isUp) -damped else damped + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(swipe) + } + verify(bubbleStashedHandleViewController).setTranslationYForSwipe(dampedTranslation) + verify(bubbleBarViewController).setTranslationYForSwipe(dampedTranslation) + } + + @Test + fun swipeUp_stashedBar_belowUnstashThreshold_viewsHaveDampedTranslation() { + setUpStashedBar() + testViewsHaveDampedTranslationOnSwipe(UP_BELOW_UNSTASH) + } + + @Test + fun swipeUp_stashedBar_aboveUnstashThreshold_viewsHaveDampedTranslation() { + setUpStashedBar() + testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_UNSTASH) + } + + @Test + fun swipeUp_collapsedBar_aboveUnstashThreshold_viewsHaveDampedTranslation() { + setUpCollapsedBar() + testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_UNSTASH) + } + + // endregion + + // region Test that translation on views is reset on finish + + private fun testViewsTranslationResetOnFinish(swipe: Float) { + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(swipe) + bubbleBarSwipeController.finish() + // We use a spring animation. Advance by 5 seconds to give it time to finish + animatorTestRule.advanceTimeBy(5000) + } + val handleSwipeTranslation = argumentCaptor() + val barSwipeTranslation = argumentCaptor() + verify(bubbleStashedHandleViewController, atLeastOnce()) + .setTranslationYForSwipe(handleSwipeTranslation.capture()) + verify(bubbleBarViewController, atLeastOnce()) + .setTranslationYForSwipe(barSwipeTranslation.capture()) + + assertThat(handleSwipeTranslation.firstValue).isNonZero() + assertThat(handleSwipeTranslation.lastValue).isZero() + + assertThat(barSwipeTranslation.firstValue).isNonZero() + assertThat(barSwipeTranslation.lastValue).isZero() + } + + @Test + fun swipeUp_stashedBar_belowUnstashThreshold_animateTranslationToZeroOnFinish() { + setUpStashedBar() + testViewsTranslationResetOnFinish(UP_BELOW_UNSTASH) + } + + @Test + fun swipeUp_stashedBar_aboveUnstashThreshold_animateTranslationToZeroOnFinish() { + setUpStashedBar() + testViewsTranslationResetOnFinish(UP_ABOVE_UNSTASH) + } + + @Test + fun swipeUp_collapsedBar_aboveUnstashThreshold_animateTranslationToZeroOnFinish() { + setUpCollapsedBar() + testViewsTranslationResetOnFinish(UP_ABOVE_UNSTASH) + } + + // endregion + + // region Test swipe interactions on stashed bar + + @Test + fun swipeUp_stashedBar_belowUnstashThreshold_doesNotShowBar() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH) + } + verify(bubbleStashController, never()).showBubbleBar(any()) + } + + @Test + fun swipeUp_stashedBar_belowUnstashThreshold_isSwipeGestureFalse() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH) + } + assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse() + } + + @Test + fun swipeUp_stashedBar_overUnstashThreshold_unstashBubbleBar() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + } + verify(bubbleStashController).showBubbleBar(expandBubbles = false, bubbleBarGesture = true) + } + + @Test + fun swipeUp_stashedBar_overUnstashThreshold_isSwipeGestureTrue() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + } + assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue() + } + + @Test + fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashesMultipleTimes() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH) + } + verify(bubbleStashController).showBubbleBar(expandBubbles = false, bubbleBarGesture = true) + verify(bubbleStashController).stashBubbleBar() + + getInstrumentation().runOnMainSync { bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) } + verify(bubbleStashController, times(2)) + .showBubbleBar(expandBubbles = false, bubbleBarGesture = true) + } + + @Test + fun swipeUp_stashedBar_releaseOverUnstashThreshold_expandsBar() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + } + verify(bubbleStashController, never()).showBubbleBar(expandBubbles = eq(true), any()) + getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() } + verify(bubbleStashController).showBubbleBar(expandBubbles = true, bubbleBarGesture = true) + } + + @Test + fun swipeUp_stashedBar_overUnstashReleaseBelowUnstash_doesNotExpandBar() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + } + verify(bubbleStashController).showBubbleBar(expandBubbles = false, bubbleBarGesture = true) + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH) + bubbleBarSwipeController.finish() + } + verify(bubbleStashController, never()).showBubbleBar(expandBubbles = eq(true), any()) + } + + @Test + fun swipeDown_stashedBar_swipeIgnored() { + setUpStashedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(DOWN) + } + verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any()) + verify(bubbleBarViewController, never()).setTranslationYForSwipe(any()) + verify(bubbleStashController, never()).showBubbleBar(any()) + } + + // endregion + + // region Test swipe interactions on expanded bar + + @Test + fun swipe_expandedBar_swipeIgnored() { + setUpExpandedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + bubbleBarSwipeController.swipeTo(DOWN) + bubbleBarSwipeController.finish() + } + verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any()) + verify(bubbleBarViewController, never()).setTranslationYForSwipe(any()) + verify(bubbleStashController, never()).showBubbleBar(any()) + } + + // endregion + + // region Test swipe interactions on collapsed bar + + @Test + fun swipeUp_collapsedBar_doesNotShowBarDuringDrag() { + setUpCollapsedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH) + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + } + verify(bubbleStashController, never()).showBubbleBar(any()) + } + + @Test + fun swipeUp_collapsedBar_belowUnstashThreshold_isSwipeGestureFalse() { + setUpCollapsedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH) + } + assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse() + } + + @Test + fun swipeUp_collapsedBar_overUnstashThreshold_isSwipeGestureTrue() { + setUpCollapsedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + } + assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue() + } + + @Test + fun swipeUp_collapsedBar_finishOverUnstashThreshold_expandsBar() { + setUpCollapsedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) + bubbleBarSwipeController.finish() + } + verify(bubbleStashController).showBubbleBar(expandBubbles = true, bubbleBarGesture = true) + } + + @Test + fun swipeUp_collapsedBar_finishBelowUnstashThreshold_doesNotExpandBar() { + setUpCollapsedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH) + bubbleBarSwipeController.finish() + } + verify(bubbleStashController, never()).showBubbleBar(any()) + } + + @Test + fun swipeDown_collapsedBar_swipeIgnored() { + setUpCollapsedBar() + getInstrumentation().runOnMainSync { + bubbleBarSwipeController.start() + bubbleBarSwipeController.swipeTo(DOWN) + } + verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any()) + verify(bubbleBarViewController, never()).setTranslationYForSwipe(any()) + verify(bubbleStashController, never()).showBubbleBar(any()) + verify(bubbleStashController, never()).stashBubbleBar() + } + + // endregion + + private fun setUpStashedBar() { + whenever(bubbleStashController.isStashed).thenReturn(true) + whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(false) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + } + + private fun setUpCollapsedBar() { + whenever(bubbleStashController.isStashed).thenReturn(false) + whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(true) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + } + + private fun setUpExpandedBar() { + whenever(bubbleStashController.isStashed).thenReturn(false) + whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(true) + whenever(bubbleBarViewController.isExpanded).thenReturn(true) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt new file mode 100644 index 00000000000..4ae887718b1 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.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.content.Context +import android.graphics.Color +import android.graphics.Path +import android.graphics.drawable.ColorDrawable +import android.view.LayoutInflater +import androidx.core.graphics.drawable.toBitmap +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.wm.shell.shared.bubbles.BubbleInfo +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleViewTest { + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var bubbleView: BubbleView + private lateinit var overflowView: BubbleView + private lateinit var bubble: BubbleBarBubble + + @Test + fun hasUnseenContent_bubble() { + setupBubbleViews() + assertThat(bubbleView.hasUnseenContent()).isTrue() + + bubbleView.markSeen() + assertThat(bubbleView.hasUnseenContent()).isFalse() + } + + @Test + fun hasUnseenContent_overflow() { + setupBubbleViews() + assertThat(overflowView.hasUnseenContent()).isFalse() + } + + private fun setupBubbleViews() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val inflater = LayoutInflater.from(context) + + val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20) + overflowView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView + overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap) + + val bubbleInfo = + BubbleInfo( + "key", + 0, + null, + null, + 0, + context.packageName, + null, + null, + false, + true, + null, + ) + bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView + bubble = + BubbleBarBubble( + bubbleInfo, + bubbleView, + bitmap, + bitmap, + Color.WHITE, + Path(), + "", + null, + ) + bubbleView.setBubble(bubble) + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt new file mode 100644 index 00000000000..da362bdfb33 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.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.taskbar.bubbles.animation + +import androidx.core.animation.AnimatorTestRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleAnimatorTest { + + @get:Rule val animatorTestRule = AnimatorTestRule() + + private lateinit var bubbleAnimator: BubbleAnimator + + @Test + fun animateNewBubble_isRunning() { + bubbleAnimator = + BubbleAnimator( + iconSize = 40f, + expandedBarIconSpacing = 10f, + bubbleCount = 5, + onLeft = false, + ) + val listener = TestBubbleAnimatorListener() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleAnimator.animateNewBubble(selectedBubbleIndex = 2, listener = listener) + } + + assertThat(bubbleAnimator.isRunning).isTrue() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + assertThat(bubbleAnimator.isRunning).isFalse() + } + + @Test + fun animateRemovedBubble_isRunning() { + bubbleAnimator = + BubbleAnimator( + iconSize = 40f, + expandedBarIconSpacing = 10f, + bubbleCount = 5, + onLeft = false, + ) + val listener = TestBubbleAnimatorListener() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleAnimator.animateRemovedBubble( + bubbleIndex = 2, + selectedBubbleIndex = 3, + removingLastBubble = false, + removingLastRemainingBubble = false, + listener, + ) + } + + assertThat(bubbleAnimator.isRunning).isTrue() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + assertThat(bubbleAnimator.isRunning).isFalse() + } + + @Test + fun animateNewAndRemoveOld_isRunning() { + bubbleAnimator = + BubbleAnimator( + iconSize = 40f, + expandedBarIconSpacing = 10f, + bubbleCount = 5, + onLeft = false, + ) + val listener = TestBubbleAnimatorListener() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleAnimator.animateNewAndRemoveOld( + selectedBubbleIndex = 3, + newlySelectedBubbleIndex = 2, + removedBubbleIndex = 1, + addedBubbleIndex = 3, + listener, + ) + } + + assertThat(bubbleAnimator.isRunning).isTrue() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + assertThat(bubbleAnimator.isRunning).isFalse() + } + + private class TestBubbleAnimatorListener : BubbleAnimator.Listener { + + override fun onAnimationUpdate(animatedFraction: Float) {} + + override fun onAnimationCancel() {} + + override fun onAnimationEnd() {} + } +} 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 deleted file mode 100644 index cc579abc9ed..00000000000 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.taskbar.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() - 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() - 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 PhysicsAnimator.assertIsRunning() { - InstrumentationRegistry.getInstrumentation().runOnMainSync { - assertThat(isRunning()).isTrue() - } - } - - private fun PhysicsAnimator.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/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt new file mode 100644 index 00000000000..91fe6a6f023 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.flyout + +import android.content.Context +import android.graphics.Color +import android.graphics.PointF +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import android.widget.TextView +import androidx.core.animation.AnimatorTestRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.R +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** Unit tests for [BubbleBarFlyoutController] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleBarFlyoutControllerTest { + + @get:Rule val animatorTestRule = AnimatorTestRule() + + private lateinit var flyoutController: BubbleBarFlyoutController + private lateinit var flyoutContainer: FrameLayout + private lateinit var flyoutCallbacks: FakeFlyoutCallbacks + private val context = ApplicationProvider.getApplicationContext() + private val flyoutMessage = BubbleBarFlyoutMessage(icon = null, "sender name", "message") + private var onLeft = true + private var flyoutTy = 50f + + private val showAnimationDuration = 400L + private val hideAnimationDuration = 350L + + @Before + fun setUp() { + flyoutContainer = FrameLayout(context) + val positioner = + object : BubbleBarFlyoutPositioner { + override val isOnLeft + get() = onLeft + + override val targetTy + get() = flyoutTy + + override val distanceToCollapsedPosition = PointF(100f, 200f) + override val collapsedSize = 30f + override val collapsedColor = Color.BLUE + override val collapsedElevation = 1f + override val distanceToRevealTriangle = 50f + } + flyoutCallbacks = FakeFlyoutCallbacks() + val flyoutScheduler = FlyoutScheduler { block -> block.invoke() } + flyoutController = + BubbleBarFlyoutController(flyoutContainer, positioner, flyoutCallbacks, flyoutScheduler) + } + + @Test + fun flyoutPosition_left() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + assertThat(flyoutContainer.childCount).isEqualTo(1) + val flyout = flyoutContainer.getChildAt(0) + val lp = flyout.layoutParams as FrameLayout.LayoutParams + assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.LEFT) + assertThat(flyout.translationY).isEqualTo(50f) + } + } + + @Test + fun flyoutPosition_right() { + onLeft = false + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + assertThat(flyoutContainer.childCount).isEqualTo(1) + val flyout = flyoutContainer.getChildAt(0) + val lp = flyout.layoutParams as FrameLayout.LayoutParams + assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.RIGHT) + assertThat(flyout.translationY).isEqualTo(50f) + } + } + + @Test + fun flyoutMessage() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + assertThat(flyoutContainer.childCount).isEqualTo(1) + val flyout = flyoutContainer.getChildAt(0) + val sender = flyout.findViewById(R.id.bubble_flyout_title) + assertThat(sender.text).isEqualTo("sender name") + val message = flyout.findViewById(R.id.bubble_flyout_text) + assertThat(message.text).isEqualTo("message") + } + } + + @Test + fun hideFlyout_removedFromContainer() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + assertThat(flyoutController.hasFlyout()).isTrue() + assertThat(flyoutContainer.childCount).isEqualTo(1) + flyoutController.collapseFlyout {} + animatorTestRule.advanceTimeBy(hideAnimationDuration) + } + assertThat(flyoutContainer.childCount).isEqualTo(0) + assertThat(flyoutController.hasFlyout()).isFalse() + } + + @Test + fun cancelFlyout_fadesOutFlyout() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + assertThat(flyoutContainer.childCount).isEqualTo(1) + val flyoutView = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + assertThat(flyoutView.alpha).isEqualTo(1f) + flyoutController.cancelFlyout {} + animatorTestRule.advanceTimeBy(hideAnimationDuration) + assertThat(flyoutView.alpha).isEqualTo(0f) + } + } + + @Test + fun clickFlyout_notifiesCallback() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + assertThat(flyoutContainer.childCount).isEqualTo(1) + val flyoutView = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + assertThat(flyoutView.alpha).isEqualTo(1f) + animatorTestRule.advanceTimeBy(showAnimationDuration) + flyoutView.performClick() + } + assertThat(flyoutCallbacks.flyoutClicked).isTrue() + } + + @Test + fun updateFlyoutWhileExpanding() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + assertThat(flyoutController.hasFlyout()).isTrue() + val flyout = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + assertThat(flyout.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("message") + // advance the animation about halfway + animatorTestRule.advanceTimeBy(100) + } + assertThat(flyoutController.hasFlyout()).isTrue() + + val newFlyoutMessage = flyoutMessage.copy(message = "new message") + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val flyout = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + // set negative translation to verify that the top boundary extends as a result of + // updating while expanding + flyout.translationY = -50f + flyoutController.updateFlyoutWhileExpanding(newFlyoutMessage) + assertThat(flyout.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("new message") + } + } + + @Test + fun updateFlyoutFullyExpanded() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + animatorTestRule.advanceTimeBy(showAnimationDuration) + } + assertThat(flyoutController.hasFlyout()).isTrue() + + val newFlyoutMessage = flyoutMessage.copy(message = "new message") + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val flyout = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + // set negative translation to verify that the top boundary extends as a result of + // updating while fully expanded + flyout.translationY = -50f + flyoutController.updateFlyoutFullyExpanded(newFlyoutMessage) {} + + // advance the timer so that the fade out animation plays + animatorTestRule.advanceTimeBy(hideAnimationDuration) + assertThat(flyout.alpha).isEqualTo(0) + assertThat(flyout.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("new message") + + // advance the timer so that the fade in animation plays + animatorTestRule.advanceTimeBy(showAnimationDuration) + assertThat(flyout.alpha).isEqualTo(1) + } + } + + @Test + fun updateFlyoutWhileCollapsing() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + setupAndShowFlyout() + animatorTestRule.advanceTimeBy(showAnimationDuration) + } + assertThat(flyoutController.hasFlyout()).isTrue() + + val newFlyoutMessage = flyoutMessage.copy(message = "new message") + InstrumentationRegistry.getInstrumentation().runOnMainSync { + var flyoutCollapsed = false + flyoutController.collapseFlyout { flyoutCollapsed = true } + // advance the fake timer so that the collapse animation runs for 125ms + animatorTestRule.advanceTimeBy(125) + + // update the flyout in the middle of collapsing, which should start expanding it. + var flyoutReversed = false + flyoutController.updateFlyoutWhileCollapsing(newFlyoutMessage) { flyoutReversed = true } + + // the collapse and expand animations use an emphasized interpolator, so the reverse + // path does not take the same time. advance the timer the by full duration of the show + // animation to ensure it completes + animatorTestRule.advanceTimeBy(showAnimationDuration) + val flyout = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + assertThat(flyout.alpha).isEqualTo(1) + assertThat(flyout.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("new message") + // verify that we never called the end action on the collapse animation + assertThat(flyoutCollapsed).isFalse() + // verify that we called the end action on the reverse animation + assertThat(flyoutReversed).isTrue() + } + assertThat(flyoutController.hasFlyout()).isTrue() + } + + private fun setupAndShowFlyout() { + flyoutController.setUpAndShowFlyout(flyoutMessage, {}, {}) + } + + class FakeFlyoutCallbacks : FlyoutCallbacks { + + var flyoutClicked = false + + override fun flyoutClicked() { + flyoutClicked = true + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt new file mode 100644 index 00000000000..88b39d36537 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.stashing + +import android.animation.AnimatorTestRule +import android.content.Context +import android.widget.FrameLayout +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.anim.AnimatedFloat +import com.android.launcher3.taskbar.TaskbarInsetsController +import com.android.launcher3.taskbar.bubbles.BubbleBarView +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState +import com.android.launcher3.util.MultiValueAlpha +import com.google.common.truth.Truth.assertThat +import org.junit.Before +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.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Unit tests for [PersistentBubbleStashController]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class PersistentBubbleStashControllerTest { + + companion object { + const val BUBBLE_BAR_HEIGHT = 100f + const val HOTSEAT_VERTICAL_CENTER = 95 + const val HOTSEAT_TRANSLATION_Y = -45f + const val TASK_BAR_TRANSLATION_Y = -5f + } + + @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) + + @get:Rule val rule: MockitoRule = MockitoJUnit.rule() + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var bubbleBarView: BubbleBarView + + @Mock lateinit var bubbleBarViewController: BubbleBarViewController + + @Mock lateinit var taskbarInsetsController: TaskbarInsetsController + + private lateinit var persistentTaskBarStashController: PersistentBubbleStashController + private lateinit var translationY: AnimatedFloat + private lateinit var scale: AnimatedFloat + private lateinit var alpha: MultiValueAlpha + + @Before + fun setUp() { + persistentTaskBarStashController = + PersistentBubbleStashController(DefaultDimensionsProvider()) + setUpBubbleBarView() + setUpBubbleBarController() + persistentTaskBarStashController.bubbleBarVerticalCenterForHome = HOTSEAT_VERTICAL_CENTER + persistentTaskBarStashController.init( + taskbarInsetsController, + bubbleBarViewController, + null, + ImmediateAction(), + ) + } + + @Test + fun updateLauncherState_noBubbles_controllerNotified() { + // Given bubble bar has no bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + + // When switch to home screen + getInstrumentation().runOnMainSync { + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + } + + // Then bubble bar view controller is notified + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ false) + } + + @Test + fun setBubblesShowingOnHomeUpdatedToFalse_barPositionYUpdated_controllersNotified() { + // Given bubble bar is on home and has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + // When switch out of the home screen + getInstrumentation().runOnMainSync { + persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP + } + + // Then translation Y is animating and the bubble bar controller is notified + assertThat(translationY.isAnimating).isTrue() + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + // Check translation Y is correct and the insets controller is notified + assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y) + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun setBubblesShowingOnHomeUpdatedToTrue_barPositionYUpdated_controllersNotified() { + // Given bubble bar has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + // When switch to home screen + getInstrumentation().runOnMainSync { + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + } + + // Then translation Y is animating and the bubble bar controller is notified + assertThat(translationY.isAnimating).isTrue() + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + + // Check translation Y is correct and the insets controller is notified + assertThat(bubbleBarView.translationY).isEqualTo(HOTSEAT_TRANSLATION_Y) + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun setBubblesShowingOnOverviewUpdatedToFalse_controllersNotified() { + // Given bubble bar is on overview + persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW + clearInvocations(bubbleBarViewController) + + // When switch out of the overview screen + persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP + + // Then bubble bar controller is notified + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + } + + @Test + fun setBubblesShowingOnOverviewUpdatedToTrue_controllersNotified() { + // When switch to the overview screen + persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW + + // Then bubble bar controller is notified + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + } + + @Test + fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() { + // Given screen is locked and bubble bar has bubbles + persistentTaskBarStashController.isSysuiLocked = true + persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + // When switch to the overview screen + getInstrumentation().runOnMainSync { + persistentTaskBarStashController.isSysuiLocked = false + } + + // Then + assertThat(translationY.isAnimating).isTrue() + assertThat(scale.isAnimating).isTrue() + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_STASH_DURATION) + + // Then bubble bar is fully visible at the correct location + assertThat(bubbleBarView.scaleX).isEqualTo(1f) + assertThat(bubbleBarView.scaleY).isEqualTo(1f) + assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(1f) + // Insets controller is notified + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun showBubbleBarImmediateToY() { + // Given bubble bar is fully transparent and scaled to 0 at 0 y position + val targetY = 341f + bubbleBarView.alpha = 0f + bubbleBarView.scaleX = 0f + bubbleBarView.scaleY = 0f + bubbleBarView.translationY = 0f + + // When + persistentTaskBarStashController.showBubbleBarImmediate(targetY) + + // Then all property values are updated + assertThat(bubbleBarView.translationY).isEqualTo(targetY) + assertThat(bubbleBarView.alpha).isEqualTo(1f) + assertThat(bubbleBarView.scaleX).isEqualTo(1f) + assertThat(bubbleBarView.scaleY).isEqualTo(1f) + } + + @Test + fun isTransientTaskbar_false() { + assertThat(persistentTaskBarStashController.isTransientTaskBar).isFalse() + } + + @Test + fun hasHandleView_false() { + assertThat(persistentTaskBarStashController.hasHandleView).isFalse() + } + + @Test + fun isStashed_false() { + assertThat(persistentTaskBarStashController.isStashed).isFalse() + } + + @Test + fun bubbleBarTranslationYForTaskbar() { + // Give bubble bar is on home + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + + // Then bubbleBarTranslationY would be HOTSEAT_TRANSLATION_Y + assertThat(persistentTaskBarStashController.bubbleBarTranslationY) + .isEqualTo(HOTSEAT_TRANSLATION_Y) + + // Give bubble bar is not on home + persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP + + // Then bubbleBarTranslationY would be TASK_BAR_TRANSLATION_Y + assertThat(persistentTaskBarStashController.bubbleBarTranslationY) + .isEqualTo(TASK_BAR_TRANSLATION_Y) + } + + @Test + fun inAppDisplayOverrideProgress_onHome_updatesTranslationFromHomeToInApp() { + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + + assertThat(persistentTaskBarStashController.bubbleBarTranslationY) + .isEqualTo(HOTSEAT_TRANSLATION_Y) + + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f + + val middleBetweenHotseatAndTaskbar = (HOTSEAT_TRANSLATION_Y + TASK_BAR_TRANSLATION_Y) / 2f + assertThat(persistentTaskBarStashController.bubbleBarTranslationY) + .isWithin(0.1f) + .of(middleBetweenHotseatAndTaskbar) + + persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f + + assertThat(persistentTaskBarStashController.bubbleBarTranslationY) + .isEqualTo(TASK_BAR_TRANSLATION_Y) + } + + @Test + fun inAppDisplayOverrideProgress_onHome_updatesInsetsWhenProgressReachesOne() { + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + // Reset invocations to track only changes from in-app display override + clearInvocations(taskbarInsetsController) + + // Insets are not updated for values between 0 and 1 + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f + verify(taskbarInsetsController, never()).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + + // Update insets when progress reaches 1 + persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun inAppDisplayOverrideProgress_onHome_updatesInsetsWhenProgressReachesZero() { + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f + // Reset invocations to track only changes from in-app display override + clearInvocations(taskbarInsetsController) + + // Insets are not updated for values between 0 and 1 + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f + verify(taskbarInsetsController, never()).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + + // Update insets when progress reaches 0 + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0f + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun inAppDisplayOverrideProgress_onHome_cancelExistingAnimation() { + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME + + bubbleBarViewController.bubbleBarTranslationY.animateToValue(100f) + advanceTimeBy(10) + assertThat(bubbleBarViewController.bubbleBarTranslationY.isAnimating).isTrue() + + getInstrumentation().runOnMainSync { + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f + } + assertThat(bubbleBarViewController.bubbleBarTranslationY.isAnimating).isFalse() + } + + @Test + fun inAppDisplayProgressUpdate_inApp_noTranslationUpdate() { + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP + + assertThat(persistentTaskBarStashController.bubbleBarTranslationY) + .isEqualTo(TASK_BAR_TRANSLATION_Y) + + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f + + assertThat(persistentTaskBarStashController.bubbleBarTranslationY) + .isEqualTo(TASK_BAR_TRANSLATION_Y) + } + + @Test + fun inAppDisplayOverrideProgress_inApp_noInsetsUpdate() { + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP + + // Reset invocations to track only changes from in-app display override + clearInvocations(taskbarInsetsController) + + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f + persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f + persistentTaskBarStashController.inAppDisplayOverrideProgress = 0f + + // Never triggers an update to insets + verify(taskbarInsetsController, never()).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun showBubbleBar_expand_bubbleBarGesture() { + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + + persistentTaskBarStashController.showBubbleBar( + expandBubbles = true, + bubbleBarGesture = true, + ) + + verify(bubbleBarViewController).setExpanded(true, true) + } + + @Test + fun showBubbleBar_expand_notBubbleBarGesture() { + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + + persistentTaskBarStashController.showBubbleBar( + expandBubbles = true, + bubbleBarGesture = false, + ) + + verify(bubbleBarViewController).setExpanded(true, false) + } + + @Test + fun showBubbleBar_notExpanding_bubbleBarGesture() { + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + + persistentTaskBarStashController.showBubbleBar( + expandBubbles = false, + bubbleBarGesture = true, + ) + + verify(bubbleBarViewController, never()).setExpanded(any(), any()) + } + + private fun advanceTimeBy(advanceMs: Long) { + // Advance animator for on-device tests + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(advanceMs) } + } + + private fun setUpBubbleBarView() { + getInstrumentation().runOnMainSync { + bubbleBarView = BubbleBarView(context) + bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0) + } + } + + private fun setUpBubbleBarController() { + translationY = AnimatedFloat(Runnable { bubbleBarView.translationY = translationY.value }) + scale = + AnimatedFloat( + Runnable { + val scale: Float = scale.value + bubbleBarView.scaleX = scale + bubbleBarView.scaleY = scale + } + ) + alpha = MultiValueAlpha(bubbleBarView, 1 /* num alpha channels */) + + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(translationY) + whenever(bubbleBarViewController.bubbleBarScaleY).thenReturn(scale) + whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(alpha) + whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt new file mode 100644 index 00000000000..96c2f4554fb --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.stashing + +class ImmediateAction : BubbleStashController.ControllersAfterInitAction { + override fun runAfterInit(action: Runnable) = action.run() +} + +class DefaultDimensionsProvider( + private val taskBarBottomSpace: Int = TASKBAR_BOTTOM_SPACE, + private val taskBarHeight: Int = TASKBAR_HEIGHT, +) : BubbleStashController.TaskbarHotseatDimensionsProvider { + override fun getTaskbarBottomSpace(): Int = taskBarBottomSpace + + override fun getTaskbarHeight(): Int = taskBarHeight + + companion object { + const val TASKBAR_BOTTOM_SPACE = 0 + const val TASKBAR_HEIGHT = 110 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt new file mode 100644 index 00000000000..b24926becaa --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.stashing + +import android.animation.AnimatorSet +import android.animation.AnimatorTestRule +import android.content.Context +import android.view.View +import android.widget.FrameLayout +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.getInstrumentation +import com.android.launcher3.anim.AnimatedFloat +import com.android.launcher3.taskbar.StashedHandleView +import com.android.launcher3.taskbar.TaskbarInsetsController +import com.android.launcher3.taskbar.TaskbarStashController +import com.android.launcher3.taskbar.bubbles.BubbleBarView +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController +import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController +import com.android.launcher3.taskbar.bubbles.BubbleView +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState +import com.android.launcher3.util.MultiValueAlpha +import com.android.wm.shell.shared.animation.PhysicsAnimator +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Unit tests for [TransientBubbleStashController]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class TransientBubbleStashControllerTest { + + companion object { + const val TASKBAR_BOTTOM_SPACE = 5 + const val HOTSEAT_VERTICAL_CENTER = 95 + const val BUBBLE_BAR_WIDTH = 200 + const val BUBBLE_BAR_HEIGHT = 100 + const val HOTSEAT_TRANSLATION_Y = -45f + const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE.toFloat() + const val HANDLE_VIEW_WIDTH = 150 + const val HANDLE_VIEW_HEIGHT = 4 + const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -4.5f + } + + @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) + + @get:Rule val rule: MockitoRule = MockitoJUnit.rule() + + @Mock lateinit var bubbleStashedHandleViewController: BubbleStashedHandleViewController + + @Mock lateinit var bubbleBarViewController: BubbleBarViewController + + @Mock lateinit var taskbarInsetsController: TaskbarInsetsController + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var bubbleBarView: BubbleBarView + private lateinit var stashedHandleView: StashedHandleView + private lateinit var bubbleView: BubbleView + private lateinit var barTranslationY: AnimatedFloat + private lateinit var barScaleX: AnimatedFloat + private lateinit var barScaleY: AnimatedFloat + private lateinit var barAlpha: MultiValueAlpha + private lateinit var bubbleOffsetY: AnimatedFloat + private lateinit var bubbleAlpha: AnimatedFloat + private lateinit var backgroundAlpha: AnimatedFloat + private lateinit var stashedHandleAlpha: MultiValueAlpha + private lateinit var stashedHandleScale: AnimatedFloat + private lateinit var stashedHandleTranslationY: AnimatedFloat + private lateinit var stashPhysicsAnimator: PhysicsAnimator + + private lateinit var mTransientBubbleStashController: TransientBubbleStashController + + @Before + fun setUp() { + val taskbarHotseatDimensionsProvider = + DefaultDimensionsProvider(taskBarBottomSpace = TASKBAR_BOTTOM_SPACE) + mTransientBubbleStashController = + TransientBubbleStashController(taskbarHotseatDimensionsProvider, context) + setUpBubbleBarView() + setUpBubbleBarController() + setUpStashedHandleView() + setUpBubbleStashedHandleViewController() + PhysicsAnimatorTestUtils.prepareForTest() + mTransientBubbleStashController.bubbleBarVerticalCenterForHome = HOTSEAT_VERTICAL_CENTER + mTransientBubbleStashController.init( + taskbarInsetsController, + bubbleBarViewController, + bubbleStashedHandleViewController, + ImmediateAction(), + ) + } + + @Test + fun updateLauncherState_noBubbles_controllerNotified() { + // Given bubble bar has no bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + + // When switch to home screen + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.launcherState = BubbleLauncherState.HOME + } + + // Then bubble bar view controller is notified + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ false) + } + + @Test + fun setBubblesShowingOnHomeUpdatedToTrue_barPositionYUpdated_controllersNotified() { + // Given bubble bar is on home and has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + // When switch out of the home screen + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.launcherState = BubbleLauncherState.HOME + } + + // Then BubbleBarView is animating, BubbleBarViewController controller is notified + assertThat(barTranslationY.isAnimating).isTrue() + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + // Then translation Y is correct and the insets controller is notified + assertThat(barTranslationY.isAnimating).isFalse() + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + assertThat(bubbleBarView.translationY).isEqualTo(HOTSEAT_TRANSLATION_Y) + } + + @Test + fun setBubblesShowingOnOverviewUpdatedToTrue_barPositionYUpdated_controllersNotified() { + // Given bubble bar is on overview and has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + // When switch out of the home screen + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW + } + + // Then BubbleBarView is animating, BubbleBarViewController controller is notified + assertThat(barTranslationY.isAnimating).isTrue() + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + // Then translation Y is correct and the insets controller is notified + assertThat(barTranslationY.isAnimating).isFalse() + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y) + } + + @Test + fun setBubblesShowingOnOverviewUpdatedToTrue_unstashes() { + // Given bubble bar is stashed with bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = true, + expand = false, + ) + } + assertThat(mTransientBubbleStashController.isStashed).isTrue() + + // Move to overview + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW + } + // No longer stashed in overview + assertThat(mTransientBubbleStashController.isStashed).isFalse() + } + + @Test + fun updateStashedAndExpandedState_stashAndCollapse_bubbleBarHidden_stashedHandleShown() { + // Given bubble bar has bubbles and not stashed + mTransientBubbleStashController.isStashed = false + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + + val bubbleInitialTranslation = bubbleView.translationY + + // When stash + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = true, + expand = false, + ) + } + + // Wait until animations ends + advanceTimeBy(BubbleStashController.BAR_STASH_DURATION) + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // Then check BubbleBarController is notified + verify(bubbleBarViewController).onStashStateChanging() + // Bubble bar is stashed + assertThat(mTransientBubbleStashController.isStashed).isTrue() + assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(0f) + assertThat(bubbleBarView.scaleX).isEqualTo(mTransientBubbleStashController.getStashScaleX()) + assertThat(bubbleBarView.scaleY).isEqualTo(mTransientBubbleStashController.getStashScaleY()) + assertThat(bubbleBarView.background.alpha).isEqualTo(255) + // Handle view is visible + assertThat(stashedHandleView.translationY).isEqualTo(0) + assertThat(stashedHandleView.alpha).isEqualTo(1) + // Bubble view is reset + assertThat(bubbleView.translationY).isEqualTo(bubbleInitialTranslation) + assertThat(bubbleView.alpha).isEqualTo(1f) + } + + @Test + fun updateStashedAndExpandedState_unstash_bubbleBarShown_stashedHandleHidden() { + // Given bubble bar has bubbles and is stashed + mTransientBubbleStashController.isStashed = true + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + + val bubbleInitialTranslation = bubbleView.translationY + + // When unstash + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = false, + expand = false, + ) + } + + // Wait until animations ends + advanceTimeBy(BubbleStashController.BAR_STASH_DURATION) + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // Then check BubbleBarController is notified + verify(bubbleBarViewController).onStashStateChanging() + // Bubble bar is unstashed + assertThat(mTransientBubbleStashController.isStashed).isFalse() + assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(1f) + assertThat(bubbleBarView.scaleX).isEqualTo(1f) + assertThat(bubbleBarView.scaleY).isEqualTo(1f) + assertThat(bubbleBarView.background.alpha).isEqualTo(255) + // Handle view is hidden + assertThat(stashedHandleView.translationY).isEqualTo(0) + assertThat(stashedHandleView.alpha).isEqualTo(0) + // Bubble view is reset + assertThat(bubbleView.translationY).isEqualTo(bubbleInitialTranslation) + assertThat(bubbleView.alpha).isEqualTo(1f) + } + + @Test + fun updateStashedAndExpandedState_stash_animatesAlphaForBubblesAndBackgroundSeparately() { + // Given bubble bar has bubbles and is unstashed + mTransientBubbleStashController.isStashed = false + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + + // When stash + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = true, + expand = false, + ) + } + + // Stop after alpha starts + advanceTimeBy(TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY + 10) + + // Bubble bar alpha is set to 1 + assertThat(bubbleBarView.alpha).isEqualTo(1f) + // We animate alpha for background and children separately + assertThat(bubbleView.alpha).isIn(Range.open(0f, 1f)) + assertThat(bubbleBarView.background.alpha).isIn(Range.open(0, 255)) + assertThat(bubbleBarView.background.alpha).isNotEqualTo((bubbleView.alpha * 255f).toInt()) + } + + @Test + fun updateStashedAndExpandedState_unstash_animatesAlphaForBubblesAndBackgroundSeparately() { + // Given bubble bar has bubbles and is stashed + mTransientBubbleStashController.isStashed = true + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + + // When unstash + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = false, + expand = false, + ) + } + + // Stop after alpha starts + advanceTimeBy(TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY + 10) + + // Bubble bar alpha is set to 1 + assertThat(bubbleBarView.alpha).isEqualTo(1f) + // We animate alpha for background and children separately + assertThat(bubbleView.alpha).isIn(Range.open(0f, 1f)) + assertThat(bubbleBarView.background.alpha).isIn(Range.open(0, 255)) + assertThat(bubbleBarView.background.alpha).isNotEqualTo((bubbleView.alpha * 255f).toInt()) + } + + @Test + fun updateStashedAndExpandedState_stash_updateBarVisibilityAfterAnimation() { + // Given bubble bar has bubbles and is unstashed + mTransientBubbleStashController.isStashed = false + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + + // When stash + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = true, + expand = false, + ) + } + + // Hides bubble bar only after animation completes + verify(bubbleBarViewController, never()).setHiddenForStashed(true) + advanceTimeBy(BubbleStashController.BAR_STASH_DURATION) + verify(bubbleBarViewController).setHiddenForStashed(true) + } + + @Test + fun updateStashedAndExpandedState_unstash_updateBarVisibilityBeforeAnimation() { + // Given bubble bar has bubbles and is stashed + mTransientBubbleStashController.isStashed = true + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + + // When unstash + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = false, + expand = false, + ) + } + + // Shows bubble bar immediately + verify(bubbleBarViewController).setHiddenForStashed(false) + } + + @Test + fun updateStashedAndExpandedState_expand_bubbleBarGesture() { + mTransientBubbleStashController.isStashed = true + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = false, + expand = true, + bubbleBarGesture = true, + ) + } + + verify(bubbleBarViewController).setExpanded(true, true) + } + + @Test + fun updateStashedAndExpandedState_expand_notBubbleBarGesture() { + mTransientBubbleStashController.isStashed = true + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = false, + expand = true, + bubbleBarGesture = false, + ) + } + + verify(bubbleBarViewController).setExpanded(true, false) + } + + @Test + fun updateStashedAndExpandedState_notExpanding_bubbleBarGesture() { + mTransientBubbleStashController.isStashed = true + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + whenever(bubbleBarViewController.isExpanded).thenReturn(false) + + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = false, + expand = false, + bubbleBarGesture = true, + ) + } + + verify(bubbleBarViewController, never()).setExpanded(any(), any()) + } + + @Test + fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() { + // Given screen is locked and bubble bar has bubbles + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.isSysuiLocked = true + mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + } + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + + // When switch to the overview screen + getInstrumentation().runOnMainSync { mTransientBubbleStashController.isSysuiLocked = false } + + // Then + assertThat(barTranslationY.isAnimating).isTrue() + assertThat(barScaleX.isAnimating).isTrue() + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_STASH_DURATION) + + // Then bubble bar is fully visible at the correct location + assertThat(bubbleBarView.scaleX).isEqualTo(1f) + assertThat(bubbleBarView.scaleY).isEqualTo(1f) + assertThat(bubbleBarView.translationY) + .isEqualTo(PersistentBubbleStashControllerTest.TASK_BAR_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(1f) + // Insets controller is notified + verify(taskbarInsetsController, atLeastOnce()) + .onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun showBubbleBarImmediateToY() { + // Given bubble bar is fully transparent and scaled to 0 at 0 y position + val targetY = 341f + bubbleBarView.alpha = 0f + bubbleBarView.scaleX = 0f + bubbleBarView.scaleY = 0f + bubbleBarView.translationY = 0f + stashedHandleView.translationY = targetY + + // When + mTransientBubbleStashController.showBubbleBarImmediate(targetY) + + // Then all property values are updated + assertThat(bubbleBarView.translationY).isEqualTo(targetY) + assertThat(bubbleBarView.alpha).isEqualTo(1f) + assertThat(bubbleBarView.scaleX).isEqualTo(1f) + assertThat(bubbleBarView.scaleY).isEqualTo(1f) + // Handle is transparent + assertThat(stashedHandleView.alpha).isEqualTo(0) + // Insets controller is notified + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + // Bubble bar visibility updated + verify(bubbleBarViewController).setHiddenForStashed(false) + } + + @Test + fun stashBubbleBarImmediate() { + // When + mTransientBubbleStashController.stashBubbleBarImmediate() + + // Then all property values are updated + assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(bubbleBarView.scaleX).isEqualTo(mTransientBubbleStashController.getStashScaleX()) + assertThat(bubbleBarView.scaleY).isEqualTo(mTransientBubbleStashController.getStashScaleY()) + // Handle is visible at correct Y position + assertThat(stashedHandleView.alpha).isEqualTo(1) + assertThat(stashedHandleView.translationY).isEqualTo(0) + // Insets controller is notified + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + // Bubble bar visibility updated + verify(bubbleBarViewController).setHiddenForStashed(true) + } + + @Test + fun getTouchableHeight_stashed_stashHeightReturned() { + // When + mTransientBubbleStashController.isStashed = true + val height = mTransientBubbleStashController.getTouchableHeight() + + // Then + assertThat(height).isEqualTo(HANDLE_VIEW_HEIGHT) + } + + @Test + fun getTouchableHeight_unstashed_barHeightReturned() { + // When BubbleBar is not stashed + mTransientBubbleStashController.isStashed = false + val height = mTransientBubbleStashController.getTouchableHeight() + + // Then bubble bar height is returned + assertThat(height).isEqualTo(BUBBLE_BAR_HEIGHT) + } + + @Test + fun getHandleViewAlpha_stashedHasBubbles_alphaPropertyReturned() { + // Given BubbleBar is stashed and has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + mTransientBubbleStashController.isStashed = true + + // When handle view alpha property + val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha() + + // Then the stash handle alpha property should not be null + assertThat(alphaProperty).isNotNull() + } + + @Test + fun getHandleViewAlpha_stashedHasNoBubblesBar_alphaPropertyIsNull() { + // Given BubbleBar is stashed and has no bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + mTransientBubbleStashController.isStashed = true + + // When handle view alpha property + val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha() + + // Then the stash handle alpha property should be null + assertThat(alphaProperty).isNull() + } + + @Test + fun getHandleViewAlpha_unstashedHasBubbles_alphaPropertyIsNull() { + // Given BubbleBar is not stashed and has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + mTransientBubbleStashController.isStashed = false + + // When handle view alpha property + val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha() + + // Then the stash handle alpha property should be null + assertThat(alphaProperty).isNull() + } + + private fun advanceTimeBy(advanceMs: Long) { + // Advance animator for on-device tests + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(advanceMs) } + } + + private fun setUpBubbleBarView() { + getInstrumentation().runOnMainSync { + bubbleBarView = BubbleBarView(context) + bubbleBarView.layoutParams = + FrameLayout.LayoutParams(BUBBLE_BAR_WIDTH, BUBBLE_BAR_HEIGHT) + bubbleView = BubbleView(context) + bubbleBarView.addBubble(bubbleView) + bubbleBarView.layout(0, 0, BUBBLE_BAR_WIDTH, BUBBLE_BAR_HEIGHT) + } + } + + private fun setUpStashedHandleView() { + getInstrumentation().runOnMainSync { + stashedHandleView = StashedHandleView(context) + stashedHandleView.layoutParams = + FrameLayout.LayoutParams(HANDLE_VIEW_WIDTH, HANDLE_VIEW_HEIGHT) + } + } + + private fun setUpBubbleBarController() { + barTranslationY = + AnimatedFloat(Runnable { bubbleBarView.translationY = barTranslationY.value }) + bubbleOffsetY = AnimatedFloat { value -> bubbleBarView.setBubbleOffsetY(value) } + barScaleX = AnimatedFloat { value -> bubbleBarView.scaleX = value } + barScaleY = AnimatedFloat { value -> bubbleBarView.scaleY = value } + barAlpha = MultiValueAlpha(bubbleBarView, 1 /* num alpha channels */) + bubbleAlpha = AnimatedFloat { value -> bubbleBarView.setBubbleAlpha(value) } + backgroundAlpha = AnimatedFloat { value -> bubbleBarView.setBackgroundAlpha(value) } + + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(barTranslationY) + whenever(bubbleBarViewController.bubbleOffsetY).thenReturn(bubbleOffsetY) + whenever(bubbleBarViewController.bubbleBarBackgroundScaleX).thenReturn(barScaleX) + whenever(bubbleBarViewController.bubbleBarBackgroundScaleY).thenReturn(barScaleY) + whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(barAlpha) + whenever(bubbleBarViewController.bubbleBarBubbleAlpha).thenReturn(bubbleAlpha) + whenever(bubbleBarViewController.bubbleBarBackgroundAlpha).thenReturn(backgroundAlpha) + whenever(bubbleBarViewController.bubbleBarCollapsedWidth) + .thenReturn(BUBBLE_BAR_WIDTH.toFloat()) + whenever(bubbleBarViewController.bubbleBarCollapsedHeight) + .thenReturn(BUBBLE_BAR_HEIGHT.toFloat()) + whenever(bubbleBarViewController.createRevealAnimatorForStashChange(any())) + .thenReturn(AnimatorSet()) + } + + private fun setUpBubbleStashedHandleViewController() { + stashedHandleTranslationY = + AnimatedFloat(Runnable { stashedHandleView.translationY = barTranslationY.value }) + stashedHandleScale = + AnimatedFloat( + Runnable { + val scale: Float = barScaleX.value + bubbleBarView.scaleX = scale + bubbleBarView.scaleY = scale + } + ) + stashedHandleAlpha = MultiValueAlpha(stashedHandleView, 1 /* num alpha channels */) + stashPhysicsAnimator = PhysicsAnimator.getInstance(stashedHandleView) + whenever(bubbleStashedHandleViewController.stashedHandleAlpha) + .thenReturn(stashedHandleAlpha) + whenever(bubbleStashedHandleViewController.physicsAnimator).thenReturn(stashPhysicsAnimator) + whenever(bubbleStashedHandleViewController.stashedWidth).thenReturn(HANDLE_VIEW_WIDTH) + whenever(bubbleStashedHandleViewController.stashedHeight).thenReturn(HANDLE_VIEW_HEIGHT) + whenever(bubbleStashedHandleViewController.setTranslationYForSwipe(any())).thenAnswer { + invocation -> + (invocation.arguments[0] as Float).also { stashedHandleView.translationY = it } + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt index eebd8f9f162..1113129ba2d 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt @@ -18,19 +18,19 @@ 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.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices -import com.android.launcher3.views.BaseDragLayer +import com.android.launcher3.util.TestUtil.getOnUiThread import com.android.systemui.shared.system.TaskStackChangeListeners import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -41,193 +41,185 @@ import org.junit.runner.RunWith @EmulatedDevices(["pixelFoldable2023"]) class TaskbarOverlayControllerTest { - @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule() + @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) @InjectController lateinit var overlayController: TaskbarOverlayController private val taskbarContext: TaskbarActivityContext get() = taskbarUnitTestRule.activityContext @Test - @UiThreadTest fun testRequestWindow_twice_reusesWindow() { - val context1 = overlayController.requestWindow() - val context2 = overlayController.requestWindow() + val (context1, context2) = + getOnUiThread { + Pair(overlayController.requestWindow(), overlayController.requestWindow()) + } assertThat(context1).isSameInstanceAs(context2) } @Test - @UiThreadTest fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() { - val context1 = overlayController.requestWindow() - overlayController.hideWindow() + val context1 = getOnUiThread { overlayController.requestWindow() } + runOnMainSync { overlayController.hideWindow() } - val context2 = overlayController.requestWindow() + val context2 = getOnUiThread { overlayController.requestWindow() } assertThat(context1).isNotSameInstanceAs(context2) } @Test - @UiThreadTest fun testRequestWindow_afterHidingOverlay_createsNewWindow() { - val context1 = overlayController.requestWindow() - TestOverlayView.show(context1) - overlayController.hideWindow() + val context1 = getOnUiThread { overlayController.requestWindow() } + runOnMainSync { + TestOverlayView.show(context1) + overlayController.hideWindow() + } - val context2 = overlayController.requestWindow() + val context2 = getOnUiThread { overlayController.requestWindow() } assertThat(context1).isNotSameInstanceAs(context2) } @Test - @UiThreadTest fun testRequestWindow_addsProxyView() { - TestOverlayView.show(overlayController.requestWindow()) + runOnMainSync { 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) + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } + runOnMainSync { + 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 - } - + val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer } // Allow drag layer to attach before checking. - getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() } + runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() } } @Test - @UiThreadTest fun testHideWindow_closesOverlay() { - val overlay = TestOverlayView.show(overlayController.requestWindow()) - overlayController.hideWindow() + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } + runOnMainSync { overlayController.hideWindow() } assertThat(overlay.isOpen).isFalse() } @Test fun testHideWindow_detachesDragLayer() { - lateinit var dragLayer: BaseDragLayer<*> - getInstrumentation().runOnMainSync { - dragLayer = overlayController.requestWindow().dragLayer - } + val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer } // Wait for drag layer to be attached to window before hiding. - getInstrumentation().runOnMainSync { + 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) + val (overlay1, overlay2) = + getOnUiThread { + val context = overlayController.requestWindow() + Pair(TestOverlayView.show(context), TestOverlayView.show(context)) + } - overlay1.close(false) + runOnMainSync { 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) + val (overlay1, overlay2) = + getOnUiThread { + val context = overlayController.requestWindow() + Pair(TestOverlayView.show(context), TestOverlayView.show(context)) + } - overlay1.close(false) - overlay2.close(false) + runOnMainSync { + overlay1.close(false) + overlay2.close(false) + } assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @Test - @UiThreadTest fun testRecreateTaskbar_closesWindow() { - TestOverlayView.show(overlayController.requestWindow()) + runOnMainSync { 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()) - } - + val overlay = getOnUiThread { 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() } + 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 - } + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } + runOnMainSync { taskbarContext.controllers.sharedState?.allAppsVisible = false } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() - getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() } + 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 - } + val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } + runOnMainSync { taskbarContext.controllers.sharedState?.allAppsVisible = true } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() - getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() } + 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 } + val context = getOnUiThread { overlayController.requestWindow() } + val overlay = getOnUiThread { + TestOverlayView.show(context).apply { type = TYPE_OPTIONS_POPUP } + } - overlayController.updateLauncherDeviceProfile( - overlayController.launcherDeviceProfile - .toBuilder(overlayContext) - .setGestureMode(false) - .build() - ) + runOnMainSync { + overlayController.updateLauncherDeviceProfile( + overlayController.launcherDeviceProfile + .toBuilder(context) + .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 } + val context = getOnUiThread { overlayController.requestWindow() } + val overlay = getOnUiThread { + TestOverlayView.show(context).apply { type = TYPE_TASKBAR_ALL_APPS } + } - overlayController.updateLauncherDeviceProfile( - overlayController.launcherDeviceProfile - .toBuilder(overlayContext) - .setGestureMode(false) - .build() - ) + runOnMainSync { + overlayController.updateLauncherDeviceProfile( + overlayController.launcherDeviceProfile + .toBuilder(context) + .setGestureMode(false) + .build() + ) + } assertThat(overlay.isOpen).isTrue() } private class TestOverlayView - private constructor( - private val overlayContext: TaskbarOverlayContext, - ) : AbstractFloatingView(overlayContext, null) { + private constructor(private val overlayContext: TaskbarOverlayContext) : + AbstractFloatingView(overlayContext, null) { var type = TYPE_OPTIONS_POPUP diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt new file mode 100644 index 00000000000..5f7b360bdfa --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.rules + +import com.android.quickstep.RecentsModel +import com.android.quickstep.RecentsModel.RecentTasksChangedListener +import com.android.quickstep.TaskIconCache +import com.android.quickstep.TaskThumbnailCache +import com.android.quickstep.util.GroupTask +import java.util.function.Consumer +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +/** Helper class to mock the [RecentsModel] object in test */ +class MockedRecentsModelHelper { + private val mockIconCache: TaskIconCache = mock() + private val mockThumbnailCache: TaskThumbnailCache = mock() + + var taskListId = 0 + var recentTasksChangedListener: RecentTasksChangedListener? = null + var taskRequests: MutableList<(List) -> Unit> = mutableListOf() + + val mockRecentsModel: RecentsModel = mock { + on { iconCache } doReturn mockIconCache + + on { thumbnailCache } doReturn mockThumbnailCache + + on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null } + + on { registerRecentTasksChangedListener(any()) } doAnswer + { + recentTasksChangedListener = it.getArgument(0) + } + + on { getTasks(anyOrNull(), anyOrNull()) } doAnswer + { + val request = it.getArgument>?>(0) + if (request != null) { + taskRequests.add { response -> request.accept(response) } + } + taskListId + } + + on { getTasks(anyOrNull()) } doAnswer + { + val request = it.getArgument>?>(0) + if (request != null) { + taskRequests.add { response -> request.accept(response) } + } + taskListId + } + + on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt new file mode 100644 index 00000000000..359b87618c7 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.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.taskbar.rules + +import com.android.quickstep.util.GroupTask +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class MockedRecentsModelTestRule(private val modelHelper: MockedRecentsModelHelper) : TestRule { + private var recentTasks: List = emptyList() + + override fun apply(base: Statement?, description: Description?): Statement { + return object : Statement() { + override fun evaluate() { + base?.evaluate() + } + } + } + + // NOTE: For the update to take effect, `resolvePendingTaskRequests()` needs to be called, so + // calbacks to any pending `RecentsModel.getTasks()` get called with the updated task list. + fun updateRecentTasks(tasks: List) { + ++modelHelper.taskListId + recentTasks = tasks + modelHelper.recentTasksChangedListener?.onRecentTasksChanged() + } + + fun resolvePendingTaskRequests() { + val requests = mutableListOf<(List) -> Unit>() + requests.addAll(modelHelper.taskRequests) + modelHelper.taskRequests.clear() + + requests.forEach { it(recentTasks) } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt new file mode 100644 index 00000000000..f22580719fc --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.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.launcher3.taskbar.rules + +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode +import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode +import com.android.launcher3.util.DisplayController +import com.android.launcher3.util.NavigationMode +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.spy + +/** + * Allows tests to specify which Taskbar [Mode] to run under. + * + * [context] should match the test's target context, so that Dagger singleton instances are properly + * sandboxed. + * + * Annotate tests with [TaskbarMode] to set a mode. If the annotation is omitted for any tests, this + * rule is a no-op. + * + * Make sure this rule precedes any rules that depend on [DisplayController], or else the instance + * might be inconsistent across the test lifecycle. + */ +class TaskbarModeRule(private val context: TaskbarWindowSandboxContext) : TestRule { + /** The selected Taskbar mode. */ + enum class Mode { + TRANSIENT, + PINNED, + THREE_BUTTONS, + } + + /** Overrides Taskbar [mode] for a test. */ + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.FUNCTION) + annotation class TaskbarMode(val mode: Mode) + + override fun apply(base: Statement, description: Description): Statement { + val taskbarMode = description.getAnnotation(TaskbarMode::class.java) ?: return base + + return object : Statement() { + override fun evaluate() { + val mode = taskbarMode.mode + + getInstrumentation().runOnMainSync { + DisplayController.INSTANCE[context].let { + if (it is DisplayControllerSpy) { + it.infoModifier = { info -> + spy(info) { + on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT) + on { isPinnedTaskbar } doReturn (mode == Mode.PINNED) + on { navigationMode } doReturn + when (mode) { + Mode.TRANSIENT, + Mode.PINNED -> NavigationMode.NO_BUTTON + Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS + } + } + } + } + } + } + + base.evaluate() + } + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt new file mode 100644 index 00000000000..0dd1324c0f1 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.rules + +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS +import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT +import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode +import com.android.launcher3.util.DisplayController +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.launcher3.util.NavigationMode +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 TaskbarModeRuleTest { + + @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context) + + @Test + @TaskbarMode(TRANSIENT) + fun testTaskbarMode_transient_overridesDisplayController() { + assertThat(DisplayController.isTransientTaskbar(context)).isTrue() + assertThat(DisplayController.isPinnedTaskbar(context)).isFalse() + assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON) + } + + @Test + @TaskbarMode(TRANSIENT) + fun testTaskbarMode_transient_overridesDeviceProfile() { + val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context) + assertThat(dp.isTransientTaskbar).isTrue() + assertThat(dp.isGestureMode).isTrue() + } + + @Test + @TaskbarMode(PINNED) + fun testTaskbarMode_pinned_overridesDisplayController() { + assertThat(DisplayController.isTransientTaskbar(context)).isFalse() + assertThat(DisplayController.isPinnedTaskbar(context)).isTrue() + assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON) + } + + @Test + @TaskbarMode(PINNED) + fun testTaskbarMode_pinned_overridesDeviceProfile() { + val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context) + assertThat(dp.isTransientTaskbar).isFalse() + assertThat(dp.isGestureMode).isTrue() + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testTaskbarMode_threeButtons_overridesDisplayController() { + assertThat(DisplayController.isTransientTaskbar(context)).isFalse() + assertThat(DisplayController.isPinnedTaskbar(context)).isFalse() + assertThat(DisplayController.getNavigationMode(context)) + .isEqualTo(NavigationMode.THREE_BUTTONS) + } + + @Test + @TaskbarMode(THREE_BUTTONS) + fun testTaskbarMode_threeButtons_overridesDeviceProfile() { + val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context) + assertThat(dp.isTransientTaskbar).isFalse() + assertThat(dp.isGestureMode).isFalse() + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt new file mode 100644 index 00000000000..19c88240d95 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.rules + +import android.app.Instrumentation +import android.app.PendingIntent +import android.content.IIntentSender +import android.provider.Settings.Secure.NAV_BAR_KIDS_MODE +import android.provider.Settings.Secure.USER_SETUP_COMPLETE +import android.provider.Settings.Secure.getUriFor +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.LauncherAppState +import com.android.launcher3.taskbar.TaskbarActivityContext +import com.android.launcher3.taskbar.TaskbarControllers +import com.android.launcher3.taskbar.TaskbarManager +import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks +import com.android.launcher3.taskbar.TaskbarUIController +import com.android.launcher3.taskbar.bubbles.BubbleControllers +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR +import com.android.launcher3.util.TestUtil +import com.android.quickstep.AllAppsActionManager +import com.android.quickstep.fallback.window.RecentsDisplayModel +import java.lang.reflect.Field +import java.lang.reflect.ParameterizedType +import java.util.Locale +import java.util.Optional +import org.junit.Assume.assumeTrue +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Manages the Taskbar lifecycle for unit tests. + * + * Tests should pass in themselves as [testInstance]. They also need to provide their target + * [context] through the constructor. + * + * 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 incompatible with this rule. The annotation causes this rule to run on the + * main thread, but it needs to be run on the test thread for it to work properly. Instead, only run + * code that requires the main thread using something like [Instrumentation.runOnMainSync] or + * [TestUtil.getOnUiThread]. + * + * ``` + * @Test + * fun example() { + * instrumentation.runOnMainSync { doWorkThatPostsMessage() } + * // Second lambda will not execute until message is processed. + * instrumentation.runOnMainSync { verifyMessageResults() } + * } + * ``` + */ +class TaskbarUnitTestRule( + private val testInstance: Any, + private val context: TaskbarWindowSandboxContext, + private val controllerInjectionCallback: () -> Unit = {}, +) : TestRule { + + private val instrumentation = InstrumentationRegistry.getInstrumentation() + + private lateinit var taskbarManager: TaskbarManager + + val activityContext: TaskbarActivityContext + get() { + return taskbarManager.currentActivityContext + ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.") + } + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + + // Only run test when Taskbar is enabled. + instrumentation.runOnMainSync { + assumeTrue( + LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent + ) + } + + // Process secure setting annotations. + context.settingsCacheSandbox[getUriFor(USER_SETUP_COMPLETE)] = + if (description.getAnnotation(UserSetupMode::class.java) != null) 0 else 1 + context.settingsCacheSandbox[getUriFor(NAV_BAR_KIDS_MODE)] = + if (description.getAnnotation(NavBarKidsMode::class.java) != null) 1 else 0 + + taskbarManager = + TestUtil.getOnUiThread { + object : + TaskbarManager( + context, + AllAppsActionManager(context, UI_HELPER_EXECUTOR) { + PendingIntent(IIntentSender.Default()) + }, + object : TaskbarNavButtonCallbacks {}, + RecentsDisplayModel.INSTANCE.get(context), + ) { + override fun recreateTaskbars() { + super.recreateTaskbars() + if (currentActivityContext != null) { + injectControllers() + // TODO(b/346394875): we should test a non-default uiController. + activityContext.setUIController(TaskbarUIController.DEFAULT) + controllerInjectionCallback.invoke() + } + } + } + } + + if (description.getAnnotation(ForceRtl::class.java) != null) { + // Needs to be set on window context instead of sandbox context, because it does + // does not propagate between them. However, this change will impact created + // TaskbarActivityContext instances, since they wrap the window context. + // TODO: iterate through all window contexts and do this. + taskbarManager.primaryWindowContext.resources.configuration.setLayoutDirection( + RTL_LOCALE + ) + } + + try { + // Required to complete initialization. + instrumentation.runOnMainSync { taskbarManager.onUserUnlocked() } + + base.evaluate() + } finally { + instrumentation.runOnMainSync { taskbarManager.destroy() } + } + } + } + } + + /** Simulates Taskbar recreation lifecycle. */ + fun recreateTaskbar() = instrumentation.runOnMainSync { taskbarManager.recreateTaskbars() } + + private fun injectControllers() { + val bubbleControllerTypes = + BubbleControllers::class.java.fields.map { f -> + if (f.type == Optional::class.java) { + (f.genericType as ParameterizedType).actualTypeArguments[0] as Class<*> + } else { + f.type + } + } + testInstance.javaClass.fields + .filter { it.isAnnotationPresent(InjectController::class.java) } + .forEach { + val controllers: Any = + if (it.type in bubbleControllerTypes) { + activityContext.controllers.bubbleControllers.orElseThrow { + NoSuchElementException("Bubble controllers are not initialized") + } + } else { + activityContext.controllers + } + injectController(it, testInstance, controllers) + } + } + + private fun injectController(field: Field, testInstance: Any, controllers: Any) { + val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type } + field.set( + testInstance, + controllerFieldsByType[field.type]?.get(controllers) + ?: throw NoSuchElementException("Failed to find controller for ${field.type}"), + ) + } + + /** + * Annotates test controller fields to inject the corresponding controllers from the current + * [TaskbarControllers] instance. + * + * 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 + + /** Overrides [USER_SETUP_COMPLETE] to be `false` for tests. */ + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) + annotation class UserSetupMode + + /** Overrides [NAV_BAR_KIDS_MODE] to be `true` for tests. */ + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) + annotation class NavBarKidsMode + + /** Forces RTL UI for tests. */ + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) + annotation class ForceRtl +} + +private val RTL_LOCALE = Locale.of("ar", "XB") diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt new file mode 100644 index 00000000000..b8b0b5d1e11 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.rules + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import com.android.launcher3.Utilities +import com.android.launcher3.taskbar.TaskbarActivityContext +import com.android.launcher3.taskbar.TaskbarKeyguardController +import com.android.launcher3.taskbar.TaskbarManager +import com.android.launcher3.taskbar.TaskbarStashController +import com.android.launcher3.taskbar.bubbles.BubbleBarController +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.NavBarKidsMode +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.android.wm.shell.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Rule +import org.junit.Test +import org.junit.runner.Description +import org.junit.runner.RunWith +import org.junit.runners.model.Statement + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) +class TaskbarUnitTestRuleTest { + + @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() + @get:Rule(order = 1) val setFlagsRule = SetFlagsRule() + + @Test + fun testSetup_taskbarInitialized() { + onSetup { assertThat(activityContext).isInstanceOf(TaskbarActivityContext::class.java) } + } + + @Test + fun testRecreateTaskbar_activityContextChanged() { + onSetup { + val context1 = activityContext + recreateTaskbar() + val context2 = activityContext + assertThat(context1).isNotSameInstanceAs(context2) + } + } + + @Test + fun testTeardown_taskbarDestroyed() { + val testRule = TaskbarUnitTestRule(this, context) + testRule.apply(EMPTY_STATEMENT, DESCRIPTION).evaluate() + assertThrows(RuntimeException::class.java) { testRule.activityContext } + } + + @Test + fun testInjectController_validControllerType_isInjected() { + val testClass = + object { + @InjectController lateinit var controller: TaskbarStashController + val isInjected: Boolean + get() = ::controller.isInitialized + } + + TaskbarUnitTestRule(testClass, context).apply(EMPTY_STATEMENT, DESCRIPTION).evaluate() + + onSetup(TaskbarUnitTestRule(testClass, context)) { + assertThat(testClass.isInjected).isTrue() + } + } + + @Test + fun testInjectController_multipleControllers_areInjected() { + val testClass = + object { + @InjectController lateinit var controller1: TaskbarStashController + @InjectController lateinit var controller2: TaskbarKeyguardController + val areInjected: Boolean + get() = ::controller1.isInitialized && ::controller2.isInitialized + } + + onSetup(TaskbarUnitTestRule(testClass, context)) { + assertThat(testClass.areInjected).isTrue() + } + } + + @Test + fun testInjectController_invalidControllerType_exceptionThrown() { + val testClass = + object { + @InjectController lateinit var manager: TaskbarManager // Not a controller. + } + + // We cannot use #assertThrows because we also catch an assumption violated exception when + // running #evaluate on devices that do not support Taskbar. + val result = + try { + TaskbarUnitTestRule(testClass, context) + .apply(EMPTY_STATEMENT, DESCRIPTION) + .evaluate() + } catch (e: NoSuchElementException) { + e + } + assertThat(result).isInstanceOf(NoSuchElementException::class.java) + } + + @Test + fun testInjectController_recreateTaskbar_controllerChanged() { + val testClass = + object { + @InjectController lateinit var controller: TaskbarStashController + } + + onSetup(TaskbarUnitTestRule(testClass, context)) { + val controller1 = testClass.controller + recreateTaskbar() + val controller2 = testClass.controller + assertThat(controller1).isNotSameInstanceAs(controller2) + } + } + + @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_BAR) + @Test + fun testInjectBubbleController_bubbleFlagOn_isInjected() { + val testClass = + object { + @InjectController lateinit var controller: BubbleBarController + val isInjected: Boolean + get() = ::controller.isInitialized + } + + TaskbarUnitTestRule(testClass, context).apply(EMPTY_STATEMENT, DESCRIPTION).evaluate() + + onSetup(TaskbarUnitTestRule(testClass, context)) { + assertThat(testClass.isInjected).isTrue() + } + } + + @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_BAR) + @Test + fun testInjectBubbleController_bubbleFlagOff_exceptionThrown() { + val testClass = + object { + @InjectController lateinit var controller: BubbleBarController + } + + // We cannot use #assertThrows because we also catch an assumption violated exception when + // running #evaluate on devices that do not support Taskbar. + val result = + try { + TaskbarUnitTestRule(testClass, context) + .apply(EMPTY_STATEMENT, DESCRIPTION) + .evaluate() + } catch (e: NoSuchElementException) { + e + } + assertThat(result).isInstanceOf(NoSuchElementException::class.java) + } + + @Test + fun testUserSetupMode_default_isComplete() { + onSetup { assertThat(activityContext.isUserSetupComplete).isTrue() } + } + + @Test + fun testUserSetupMode_withAnnotation_isIncomplete() { + @UserSetupMode class Mode + onSetup(description = Description.createSuiteDescription(Mode::class.java)) { + assertThat(activityContext.isUserSetupComplete).isFalse() + } + } + + @Test + fun testNavBarKidsMode_default_navBarNotForcedVisible() { + onSetup { assertThat(activityContext.isNavBarForceVisible).isFalse() } + } + + @Test + fun testNavBarKidsMode_withAnnotation_navBarForcedVisible() { + @NavBarKidsMode class Mode + onSetup(description = Description.createSuiteDescription(Mode::class.java)) { + assertThat(activityContext.isNavBarForceVisible).isTrue() + } + } + + @Test + fun testForceRtlAnnotation_setsActivityContextLayoutDirection() { + @ForceRtl class Rtl + onSetup(description = Description.createSuiteDescription(Rtl::class.java)) { + assertThat(Utilities.isRtl(activityContext.resources)).isTrue() + } + } + + /** + * Executes [runTest] after the [testRule] setup phase completes. + * + * A [description] can also be provided to mimic annotating a test or test class. + */ + private fun onSetup( + testRule: TaskbarUnitTestRule = TaskbarUnitTestRule(this, context), + description: Description = DESCRIPTION, + runTest: TaskbarUnitTestRule.() -> Unit, + ) { + testRule + .apply( + object : Statement() { + override fun evaluate() = runTest(testRule) + }, + description, + ) + .evaluate() + } + + private companion object { + private val EMPTY_STATEMENT = + object : Statement() { + override fun evaluate() = Unit + } + private val DESCRIPTION = + Description.createSuiteDescription(TaskbarUnitTestRuleTest::class.java) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt new file mode 100644 index 00000000000..d96e06eca54 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.rules + +import android.content.Context +import android.content.ContextWrapper +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.core.app.ApplicationProvider +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.dagger.ApplicationContext +import com.android.launcher3.dagger.LauncherAppComponent +import com.android.launcher3.dagger.LauncherAppSingleton +import com.android.launcher3.statehandlers.DesktopVisibilityController +import com.android.launcher3.util.AllModulesMinusWMProxy +import com.android.launcher3.util.DaggerSingletonTracker +import com.android.launcher3.util.DisplayController +import com.android.launcher3.util.FakePrefsModule +import com.android.launcher3.util.SandboxApplication +import com.android.launcher3.util.SettingsCache +import com.android.launcher3.util.SettingsCacheSandbox +import com.android.launcher3.util.window.WindowManagerProxy +import com.android.quickstep.SystemUiProxy +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import javax.inject.Inject +import org.junit.rules.ExternalResource +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import org.mockito.kotlin.spy + +/** + * [SandboxApplication] for running Taskbar tests. + * + * Tests need to run on a [VirtualDisplay] to avoid conflicting with Launcher's Taskbar on the + * [DEFAULT_DISPLAY] (i.e. test is executing on a device). + */ +class TaskbarWindowSandboxContext +private constructor( + private val base: SandboxApplication, + val virtualDisplay: VirtualDisplay, + private val params: SandboxParams, +) : ContextWrapper(base), TestRule { + + val settingsCacheSandbox = SettingsCacheSandbox() + + private val virtualDisplayRule = + object : ExternalResource() { + override fun after() = virtualDisplay.release() + } + + private val singletonSetupRule = + object : ExternalResource() { + override fun before() { + val context = this@TaskbarWindowSandboxContext + val builder = + params.builderBase + .bindSystemUiProxy(params.systemUiProxyProvider.invoke(context)) + .bindSettingsCache(settingsCacheSandbox.cache) + base.initDaggerComponent(builder) + } + } + + override fun apply(statement: Statement, description: Description): Statement { + return RuleChain.outerRule(virtualDisplayRule) + .around(base) + .around(singletonSetupRule) + .apply(statement, description) + } + + companion object { + private const val VIRTUAL_DISPLAY_NAME = "TaskbarSandboxDisplay" + + /** Creates a [SandboxApplication] for Taskbar tests. */ + fun create(params: SandboxParams = SandboxParams()): TaskbarWindowSandboxContext { + val base = ApplicationProvider.getApplicationContext() + val displayManager = checkNotNull(base.getSystemService(DisplayManager::class.java)) + // Create virtual display to avoid clashing with Taskbar on default display. + val virtualDisplay = + base.resources.displayMetrics.let { + displayManager.createVirtualDisplay( + VIRTUAL_DISPLAY_NAME, + it.widthPixels, + it.heightPixels, + it.densityDpi, + /* surface= */ null, + /* flags= */ 0, + ) + } + + return TaskbarWindowSandboxContext( + SandboxApplication(base.createDisplayContext(virtualDisplay.display)), + virtualDisplay, + params, + ) + } + } +} + +/** A wrapper over display controller which allows modifying the underlying info */ +@LauncherAppSingleton +class DisplayControllerSpy +@Inject +constructor( + @ApplicationContext context: Context, + wmProxy: WindowManagerProxy, + prefs: LauncherPrefs, + lifecycle: DaggerSingletonTracker, +) : DisplayController(context, wmProxy, prefs, lifecycle) { + + var infoModifier: ((Info) -> Info)? = null + + override fun getInfo(): Info = infoModifier?.invoke(super.getInfo()) ?: super.getInfo() +} + +@Module +abstract class DisplayControllerModule { + @Binds abstract fun bindDisplayController(controller: DisplayControllerSpy): DisplayController +} + +@Module +object DesktopVisibilityControllerModule { + @JvmStatic + @Provides + @LauncherAppSingleton + fun provideDesktopVisibilityController( + @ApplicationContext context: Context, + systemUiProxy: SystemUiProxy, + lifecycleTracker: DaggerSingletonTracker, + ): DesktopVisibilityController { + return spy(DesktopVisibilityController(context, systemUiProxy, lifecycleTracker)) + } +} + +@LauncherAppSingleton +@Component( + modules = + [ + AllModulesMinusWMProxy::class, + FakePrefsModule::class, + DisplayControllerModule::class, + TaskbarSandboxModule::class, + DesktopVisibilityControllerModule::class, + ] +) +interface TaskbarSandboxComponent : LauncherAppComponent { + + @Component.Builder + interface Builder : LauncherAppComponent.Builder { + @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder + + @BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder + + override fun build(): TaskbarSandboxComponent + } +} + +/** Include additional bindings when building a [TaskbarSandboxComponent]. */ +data class SandboxParams( + val systemUiProxyProvider: (Context) -> SystemUiProxy = { SystemUiProxy(it) }, + val builderBase: TaskbarSandboxComponent.Builder = DaggerTaskbarSandboxComponent.builder(), +) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt new file mode 100644 index 00000000000..69095e7e75a --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.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.launcher3.taskbar.rules + +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.Description +import org.junit.runner.RunWith +import org.junit.runners.model.Statement + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023"]) +class TaskbarWindowSandboxContextTest { + + @Test + fun testVirtualDisplay_releasedOnTeardown() { + val context = TaskbarWindowSandboxContext.create() + assertThat(context.virtualDisplay.token).isNotNull() + + context + .apply( + object : Statement() { + override fun evaluate() = Unit + }, + Description.createSuiteDescription(TaskbarWindowSandboxContextTest::class.java), + ) + .evaluate() + + assertThat(context.virtualDisplay.token).isNull() + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt new file mode 100644 index 00000000000..52238c83d98 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util + +import android.net.Uri +import com.android.launcher3.util.SettingsCache.OnChangeListener +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +/** Provides [SettingsCache] sandboxed from system settings for testing. */ +class SettingsCacheSandbox { + private val values = mutableMapOf() + private val listeners = mutableMapOf>() + + /** + * Fake cache that delegates: + * - [SettingsCache.getValue] to [values] + * - [SettingsCache.mListenerMap] to [listeners]. + */ + val cache = + mock { + on { getValue(any()) } doAnswer { mock.getValue(it.getArgument(0), 1) } + on { getValue(any(), any()) } doAnswer + { + values.getOrDefault(it.getArgument(0), it.getArgument(1)) == 1 + } + + doAnswer { + listeners.getOrPut(it.getArgument(0)) { mutableSetOf() }.add(it.getArgument(1)) + } + .whenever(mock) + .register(any(), any()) + doAnswer { listeners[it.getArgument(0)]?.remove(it.getArgument(1)) } + .whenever(mock) + .unregister(any(), any()) + } + + operator fun get(key: Uri): Int? = values[key] + + operator fun set(key: Uri, value: Int) { + if (value == values[key]) return + values[key] = value + listeners[key]?.forEach { it.onSettingsChanged(value == 1) } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt new file mode 100644 index 00000000000..9b0a95a526c --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.widget.picker + +import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN +import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD +import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_NOT_KEYGUARD +import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class WidgetCategoryFilterTest { + + @Test + fun filterValueZero_everythingMatches() { + val noFilter = WidgetCategoryFilter(categoryMask = 0) + + noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN) + noFilter.assertMatches(WIDGET_CATEGORY_KEYGUARD) + noFilter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD) + noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX) + noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD) + noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_NOT_KEYGUARD) + noFilter.assertMatches( + WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD + ) + noFilter.assertMatches( + WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_NOT_KEYGUARD + ) + noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD) + } + + @Test + fun includeHomeScreen_matchesOnlyIfHomeScreenExists() { + val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN) + + filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + + filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD) + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD) + filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX) + filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_SEARCHBOX) + } + + @Test + fun includeHomeScreenOrKeyguard_matchesIfEitherHomeScreenOrKeyguardExists() { + val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD) + + filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_KEYGUARD) + filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD) + filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD) + + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD) + filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX) + filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD) + } + + @Test + fun excludeNotKeyguard_doesNotMatchIfNotKeyguardExists() { + val filter = WidgetCategoryFilter(WIDGET_CATEGORY_NOT_KEYGUARD.inv()) + + filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_KEYGUARD) + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX) + filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD) + + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD) + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD) + filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD) + filter.assertDoesNotMatch( + WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN + ) + } + + @Test + fun multipleExclusions_doesNotMatchIfExcludedCategoriesExist() { + val filter = + WidgetCategoryFilter( + WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv() + ) + + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX) + filter.assertMatches(WIDGET_CATEGORY_KEYGUARD) + filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD) + + filter.assertDoesNotMatch(WIDGET_CATEGORY_HOME_SCREEN) + filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN) + + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD) + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN) + filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD) + filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD) + filter.assertDoesNotMatch( + WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN + ) + } + + private fun WidgetCategoryFilter.assertMatches(category: Int) { + assertThat(matches(category)).isTrue() + } + + private fun WidgetCategoryFilter.assertDoesNotMatch(category: Int) { + assertThat(matches(category)).isFalse() + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java new file mode 100644 index 00000000000..6adb7b40529 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java @@ -0,0 +1,402 @@ +/* + * 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 android.view.Display.DEFAULT_DISPLAY; + +import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED; +import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION; +import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.view.Display; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.ViewTreeObserver; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherRootView; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.statemanager.BaseState; +import com.android.launcher3.statemanager.StatefulContainer; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.MSDLPlayerWrapper; +import com.android.launcher3.util.SystemUiController; +import com.android.quickstep.util.ContextInitListener; +import com.android.quickstep.util.MotionPauseDetector; +import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.RecentsViewContainer; +import com.android.systemui.shared.Flags; +import com.android.systemui.shared.system.InputConsumerController; +import com.android.wm.shell.shared.split.SplitBounds; + +import com.google.android.msdl.data.model.MSDLToken; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Collections; +import java.util.HashMap; + +public abstract class AbsSwipeUpHandlerTestCase< + STATE_TYPE extends BaseState, + RECENTS_CONTAINER extends Context & RecentsViewContainer & StatefulContainer, + RECENTS_VIEW extends RecentsView, + SWIPE_HANDLER extends AbsSwipeUpHandler, + CONTAINER_INTERFACE extends BaseContainerInterface> { + + protected final LauncherModelHelper mLauncherModelHelper = new LauncherModelHelper(); + protected final LauncherModelHelper.SandboxModelContext mContext = + mLauncherModelHelper.sandboxContext; + protected final InputConsumerController mInputConsumerController = + InputConsumerController.getRecentsAnimationInputConsumer(); + protected final ActivityManager.RunningTaskInfo mRunningTaskInfo = + new ActivityManager.RunningTaskInfo(); + protected final TopTaskTracker.CachedTaskInfo mCachedTaskInfo = + new TopTaskTracker.CachedTaskInfo( + Collections.singletonList(mRunningTaskInfo), /* canEnterDesktop = */ false, + DEFAULT_DISPLAY); + protected final RemoteAnimationTarget mRemoteAnimationTarget = new RemoteAnimationTarget( + /* taskId= */ 0, + /* mode= */ RemoteAnimationTarget.MODE_CLOSING, + /* leash= */ new SurfaceControl(), + /* isTranslucent= */ false, + /* clipRect= */ null, + /* contentInsets= */ null, + /* prefixOrderIndex= */ 0, + /* position= */ null, + /* localBounds= */ null, + /* screenSpaceBounds= */ null, + new Configuration().windowConfiguration, + /* isNotInRecents= */ false, + /* startLeash= */ null, + /* startBounds= */ null, + /* taskInfo= */ mRunningTaskInfo, + /* allowEnterPip= */ false); + + protected RecentsAnimationTargets mRecentsAnimationTargets; + protected TaskAnimationManager mTaskAnimationManager; + + @Mock protected CONTAINER_INTERFACE mActivityInterface; + @Mock protected ContextInitListener mContextInitListener; + @Mock protected RecentsAnimationController mRecentsAnimationController; + @Mock protected STATE_TYPE mState; + @Mock protected ViewTreeObserver mViewTreeObserver; + @Mock protected DragLayer mDragLayer; + @Mock protected LauncherRootView mRootView; + @Mock protected SystemUiController mSystemUiController; + @Mock protected GestureState mGestureState; + @Mock protected MSDLPlayerWrapper mMSDLPlayerWrapper; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Before + public void setUpAnimationTargets() { + Bundle extras = new Bundle(); + extras.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, true); + extras.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, new SplitBounds( + /* leftTopBounds = */ new Rect(), + /* rightBottomBounds = */ new Rect(), + /* leftTopTaskId = */ -1, + /* rightBottomTaskId = */ -1, + /* snapPosition = */ SNAP_TO_2_50_50)); + mRecentsAnimationTargets = new RecentsAnimationTargets( + new RemoteAnimationTarget[] {mRemoteAnimationTarget}, + new RemoteAnimationTarget[] {mRemoteAnimationTarget}, + new RemoteAnimationTarget[] {mRemoteAnimationTarget}, + /* homeContentInsets= */ new Rect(), + /* minimizedHomeBounds= */ null, + extras); + } + + @Before + public void setUpRunningTaskInfo() { + mRunningTaskInfo.baseIntent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + + @Before + public void setUpGestureState() { + when(mGestureState.getRunningTask()).thenReturn(mCachedTaskInfo); + when(mGestureState.getLastAppearedTaskIds()).thenReturn(new int[0]); + when(mGestureState.getLastStartedTaskIds()).thenReturn(new int[1]); + when(mGestureState.getHomeIntent()).thenReturn(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + doReturn(mActivityInterface).when(mGestureState).getContainerInterface(); + } + + @Before + public void setUpRecentsView() { + RECENTS_VIEW recentsView = getRecentsView(); + when(recentsView.getViewTreeObserver()).thenReturn(mViewTreeObserver); + doAnswer(answer -> { + runOnMainSync(() -> answer.getArgument(0).run()); + return this; + }).when(recentsView).runOnPageScrollsInitialized(any()); + } + + @Before + public void setUpRecentsContainer() { + mTaskAnimationManager = new TaskAnimationManager(mContext, + RecentsAnimationDeviceState.INSTANCE.get(mContext), DEFAULT_DISPLAY); + RecentsViewContainer recentsContainer = getRecentsContainer(); + RECENTS_VIEW recentsView = getRecentsView(); + + when(recentsContainer.getDeviceProfile()).thenReturn(new DeviceProfile()); + when(recentsContainer.getOverviewPanel()).thenReturn(recentsView); + when(recentsContainer.getDragLayer()).thenReturn(mDragLayer); + when(recentsContainer.getRootView()).thenReturn(mRootView); + when(recentsContainer.getSystemUiController()).thenReturn(mSystemUiController); + when(mActivityInterface.createActivityInitListener(any())) + .thenReturn(mContextInitListener); + doReturn(recentsContainer).when(mActivityInterface).getCreatedContainer(); + doAnswer(answer -> { + answer.getArgument(0).run(); + return this; + }).when(recentsContainer).runOnBindToTouchInteractionService(any()); + } + + @Test + public void testInitWhenReady_registersActivityInitListener() { + String reasonString = "because i said so"; + + createSwipeHandler().initWhenReady(reasonString); + verify(mContextInitListener).register(eq(reasonString)); + } + + @Test + public void testOnRecentsAnimationCanceled_unregistersActivityInitListener() { + createSwipeHandler() + .onRecentsAnimationCanceled(new HashMap<>()); + + runOnMainSync(() -> verify(mContextInitListener) + .unregister(eq("AbsSwipeUpHandler.onRecentsAnimationCanceled"))); + } + + @Test + public void testOnConsumerAboutToBeSwitched_unregistersActivityInitListener() { + createSwipeHandler().onConsumerAboutToBeSwitched(); + + runOnMainSync(() -> verify(mContextInitListener) + .unregister("AbsSwipeUpHandler.invalidateHandler")); + } + + @Test + public void testOnConsumerAboutToBeSwitched_midQuickSwitch_unregistersActivityInitListener() { + createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.NEW_TASK) + .onConsumerAboutToBeSwitched(); + + runOnMainSync(() -> verify(mContextInitListener) + .unregister(eq("AbsSwipeUpHandler.cancelCurrentAnimation"))); + } + + @Test + public void testStartNewTask_finishesRecentsAnimationController() { + SWIPE_HANDLER absSwipeUpHandler = createSwipeHandler(); + + onRecentsAnimationStart(absSwipeUpHandler); + + runOnMainSync(() -> { + absSwipeUpHandler.startNewTask(unused -> {}); + verifyRecentsAnimationFinishedAndCallCallback(); + }); + } + + @Test + public void testHomeGesture_finishesRecentsAnimationController() { + createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME); + + runOnMainSync(() -> { + verify(mRecentsAnimationController).detachNavigationBarFromApp(true); + verifyRecentsAnimationFinishedAndCallCallback(); + }); + } + + @EnableFlags({Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED}) + @Test + public void testHomeGesture_handsOffAnimation() { + createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME); + + runOnMainSync(() -> { + verify(mRecentsAnimationController).handOffAnimation(any(), any()); + verifyRecentsAnimationFinishedAndCallCallback(); + }); + } + + @DisableFlags({Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED}) + @Test + public void testHomeGesture_doesNotHandOffAnimation_withFlagsDisabled() { + createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME); + + runOnMainSync(() -> { + verify(mRecentsAnimationController, never()).handOffAnimation(any(), any()); + verifyRecentsAnimationFinishedAndCallCallback(); + }); + } + + @Test + public void testHomeGesture_invalidatesHandlerAfterParallelAnim() { + ValueAnimator parallelAnim = new ValueAnimator(); + parallelAnim.setRepeatCount(ValueAnimator.INFINITE); + when(mActivityInterface.getParallelAnimationToGestureEndTarget(any(), anyLong(), any())) + .thenReturn(parallelAnim); + SWIPE_HANDLER handler = createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME); + runOnMainSync(() -> { + parallelAnim.start(); + verifyRecentsAnimationFinishedAndCallCallback(); + assertFalse(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)); + parallelAnim.end(); + assertTrue(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)); + }); + } + + @Test + public void testHomeGesture_invalidatesHandlerIfNoParallelAnim() { + when(mActivityInterface.getParallelAnimationToGestureEndTarget(any(), anyLong(), any())) + .thenReturn(null); + SWIPE_HANDLER handler = createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME); + runOnMainSync(() -> { + verifyRecentsAnimationFinishedAndCallCallback(); + assertTrue(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)); + }); + } + + @Test + @EnableFlags(com.android.launcher3.Flags.FLAG_MSDL_FEEDBACK) + public void onMotionPauseDetected_playsSwipeThresholdToken() { + SWIPE_HANDLER handler = createSwipeHandler(); + MotionPauseDetector.OnMotionPauseListener listener = handler.getMotionPauseListener(); + listener.onMotionPauseDetected(); + + verify(mMSDLPlayerWrapper, times(1)).playToken(eq(MSDLToken.SWIPE_THRESHOLD_INDICATOR)); + verifyNoMoreInteractions(mMSDLPlayerWrapper); + } + + /** + * Verifies that RecentsAnimationController#finish() is called, and captures and runs any + * callback that was passed to it. This ensures that STATE_CURRENT_TASK_FINISHED is correctly + * set for example. + */ + private void verifyRecentsAnimationFinishedAndCallCallback() { + ArgumentCaptor finishCallback = ArgumentCaptor.forClass(Runnable.class); + // Check if the 2 parameter method is called. + verify(mRecentsAnimationController, atLeast(0)).finish( + anyBoolean(), finishCallback.capture()); + if (finishCallback.getAllValues().isEmpty()) { + // Check if the 3 parameter method is called. + verify(mRecentsAnimationController).finish( + anyBoolean(), finishCallback.capture(), anyBoolean()); + } + if (finishCallback.getValue() != null) { + finishCallback.getValue().run(); + } + } + + private SWIPE_HANDLER createSwipeUpHandlerForGesture(GestureState.GestureEndTarget endTarget) { + boolean isQuickSwitch = endTarget == GestureState.GestureEndTarget.NEW_TASK; + + doReturn(mState).when(mActivityInterface).stateFromGestureEndTarget(any()); + + SWIPE_HANDLER swipeHandler = createSwipeHandler(SystemClock.uptimeMillis(), isQuickSwitch); + + swipeHandler.onActivityInit(/* alreadyOnHome= */ false); + swipeHandler.onGestureStarted(isQuickSwitch); + onRecentsAnimationStart(swipeHandler); + + when(mGestureState.getRunningTaskIds(anyBoolean())).thenReturn(new int[0]); + runOnMainSync(swipeHandler::switchToScreenshot); + + when(mGestureState.getEndTarget()).thenReturn(endTarget); + when(mGestureState.isRecentsAnimationRunning()).thenReturn(isQuickSwitch); + float xVelocityPxPerMs = isQuickSwitch ? 100 : 0; + float yVelocityPxPerMs = isQuickSwitch ? 0 : -100; + swipeHandler.onGestureEnded( + yVelocityPxPerMs, new PointF(xVelocityPxPerMs, yVelocityPxPerMs), isQuickSwitch); + swipeHandler.onCalculateEndTarget(); + runOnMainSync(swipeHandler::onSettledOnEndTarget); + + return swipeHandler; + } + + private void onRecentsAnimationStart(SWIPE_HANDLER absSwipeUpHandler) { + runOnMainSync(() -> absSwipeUpHandler.onRecentsAnimationStart( + mRecentsAnimationController, mRecentsAnimationTargets, /* transitionInfo= */null)); + } + + protected static void runOnMainSync(Runnable runnable) { + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } + + @NonNull + private SWIPE_HANDLER createSwipeHandler() { + return createSwipeHandler(SystemClock.uptimeMillis(), false); + } + + @NonNull + protected abstract SWIPE_HANDLER createSwipeHandler( + long touchTimeMs, boolean continuingLastGesture); + + @NonNull + protected abstract RecentsViewContainer getRecentsContainer(); + + @NonNull + protected abstract RECENTS_VIEW getRecentsView(); +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt index 73b35e8a5c3..a1bd107caff 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt @@ -18,32 +18,59 @@ package com.android.quickstep import android.app.PendingIntent import android.content.IIntentSender +import android.provider.Settings +import android.provider.Settings.Secure.USER_SETUP_COMPLETE import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.dagger.LauncherAppComponent +import com.android.launcher3.dagger.LauncherAppSingleton +import com.android.launcher3.util.AllModulesForTest import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR +import com.android.launcher3.util.SandboxApplication +import com.android.launcher3.util.SettingsCache +import com.android.launcher3.util.SettingsCacheSandbox import com.android.launcher3.util.TestUtil import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit.SECONDS +import org.junit.After +import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith private const val TIMEOUT = 5L +private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE) @RunWith(AndroidJUnit4::class) class AllAppsActionManagerTest { private val callbackSemaphore = Semaphore(0) private val bgExecutor = UI_HELPER_EXECUTOR - private val allAppsActionManager = - AllAppsActionManager( - InstrumentationRegistry.getInstrumentation().targetContext, - bgExecutor, - ) { - callbackSemaphore.release() - PendingIntent(IIntentSender.Default()) + @get:Rule val context = SandboxApplication() + + private val settingsCacheSandbox = + SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 } + + private val allAppsActionManager by + lazy(LazyThreadSafetyMode.NONE) { + AllAppsActionManager(context, bgExecutor) { + callbackSemaphore.release() + PendingIntent(IIntentSender.Default()) + } } + @Before + fun initDaggerComponent() { + context.initDaggerComponent( + DaggerAllAppsActionManagerTestComponent.builder() + .bindSettingsCache(settingsCacheSandbox.cache) + ) + } + + @After fun destroyManager() = allAppsActionManager.onDestroy() + @Test fun taskbarPresent_actionRegistered() { allAppsActionManager.isTaskbarPresent = true @@ -88,4 +115,50 @@ class AllAppsActionManagerTest { assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() assertThat(allAppsActionManager.isActionRegistered).isTrue() } + + @Test + fun taskbarPresent_userSetupIncomplete_actionUnregistered() { + settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0 + allAppsActionManager.isTaskbarPresent = true + assertThat(allAppsActionManager.isActionRegistered).isFalse() + } + + @Test + fun taskbarPresent_setupUiVisible_actionUnregistered() { + allAppsActionManager.isSetupUiVisible = true + allAppsActionManager.isTaskbarPresent = true + assertThat(allAppsActionManager.isActionRegistered).isFalse() + } + + @Test + fun taskbarPresent_userSetupCompleted_actionRegistered() { + settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0 + allAppsActionManager.isTaskbarPresent = true + + settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1 + assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() + assertThat(allAppsActionManager.isActionRegistered).isTrue() + } + + @Test + fun taskbarPresent_setupUiDismissed_actionRegistered() { + allAppsActionManager.isSetupUiVisible = true + allAppsActionManager.isTaskbarPresent = true + + allAppsActionManager.isSetupUiVisible = false + assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() + assertThat(allAppsActionManager.isActionRegistered).isTrue() + } +} + +@LauncherAppSingleton +@Component(modules = [AllModulesForTest::class]) +interface AllAppsActionManagerTestComponent : LauncherAppComponent { + + @Component.Builder + interface Builder : LauncherAppComponent.Builder { + @BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder + + override fun build(): AllAppsActionManagerTestComponent + } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/DesktopFullscreenDrawParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/DesktopFullscreenDrawParamsTest.kt new file mode 100644 index 00000000000..e62455fd023 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/DesktopFullscreenDrawParamsTest.kt @@ -0,0 +1,48 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +/** Test for [DesktopFullscreenDrawParams] class. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class DesktopFullscreenDrawParamsTest() { + private val params = + DesktopFullscreenDrawParams(mock(), cornerRadiusProvider = { CORNER_RADIUS }) + + @Test + fun setMiddleProgress_invariantCornerRadiusForDesktop() { + params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(CORNER_RADIUS) + + params.setProgress(fullscreenProgress = 0.67f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(CORNER_RADIUS) + + params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(CORNER_RADIUS) + } + + companion object { + const val CORNER_RADIUS = 32f + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt new file mode 100644 index 00000000000..fa7907fa4b9 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.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.quickstep + +import android.content.Context +import android.view.Display +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import java.io.PrintWriter +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DisplayModelTest { + private val context: Context = ApplicationProvider.getApplicationContext() + + class TestableResource : DisplayModel.DisplayResource() { + var isCleanupCalled = false + + override fun cleanup() { + isCleanupCalled = true + } + + override fun dump(prefix: String, writer: PrintWriter) { + // No-Op + } + } + + private val testableDisplayModel = + object : DisplayModel(context) { + override fun createDisplayResource(display: Display): TestableResource { + return TestableResource() + } + } + + @Test + fun testCreate() { + testableDisplayModel.storeDisplayResource(Display.DEFAULT_DISPLAY) + val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY) + assertNotNull(resource) + } + + @Test + fun testCleanAndDelete() { + testableDisplayModel.storeDisplayResource(Display.DEFAULT_DISPLAY) + val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)!! + assertNotNull(resource) + testableDisplayModel.deleteDisplayResource(Display.DEFAULT_DISPLAY) + assert(resource.isCleanupCalled) + assertNull(testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java new file mode 100644 index 00000000000..3489519e74a --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java @@ -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 androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.util.LauncherMultivalentJUnit; +import com.android.quickstep.fallback.FallbackRecentsView; +import com.android.quickstep.fallback.RecentsState; + +import org.junit.runner.RunWith; +import org.mockito.Mock; + +@SmallTest +@RunWith(LauncherMultivalentJUnit.class) +public class FallbackSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase< + RecentsState, + RecentsActivity, + FallbackRecentsView, + FallbackSwipeHandler, + FallbackActivityInterface> { + + @Mock private RecentsActivity mRecentsActivity; + @Mock private FallbackRecentsView mRecentsView; + + + @Override + protected FallbackSwipeHandler createSwipeHandler( + long touchTimeMs, boolean continuingLastGesture) { + return new FallbackSwipeHandler( + mContext, + mTaskAnimationManager, + mGestureState, + touchTimeMs, + continuingLastGesture, + mInputConsumerController, + mMSDLPlayerWrapper); + } + + @NonNull + @Override + protected RecentsActivity getRecentsContainer() { + return mRecentsActivity; + } + + @NonNull + @Override + protected FallbackRecentsView getRecentsView() { + return mRecentsView; + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt index 5d62a4c749b..99b81e05eb0 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt @@ -20,21 +20,17 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.FakeInvariantDeviceProfileTest import com.android.quickstep.util.TaskCornerRadius -import com.android.quickstep.views.TaskView.FullscreenDrawParams import com.android.systemui.shared.system.QuickStepContract import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.mock -import org.mockito.Mockito.spy +import org.mockito.kotlin.mock -/** Test for FullscreenDrawParams class. */ +/** Test for [FullscreenDrawParams] class. */ @SmallTest @RunWith(AndroidJUnit4::class) class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() { - private lateinit var params: FullscreenDrawParams @Before @@ -46,115 +42,108 @@ class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() { fun setStartProgress_correctCornerRadiusForTablet() { initializeVarsForTablet() - params.setProgress( - /* fullscreenProgress= */ 0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f - ) + params.setProgress(fullscreenProgress = 0f, parentScale = 1.0f, taskViewScale = 1.0f) val expectedRadius = TaskCornerRadius.get(context) - assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) } @Test fun setFullProgress_correctCornerRadiusForTablet() { initializeVarsForTablet() - params.setProgress( - /* fullscreenProgress= */ 1.0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f - ) + params.setProgress(fullscreenProgress = 1.0f, parentScale = 1f, taskViewScale = 1f) val expectedRadius = QuickStepContract.getWindowCornerRadius(context) - assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) } @Test fun setStartProgress_correctCornerRadiusForPhone() { initializeVarsForPhone() - params.setProgress( - /* fullscreenProgress= */ 0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f - ) + params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) val expectedRadius = TaskCornerRadius.get(context) - assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) } @Test fun setFullProgress_correctCornerRadiusForPhone() { initializeVarsForPhone() - params.setProgress( - /* fullscreenProgress= */ 1.0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f - ) + params.setProgress(fullscreenProgress = 1.0f, parentScale = 1f, taskViewScale = 1f) val expectedRadius = QuickStepContract.getWindowCornerRadius(context) - assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) } @Test fun setStartProgress_correctCornerRadiusForMultiDisplay() { - val display1Context = context - val display2Context = mock(Context::class.java) - val spyParams = spy(params) - - val display1TaskRadius = TaskCornerRadius.get(display1Context) - val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context) - val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different. - val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different. - doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context) - doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context) - - spyParams.updateCornerRadius(display1Context) - spyParams.setProgress( - /* fullscreenProgress= */ 0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f - ) - assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1TaskRadius) - - spyParams.updateCornerRadius(display2Context) - spyParams.setProgress( - /* fullscreenProgress= */ 0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f - ) - assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2TaskRadius) + val display1Context = mock() + val display2Context = mock() + val display1TaskRadius = TASK_CORNER_RADIUS + 1 + val display2TaskRadius = TASK_CORNER_RADIUS + 2 + + val params = + FullscreenDrawParams( + context, + taskCornerRadiusProvider = { context -> + when (context) { + display1Context -> display1TaskRadius + display2Context -> display2TaskRadius + else -> TASK_CORNER_RADIUS + } + }, + windowCornerRadiusProvider = { 0f }, + ) + + params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(TASK_CORNER_RADIUS) + + params.updateCornerRadius(display1Context) + params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(display1TaskRadius) + + params.updateCornerRadius(display2Context) + params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(display2TaskRadius) } @Test fun setFullProgress_correctCornerRadiusForMultiDisplay() { - val display1Context = context - val display2Context = mock(Context::class.java) - val spyParams = spy(params) - - val display1TaskRadius = TaskCornerRadius.get(display1Context) - val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context) - val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different. - val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different. - doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context) - doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context) - - spyParams.updateCornerRadius(display1Context) - spyParams.setProgress( - /* fullscreenProgress= */ 1.0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f - ) - assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1WindowRadius) - - spyParams.updateCornerRadius(display2Context) - spyParams.setProgress( - /* fullscreenProgress= */ 1.0f, - /* parentScale= */ 1.0f, - /* taskViewScale= */ 1.0f, - ) - assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2WindowRadius) + val display1Context = mock() + val display2Context = mock() + val display1WindowRadius = WINDOW_CORNER_RADIUS + 1 + val display2WindowRadius = WINDOW_CORNER_RADIUS + 2 + + val params = + FullscreenDrawParams( + context, + taskCornerRadiusProvider = { 0f }, + windowCornerRadiusProvider = { context -> + when (context) { + display1Context -> display1WindowRadius + display2Context -> display2WindowRadius + else -> WINDOW_CORNER_RADIUS + } + }, + ) + + params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(WINDOW_CORNER_RADIUS) + + params.updateCornerRadius(display1Context) + params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(display1WindowRadius) + + params.updateCornerRadius(display2Context) + params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f) + assertThat(params.currentCornerRadius).isEqualTo(display2WindowRadius) + } + + companion object { + const val TASK_CORNER_RADIUS = 56f + const val WINDOW_CORNER_RADIUS = 32f } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt new file mode 100644 index 00000000000..24f96964372 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt @@ -0,0 +1,139 @@ +package com.android.quickstep + +/* + * 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. + */ + +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.Flags +import com.android.launcher3.LauncherSettings.Favorites +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED) +class LauncherRestoreEventLoggerImplTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + + private val mLauncherModelHelper = LauncherModelHelper() + private val mSandboxContext: SandboxModelContext = mLauncherModelHelper.sandboxContext + private lateinit var loggerUnderTest: LauncherRestoreEventLoggerImpl + + @Before + fun setup() { + loggerUnderTest = LauncherRestoreEventLoggerImpl(mSandboxContext) + } + + @After + fun teardown() { + loggerUnderTest.restoreEventLogger.clearData() + mLauncherModelHelper.destroy() + } + + @Test + fun `logLauncherItemsRestoreFailed logs multiple items as failing restore`() { + // Given + val expectedDataType = "application" + val expectedError = "test_failure" + // When + loggerUnderTest.logLauncherItemsRestoreFailed( + dataType = expectedDataType, + count = 5, + error = expectedError + ) + // Then + val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first() + assertThat(actualResult.dataType).isEqualTo(expectedDataType) + assertThat(actualResult.successCount).isEqualTo(0) + assertThat(actualResult.failCount).isEqualTo(5) + assertThat(actualResult.errors.keys).containsExactly(expectedError) + } + + @Test + fun `logLauncherItemsRestored logs multiple items as restored`() { + // Given + val expectedDataType = "application" + // When + loggerUnderTest.logLauncherItemsRestored(dataType = expectedDataType, count = 5) + // Then + val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first() + assertThat(actualResult.dataType).isEqualTo(expectedDataType) + assertThat(actualResult.successCount).isEqualTo(5) + assertThat(actualResult.failCount).isEqualTo(0) + assertThat(actualResult.errors.keys).isEmpty() + } + + @Test + fun `logSingleFavoritesItemRestored logs a single Favorites Item as restored`() { + // Given + val expectedDataType = "widget" + // When + loggerUnderTest.logSingleFavoritesItemRestored(favoritesId = Favorites.ITEM_TYPE_APPWIDGET) + // Then + val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first() + assertThat(actualResult.dataType).isEqualTo(expectedDataType) + assertThat(actualResult.successCount).isEqualTo(1) + assertThat(actualResult.failCount).isEqualTo(0) + assertThat(actualResult.errors.keys).isEmpty() + } + + @Test + fun `logSingleFavoritesItemRestoreFailed logs a single Favorites Item as failing restore`() { + // Given + val expectedDataType = "widget" + val expectedError = "test_failure" + // When + loggerUnderTest.logSingleFavoritesItemRestoreFailed( + favoritesId = Favorites.ITEM_TYPE_APPWIDGET, + error = expectedError + ) + // Then + val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first() + assertThat(actualResult.dataType).isEqualTo(expectedDataType) + assertThat(actualResult.successCount).isEqualTo(0) + assertThat(actualResult.failCount).isEqualTo(1) + assertThat(actualResult.errors.keys).containsExactly(expectedError) + } + + @Test + fun `logFavoritesItemsRestoreFailed logs multiple Favorites Items as failing restore`() { + // Given + val expectedDataType = "deep_shortcut" + val expectedError = "test_failure" + // When + loggerUnderTest.logFavoritesItemsRestoreFailed( + favoritesId = Favorites.ITEM_TYPE_DEEP_SHORTCUT, + count = 5, + error = expectedError + ) + // Then + val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first() + assertThat(actualResult.dataType).isEqualTo(expectedDataType) + assertThat(actualResult.successCount).isEqualTo(0) + assertThat(actualResult.failCount).isEqualTo(5) + assertThat(actualResult.errors.keys).containsExactly(expectedError) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt new file mode 100644 index 00000000000..5661dcf6992 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt @@ -0,0 +1,145 @@ +/* + * 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. + */ + +package com.android.quickstep + +import android.graphics.PointF +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManagerGlobal +import android.view.Display +import android.view.Display.DEFAULT_DISPLAY +import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS +import android.view.DisplayInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.R +import com.android.launcher3.dagger.LauncherAppComponent +import com.android.launcher3.dagger.LauncherAppModule +import com.android.launcher3.dagger.LauncherAppSingleton +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.MSDLPlayerWrapper +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.shared.system.InputConsumerController +import dagger.BindsInstance +import dagger.Component +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.eq +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class LauncherSwipeHandlerV2Test { + + @Mock private lateinit var taskAnimationManager: TaskAnimationManager + + private lateinit var gestureState: GestureState + @Mock private lateinit var inputConsumerController: InputConsumerController + + @Mock private lateinit var systemUiProxy: SystemUiProxy + + @Mock private lateinit var msdlPlayerWrapper: MSDLPlayerWrapper + + private lateinit var underTest: LauncherSwipeHandlerV2 + + @get:Rule val mockitoRule = MockitoJUnit.rule() + + private val launcherModelHelper = LauncherModelHelper() + private val sandboxContext = launcherModelHelper.sandboxContext + + private val flingSpeed = + -(sandboxContext.resources.getDimension(R.dimen.quickstep_fling_threshold_speed) + 1) + + private val displayManager: DisplayManager = + sandboxContext.spyService(DisplayManager::class.java) + + @Before + fun setup() { + val display = + Display( + DisplayManagerGlobal.getInstance(), + DEFAULT_DISPLAY, + DisplayInfo(), + DEFAULT_DISPLAY_ADJUSTMENTS, + ) + whenever(displayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(display) + whenever(displayManager.displays).thenReturn(arrayOf(display)) + + sandboxContext.initDaggerComponent( + DaggerTestComponent.builder() + .bindSystemUiProxy(systemUiProxy) + .bindRotationHelper(mock(RotationTouchHelper::class.java)) + .bindRecentsState(mock(RecentsAnimationDeviceState::class.java)) + ) + gestureState = + spy( + GestureState( + OverviewComponentObserver.INSTANCE.get(sandboxContext), + DEFAULT_DISPLAY, + 0, + ) + ) + + underTest = + LauncherSwipeHandlerV2( + sandboxContext, + taskAnimationManager, + gestureState, + 0, + false, + inputConsumerController, + msdlPlayerWrapper, + ) + underTest.onGestureStarted(/* isLikelyToStartNewTask= */ false) + } + + @Test + fun goHomeFromAppByTrackpad_updateEduStats() { + gestureState.setTrackpadGestureType(GestureState.TrackpadGestureType.THREE_FINGER) + underTest.onGestureEnded(flingSpeed, PointF(), /* horizontalTouchSlopPassed= */ false) + verify(systemUiProxy) + .updateContextualEduStats(/* isTrackpadGesture= */ eq(true), eq(GestureType.HOME)) + } + + @Test + fun goHomeFromAppByTouch_updateEduStats() { + underTest.onGestureEnded(flingSpeed, PointF(), /* horizontalTouchSlopPassed= */ false) + verify(systemUiProxy) + .updateContextualEduStats(/* isTrackpadGesture= */ eq(false), eq(GestureType.HOME)) + } +} + +@LauncherAppSingleton +@Component(modules = [LauncherAppModule::class]) +interface TestComponent : LauncherAppComponent { + @Component.Builder + interface Builder : LauncherAppComponent.Builder { + @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder + + @BindsInstance fun bindRotationHelper(helper: RotationTouchHelper): Builder + + @BindsInstance fun bindRecentsState(state: RecentsAnimationDeviceState): Builder + + override fun build(): TestComponent + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java new file mode 100644 index 00000000000..66c4ab5ce3b --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java @@ -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.quickstep; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.Hotseat; +import com.android.launcher3.LauncherState; +import com.android.launcher3.Workspace; +import com.android.launcher3.WorkspaceStateTransitionAnimation; +import com.android.launcher3.statemanager.StateManager; +import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; +import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.util.LauncherMultivalentJUnit; +import com.android.quickstep.views.RecentsView; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +@SmallTest +@RunWith(LauncherMultivalentJUnit.class) +@Ignore +public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase< + LauncherState, + QuickstepLauncher, + RecentsView, + LauncherSwipeHandlerV2, + LauncherActivityInterface> { + + @Mock private QuickstepLauncher mQuickstepLauncher; + @Mock private RecentsView mRecentsView; + @Mock private Workspace mWorkspace; + @Mock private Hotseat mHotseat; + @Mock private WorkspaceStateTransitionAnimation mTransitionAnimation; + + @Before + public void setUpQuickStepLauncher() { + when(mQuickstepLauncher.createAtomicAnimationFactory()) + .thenReturn(new AtomicAnimationFactory<>(0)); + when(mQuickstepLauncher.getHotseat()).thenReturn(mHotseat); + doReturn(mWorkspace).when(mQuickstepLauncher).getWorkspace(); + doReturn(new StateManager(mQuickstepLauncher, LauncherState.NORMAL)) + .when(mQuickstepLauncher).getStateManager(); + + } + + @Before + public void setUpWorkspace() { + when(mWorkspace.getStateTransitionAnimation()).thenReturn(mTransitionAnimation); + } + + @NonNull + @Override + protected LauncherSwipeHandlerV2 createSwipeHandler( + long touchTimeMs, boolean continuingLastGesture) { + return new LauncherSwipeHandlerV2( + mContext, + mTaskAnimationManager, + mGestureState, + touchTimeMs, + continuingLastGesture, + mInputConsumerController, + mMSDLPlayerWrapper); + } + + @NonNull + @Override + protected QuickstepLauncher getRecentsContainer() { + return mQuickstepLauncher; + } + + @NonNull + @Override + protected RecentsView getRecentsView() { + return mRecentsView; + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/MultiStateCallbackTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/MultiStateCallbackTest.java new file mode 100644 index 00000000000..0ff142a47b7 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/MultiStateCallbackTest.java @@ -0,0 +1,271 @@ +/* + * 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.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.launcher3.util.LauncherMultivalentJUnit; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +@SmallTest +@RunWith(LauncherMultivalentJUnit.class) +public class MultiStateCallbackTest { + + private int mFlagCount = 0; + private int getNextStateFlag() { + int index = 1 << mFlagCount; + mFlagCount++; + return index; + } + + private final MultiStateCallback mMultiStateCallback = new MultiStateCallback(new String[0]); + private final Runnable mCallback = spy(new Runnable() { + @Override + public void run() {} + }); + private final Consumer mListener = spy(new Consumer() { + @Override + public void accept(Boolean isOn) {} + }); + + @Test + public void testSetState_trackedProperly() { + int watchedAnime = getNextStateFlag(); + + assertThat(mMultiStateCallback.getState()).isEqualTo(0); + assertThat(mMultiStateCallback.hasStates(watchedAnime)).isFalse(); + + mMultiStateCallback.setState(watchedAnime); + + assertThat(mMultiStateCallback.getState()).isEqualTo(watchedAnime); + assertThat(mMultiStateCallback.hasStates(watchedAnime)).isTrue(); + } + + @Test + public void testSetState_withMultipleStates_trackedProperly() { + int watchedAnime = getNextStateFlag(); + int sharedMemes = getNextStateFlag(); + + mMultiStateCallback.setState(watchedAnime); + mMultiStateCallback.setState(sharedMemes); + + assertThat(mMultiStateCallback.getState()).isEqualTo(watchedAnime | sharedMemes); + assertThat(mMultiStateCallback.hasStates(watchedAnime)).isTrue(); + assertThat(mMultiStateCallback.hasStates(sharedMemes)).isTrue(); + assertThat(mMultiStateCallback.hasStates(watchedAnime | sharedMemes)).isTrue(); + } + + @Test + public void testClearState_trackedProperly() { + int lovedAnime = getNextStateFlag(); + + mMultiStateCallback.setState(lovedAnime); + mMultiStateCallback.clearState(lovedAnime); + + assertThat(mMultiStateCallback.getState()).isEqualTo(0); + assertThat(mMultiStateCallback.hasStates(lovedAnime)).isFalse(); + } + + @Test + public void testClearState_withMultipleState_trackedProperly() { + int lovedAnime = getNextStateFlag(); + int talkedAboutAnime = getNextStateFlag(); + + mMultiStateCallback.setState(lovedAnime); + mMultiStateCallback.setState(talkedAboutAnime); + mMultiStateCallback.clearState(talkedAboutAnime); + + assertThat(mMultiStateCallback.getState()).isEqualTo(lovedAnime); + assertThat(mMultiStateCallback.hasStates(lovedAnime)).isTrue(); + assertThat(mMultiStateCallback.hasStates(talkedAboutAnime)).isFalse(); + assertThat(mMultiStateCallback.hasStates(lovedAnime | talkedAboutAnime)).isFalse(); + } + + @Test + public void testCallbackDoesNotRun_withoutState() { + int watchedOnePiece = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(watchedOnePiece, mCallback); + + verify(mCallback, never()).run(); + } + + @Test + public void testCallbackDoesNotRun_whenNotTracked() { + int watchedJujutsuKaisen = getNextStateFlag(); + + mMultiStateCallback.setState(watchedJujutsuKaisen); + + verify(mCallback, never()).run(); + } + + @Test + public void testCallbackRuns_afterTrackedAndStateSet() { + int watchedHunterXHunter = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(watchedHunterXHunter, mCallback); + mMultiStateCallback.setState(watchedHunterXHunter); + + verify(mCallback, times(1)).run(); + } + + @Test + public void testCallbackRuns_onUiThread() { + int watchedHunterXHunter = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(watchedHunterXHunter, mCallback); + mMultiStateCallback.setStateOnUiThread(watchedHunterXHunter); + + runOnMainSync(() -> verify(mCallback, times(1)).run()); + } + + @Test + public void testCallbackRuns_agnosticallyToCallOrder() { + int watchedFullMetalAlchemist = getNextStateFlag(); + + mMultiStateCallback.setState(watchedFullMetalAlchemist); + mMultiStateCallback.runOnceAtState(watchedFullMetalAlchemist, mCallback); + + verify(mCallback, times(1)).run(); + } + + @Test + public void testCallbackRuns_onlyOnceAfterStateSet() { + int watchedBleach = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(watchedBleach, mCallback); + mMultiStateCallback.setState(watchedBleach); + mMultiStateCallback.setState(watchedBleach); + + verify(mCallback, times(1)).run(); + } + + @Test + public void testCallbackRuns_onlyOnceAfterClearState() { + int rememberedGreatShow = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(rememberedGreatShow, mCallback); + mMultiStateCallback.setState(rememberedGreatShow); + mMultiStateCallback.clearState(rememberedGreatShow); + mMultiStateCallback.setState(rememberedGreatShow); + + verify(mCallback, times(1)).run(); + } + + @Test + public void testCallbackDoesNotRun_withoutFullStateSet() { + int watchedMobPsycho = getNextStateFlag(); + int watchedVinlandSaga = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(watchedMobPsycho | watchedVinlandSaga, mCallback); + mMultiStateCallback.setState(watchedMobPsycho); + + verify(mCallback, times(0)).run(); + } + + @Test + public void testCallbackRuns_withFullStateSet_agnosticallyToCallOrder() { + int watchedReZero = getNextStateFlag(); + int watchedJojosBizareAdventure = getNextStateFlag(); + + mMultiStateCallback.setState(watchedJojosBizareAdventure); + mMultiStateCallback.runOnceAtState(watchedReZero | watchedJojosBizareAdventure, mCallback); + mMultiStateCallback.setState(watchedReZero); + + verify(mCallback, times(1)).run(); + } + + @Test + public void testCallbackRuns_withFullStateSet_asIntegerMask() { + int watchedPokemon = getNextStateFlag(); + int watchedDigimon = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(watchedPokemon | watchedDigimon, mCallback); + mMultiStateCallback.setState(watchedPokemon | watchedDigimon); + + verify(mCallback, times(1)).run(); + } + + @Test + public void testCallbackDoesNotRun_afterClearState() { + int watchedMonster = getNextStateFlag(); + int watchedPingPong = getNextStateFlag(); + + mMultiStateCallback.runOnceAtState(watchedMonster | watchedPingPong, mCallback); + mMultiStateCallback.setState(watchedMonster); + mMultiStateCallback.clearState(watchedMonster); + mMultiStateCallback.setState(watchedPingPong); + + verify(mCallback, times(0)).run(); + } + + @Test + public void testlistenerRuns_multipleTimes() { + int watchedSteinsGate = getNextStateFlag(); + + mMultiStateCallback.addChangeListener(watchedSteinsGate, mListener); + mMultiStateCallback.setState(watchedSteinsGate); + + // Called exactly one + verify(mListener, times(1)).accept(anyBoolean()); + // Called exactly once with isOn = true + verify(mListener, times(1)).accept(eq(true)); + // Never called with isOn = false + verify(mListener, times(0)).accept(eq(false)); + + mMultiStateCallback.clearState(watchedSteinsGate); + + // Called exactly twice + verify(mListener, times(2)).accept(anyBoolean()); + // Called exactly once with isOn = true + verify(mListener, times(1)).accept(eq(true)); + // Called exactly once with isOn = false + verify(mListener, times(1)).accept(eq(false)); + } + + @Test + public void testlistenerDoesNotRun_forUnchangedState() { + int watchedSteinsGate = getNextStateFlag(); + + mMultiStateCallback.addChangeListener(watchedSteinsGate, mListener); + mMultiStateCallback.setState(watchedSteinsGate); + mMultiStateCallback.setState(watchedSteinsGate); + + // State remained unchanged + verify(mListener, times(1)).accept(anyBoolean()); + // Called exactly once with isOn = true + verify(mListener, times(1)).accept(eq(true)); + } + + private static void runOnMainSync(Runnable runnable) { + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt new file mode 100644 index 00000000000..11e0ee88b60 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt @@ -0,0 +1,259 @@ +/* + * 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.platform.test.flag.junit.SetFlagsRule +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.filters.SmallTest +import com.android.launcher3.Flags +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.TestDispatcherProvider +import com.android.launcher3.util.rule.setFlags +import com.android.quickstep.OverviewCommandHelper.CommandInfo +import com.android.quickstep.OverviewCommandHelper.CommandInfo.CommandStatus +import com.android.quickstep.OverviewCommandHelper.CommandType +import com.android.quickstep.fallback.window.RecentsDisplayModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.spy +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(LauncherMultivalentJUnit::class) +@OptIn(ExperimentalCoroutinesApi::class) +class OverviewCommandHelperTest { + @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() + + private lateinit var sut: OverviewCommandHelper + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + + private var pendingCallbacksWithDelays = mutableListOf() + + private val recentsDisplayModel: RecentsDisplayModel = mock() + private val defaultDisplayResource: RecentsDisplayModel.RecentsDisplayResource = mock() + private val secondaryDisplayResource: RecentsDisplayModel.RecentsDisplayResource = mock() + private val executeCommandDisplayIds = mutableListOf() + + private fun setupDefaultDisplay() { + whenever(defaultDisplayResource.displayId).thenReturn(DEFAULT_DISPLAY) + whenever(recentsDisplayModel.activeDisplayResources) + .thenReturn(listOf(defaultDisplayResource)) + } + + private fun setupMultipleDisplays() { + whenever(defaultDisplayResource.displayId).thenReturn(DEFAULT_DISPLAY) + whenever(secondaryDisplayResource.displayId).thenReturn(1) + whenever(recentsDisplayModel.activeDisplayResources) + .thenReturn(listOf(defaultDisplayResource, secondaryDisplayResource)) + } + + @Suppress("UNCHECKED_CAST") + @Before + fun setup() { + setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT) + + setupDefaultDisplay() + + sut = + spy( + OverviewCommandHelper( + touchInteractionService = mock(), + overviewComponentObserver = mock(), + dispatcherProvider = TestDispatcherProvider(dispatcher), + recentsDisplayModel = recentsDisplayModel, + focusState = mock(), + taskbarManager = mock(), + ) + ) + + doAnswer { invocation -> + val pendingCallback = invocation.arguments[1] as () -> Unit + + val delayInMillis = pendingCallbacksWithDelays.removeFirstOrNull() + if (delayInMillis != null) { + runBlocking { + testScope.backgroundScope.launch { + delay(delayInMillis) + pendingCallback.invoke() + } + } + } + val commandInfo = invocation.arguments[0] as CommandInfo + executeCommandDisplayIds.add(commandInfo.displayId) + delayInMillis == null // if no callback to execute, returns success + } + .`when`(sut) + .executeCommand(any(), any()) + } + + private fun addCallbackDelay(delayInMillis: Long = 0) { + pendingCallbacksWithDelays.add(delayInMillis) + } + + @Test + fun whenFirstCommandIsAdded_executeCommandImmediately() = + testScope.runTest { + // Add command to queue + val commandInfo: CommandInfo = sut.addCommand(CommandType.HOME)!! + assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE) + runCurrent() + assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED) + } + + @Test + fun whenFirstCommandIsAdded_executeCommandImmediately_WithCallbackDelay() = + testScope.runTest { + addCallbackDelay(100) + + // Add command to queue + val commandType = CommandType.HOME + val commandInfo: CommandInfo = sut.addCommand(commandType)!! + assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE) + + runCurrent() + assertThat(commandInfo.status).isEqualTo(CommandStatus.PROCESSING) + + advanceTimeBy(200L) + assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED) + } + + @Test + fun whenFirstCommandIsPendingCallback_NextCommandWillWait() = + testScope.runTest { + // Add command to queue + addCallbackDelay(100) + val commandType1 = CommandType.HOME + val commandInfo1: CommandInfo = sut.addCommand(commandType1)!! + assertThat(commandInfo1.status).isEqualTo(CommandStatus.IDLE) + + addCallbackDelay(100) + val commandType2 = CommandType.SHOW + val commandInfo2: CommandInfo = sut.addCommand(commandType2)!! + assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE) + + runCurrent() + assertThat(commandInfo1.status).isEqualTo(CommandStatus.PROCESSING) + assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE) + + advanceTimeBy(101L) + assertThat(commandInfo1.status).isEqualTo(CommandStatus.COMPLETED) + assertThat(commandInfo2.status).isEqualTo(CommandStatus.PROCESSING) + + advanceTimeBy(101L) + assertThat(commandInfo2.status).isEqualTo(CommandStatus.COMPLETED) + } + + @Test + fun whenCommandTakesTooLong_TriggerTimeout_AndExecuteNextCommand() = + testScope.runTest { + // Add command to queue + addCallbackDelay(QUEUE_TIMEOUT) + val commandType1 = CommandType.HOME + val commandInfo1: CommandInfo = sut.addCommand(commandType1)!! + assertThat(commandInfo1.status).isEqualTo(CommandStatus.IDLE) + + addCallbackDelay(100) + val commandType2 = CommandType.SHOW + val commandInfo2: CommandInfo = sut.addCommand(commandType2)!! + assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE) + + runCurrent() + assertThat(commandInfo1.status).isEqualTo(CommandStatus.PROCESSING) + assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE) + + advanceTimeBy(QUEUE_TIMEOUT) + assertThat(commandInfo1.status).isEqualTo(CommandStatus.CANCELED) + assertThat(commandInfo2.status).isEqualTo(CommandStatus.PROCESSING) + + advanceTimeBy(101) + assertThat(commandInfo2.status).isEqualTo(CommandStatus.COMPLETED) + } + + @Test + fun whenAllDisplaysCommandIsAdded_singleCommandProcessedForDefaultDisplay() = + testScope.runTest { + executeCommandDisplayIds.clear() + // Add command to queue + val commandInfo: CommandInfo = sut.addCommandsForAllDisplays(CommandType.HOME)!! + assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE) + runCurrent() + assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED) + assertThat(executeCommandDisplayIds).containsExactly(DEFAULT_DISPLAY) + } + + @Test + fun whenAllDisplaysCommandIsAdded_multipleCommandsProcessedForMultipleDisplays() = + testScope.runTest { + setupMultipleDisplays() + executeCommandDisplayIds.clear() + // Add command to queue + val commandInfo: CommandInfo = sut.addCommandsForAllDisplays(CommandType.HOME)!! + assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE) + runCurrent() + assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED) + assertThat(executeCommandDisplayIds) + .containsExactly(DEFAULT_DISPLAY, EXTERNAL_DISPLAY_ID) + } + + @Test + fun whenAllExceptDisplayCommandIsAdded_otherDisplayProcessed() = + testScope.runTest { + setupMultipleDisplays() + executeCommandDisplayIds.clear() + // Add command to queue + val commandInfo: CommandInfo = + sut.addCommandsForDisplaysExcept(CommandType.HOME, DEFAULT_DISPLAY)!! + assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE) + runCurrent() + assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED) + assertThat(executeCommandDisplayIds).containsExactly(EXTERNAL_DISPLAY_ID) + } + + @Test + fun whenSingleDisplayCommandIsAdded_thatDisplayIsProcessed() = + testScope.runTest { + executeCommandDisplayIds.clear() + val displayId = 5 + // Add command to queue + val commandInfo: CommandInfo = sut.addCommand(CommandType.HOME, displayId)!! + assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE) + runCurrent() + assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED) + assertThat(executeCommandDisplayIds).containsExactly(displayId) + } + + private companion object { + const val QUEUE_TIMEOUT = 5001L + const val EXTERNAL_DISPLAY_ID = 1 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java new file mode 100644 index 00000000000..1e4315a0400 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java @@ -0,0 +1,340 @@ +/* + * 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 android.view.Display.DEFAULT_DISPLAY; + +import static com.android.launcher3.Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS; +import static com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND; + +import static com.google.common.truth.Truth.assertThat; + +import static junit.framework.TestCase.assertNull; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.app.KeyguardManager; +import android.app.TaskInfo; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.R; +import com.android.launcher3.statehandlers.DesktopVisibilityController; +import com.android.launcher3.util.DaggerSingletonTracker; +import com.android.launcher3.util.LooperExecutor; +import com.android.quickstep.util.DesktopTask; +import com.android.quickstep.util.GroupTask; +import com.android.quickstep.views.TaskViewType; +import com.android.systemui.shared.recents.model.Task; +import com.android.wm.shell.shared.GroupedTaskInfo; +import com.android.wm.shell.shared.split.SplitBounds; +import com.android.wm.shell.shared.split.SplitScreenConstants; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RecentTasksListTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private SystemUiProxy mSystemUiProxy; + @Mock + private TopTaskTracker mTopTaskTracker; + + // Class under test + private RecentTasksList mRecentTasksList; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class); + KeyguardManager mockKeyguardManager = mock(KeyguardManager.class); + + // Set desktop mode supported + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true); + when(mResources.getBoolean(R.bool.config_canInternalDisplayHostDesktops)) + .thenReturn(true); + + mRecentTasksList = new RecentTasksList(mContext, mockMainThreadExecutor, + mockKeyguardManager, mSystemUiProxy, mTopTaskTracker, + mock(DesktopVisibilityController.class), + mock(DaggerSingletonTracker.class)); + } + + @Test + public void onRecentTasksChanged_doesNotFetchTasks() throws Exception { + mRecentTasksList.onRecentTasksChanged(); + verify(mSystemUiProxy, times(0)) + .getRecentTasks(anyInt(), anyInt()); + } + + @Test + public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception { + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks( + new RecentTaskInfo(), new RecentTaskInfo(), new SplitBounds( + /* leftTopBounds = */ new Rect(), + /* rightBottomBounds = */ new Rect(), + /* leftTopTaskId = */ -1, + /* rightBottomTaskId = */ -1, + /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50)); + when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, + true); + + assertEquals(1, taskList.size()); + taskList.get(0).getTasks().forEach(t -> assertNull(t.taskDescription.getLabel())); + } + + @Test + public void loadTasksInBackground_GetRecentTasksException() throws Exception { + when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenThrow(new SystemUiProxy.GetRecentTasksException("task load failed")); + + RecentTasksList.TaskLoadResult taskList = mRecentTasksList.loadTasksInBackground( + Integer.MAX_VALUE, -1, false); + + assertThat(taskList.mRequestId).isEqualTo(-1); + assertThat(taskList).isEmpty(); + } + + @Test + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + public void loadTasksInBackground_freeformTask_multiDesksInMultiDisplays() throws Exception { + List tasksInDefaultDesk1 = Arrays.asList( + createRecentTaskInfo(/* taskId = */ 1, DEFAULT_DISPLAY), + createRecentTaskInfo(/* taskId = */ 4, DEFAULT_DISPLAY)); + List tasksInDefaultDesk2 = Arrays.asList( + createRecentTaskInfo(/* taskId = */ 2, DEFAULT_DISPLAY), + createRecentTaskInfo(/* taskId = */ 3, DEFAULT_DISPLAY)); + List tasksInExtend = Arrays.asList( + createRecentTaskInfo(/* taskId = */ 5, /* displayId = */ 1), + createRecentTaskInfo(/* taskId = */ 6, /* displayId = */ 1)); + GroupedTaskInfo recentTaskInfosOfDesk1 = GroupedTaskInfo.forDeskTasks(/* deskId = */1, + DEFAULT_DISPLAY, tasksInDefaultDesk1, /* minimizedTaskIds = */ + Collections.emptySet()); + GroupedTaskInfo recentTaskInfosOfDesk2 = GroupedTaskInfo.forDeskTasks(/* deskId = */2, + DEFAULT_DISPLAY, tasksInDefaultDesk2, /* minimizedTaskIds = */ + Collections.emptySet()); + GroupedTaskInfo recentTaskInfosOfDesk3 = GroupedTaskInfo.forDeskTasks(/* deskId = */3, + /* displayId = */ 1, tasksInExtend, /* minimizedTaskIds = */ + Collections.emptySet()); + when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())).thenReturn( + new ArrayList<>(Arrays.asList(recentTaskInfosOfDesk1, recentTaskInfosOfDesk2, + recentTaskInfosOfDesk3))); + + List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, + false); + + assertThat(taskList).hasSize(3); + assertThat(taskList.get(2).taskViewType).isEqualTo(TaskViewType.DESKTOP); + List actualFreeformTasksInDesk1 = taskList.get(2).getTasks(); + assertThat(actualFreeformTasksInDesk1).hasSize(2); + assertThat(actualFreeformTasksInDesk1.get(0).key.id).isEqualTo(1); + assertThat(actualFreeformTasksInDesk1.get(0).isMinimized).isFalse(); + assertThat(actualFreeformTasksInDesk1.get(1).key.id).isEqualTo(4); + assertThat(actualFreeformTasksInDesk1.get(1).isMinimized).isFalse(); + assertThat(((DesktopTask) taskList.get(2)).getDeskId()).isEqualTo(1); + assertThat(((DesktopTask) taskList.get(2)).getDisplayId()).isEqualTo(DEFAULT_DISPLAY); + + assertThat(taskList.get(1).taskViewType).isEqualTo(TaskViewType.DESKTOP); + List actualFreeformTasksInDesk2 = taskList.get(1).getTasks(); + assertThat(actualFreeformTasksInDesk2).hasSize(2); + assertThat(actualFreeformTasksInDesk2.get(0).key.id).isEqualTo(2); + assertThat(actualFreeformTasksInDesk2.get(0).isMinimized).isFalse(); + assertThat(actualFreeformTasksInDesk2.get(1).key.id).isEqualTo(3); + assertThat(actualFreeformTasksInDesk2.get(1).isMinimized).isFalse(); + assertThat(((DesktopTask) taskList.get(1)).getDeskId()).isEqualTo(2); + assertThat(((DesktopTask) taskList.get(1)).getDisplayId()).isEqualTo(DEFAULT_DISPLAY); + + assertThat(taskList.get(0).taskViewType).isEqualTo(TaskViewType.DESKTOP); + List actualFreeformTasksInDesk3 = taskList.get(0).getTasks(); + assertThat(actualFreeformTasksInDesk3).hasSize(2); + assertThat(actualFreeformTasksInDesk3.get(0).key.id).isEqualTo(5); + assertThat(actualFreeformTasksInDesk3.get(0).isMinimized).isFalse(); + assertThat(actualFreeformTasksInDesk3.get(1).key.id).isEqualTo(6); + assertThat(actualFreeformTasksInDesk3.get(1).isMinimized).isFalse(); + assertThat(((DesktopTask) taskList.get(0)).getDeskId()).isEqualTo(3); + assertThat(((DesktopTask) taskList.get(0)).getDisplayId()).isEqualTo(1); + } + + @Test + public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception { + String taskDescription = "Wheeee!"; + RecentTaskInfo task1 = new RecentTaskInfo(); + task1.taskDescription = new ActivityManager.TaskDescription(taskDescription); + RecentTaskInfo task2 = new RecentTaskInfo(); + task2.taskDescription = new ActivityManager.TaskDescription(); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2, + new SplitBounds( + /* leftTopBounds = */ new Rect(), + /* rightBottomBounds = */ new Rect(), + /* leftTopTaskId = */ -1, + /* rightBottomTaskId = */ -1, + /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50)); + when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, + false); + + assertEquals(1, taskList.size()); + var tasks = taskList.get(0).getTasks(); + assertEquals(2, tasks.size()); + assertEquals(taskDescription, tasks.get(0).taskDescription.getLabel()); + assertNull(tasks.get(1).taskDescription.getLabel()); + } + + @Test + @DisableFlags({FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS, + FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND}) + public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception { + List tasks = Arrays.asList( + createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY), + createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY), + createRecentTaskInfo(5 /* taskId */, 1 /* displayId */), + createRecentTaskInfo(6 /* taskId */, 1 /* displayId */)); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks( + 0 /* deskId */, DEFAULT_DISPLAY, tasks, + Collections.emptySet() /* minimizedTaskIds */); + when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground( + Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */); + + assertEquals(1, taskList.size()); + assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType); + List actualFreeformTasks = taskList.get(0).getTasks(); + assertEquals(4, actualFreeformTasks.size()); + assertEquals(1, actualFreeformTasks.get(0).key.id); + assertFalse(actualFreeformTasks.get(0).isMinimized); + assertEquals(4, actualFreeformTasks.get(1).key.id); + assertFalse(actualFreeformTasks.get(1).isMinimized); + assertEquals(5, actualFreeformTasks.get(2).key.id); + assertFalse(actualFreeformTasks.get(2).isMinimized); + assertEquals(6, actualFreeformTasks.get(3).key.id); + assertFalse(actualFreeformTasks.get(3).isMinimized); + } + + @Test + @EnableFlags(FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS) + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + public void loadTasksInBackground_freeformTask_createsDesktopTaskPerDisplay() throws Exception { + List tasks = Arrays.asList( + createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY), + createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY), + createRecentTaskInfo(5 /* taskId */, 1 /* displayId */), + createRecentTaskInfo(6 /* taskId */, 1 /* displayId */)); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks( + 0 /* deskId */, DEFAULT_DISPLAY, tasks, + Collections.emptySet() /* minimizedTaskIds */); + when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground( + Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */); + + assertEquals(2, taskList.size()); + assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType); + List actualFreeformTasksDefaultDisplay = taskList.get(0).getTasks(); + assertEquals(2, actualFreeformTasksDefaultDisplay.size()); + assertEquals(1, actualFreeformTasksDefaultDisplay.get(0).key.id); + assertFalse(actualFreeformTasksDefaultDisplay.get(0).isMinimized); + assertEquals(4, actualFreeformTasksDefaultDisplay.get(1).key.id); + assertFalse(actualFreeformTasksDefaultDisplay.get(1).isMinimized); + + List actualFreeformTasksExternalDisplay = taskList.get(1).getTasks(); + assertEquals(2, actualFreeformTasksExternalDisplay.size()); + assertEquals(5, actualFreeformTasksExternalDisplay.get(0).key.id); + assertFalse(actualFreeformTasksExternalDisplay.get(0).isMinimized); + assertEquals(6, actualFreeformTasksExternalDisplay.get(1).key.id); + assertFalse(actualFreeformTasksExternalDisplay.get(1).isMinimized); + } + + @Test + public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_createDesktopTask() + throws Exception { + List tasks = Arrays.asList( + createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY), + createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY), + createRecentTaskInfo(5 /* taskId */, DEFAULT_DISPLAY)); + Set minimizedTaskIds = + Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet()); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks( + 0 /* deskId */, DEFAULT_DISPLAY, tasks, minimizedTaskIds); + when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground( + Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */); + + assertEquals(1, taskList.size()); + assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType); + List actualFreeformTasks = taskList.get(0).getTasks(); + assertEquals(3, actualFreeformTasks.size()); + assertEquals(1, actualFreeformTasks.get(0).key.id); + assertTrue(actualFreeformTasks.get(0).isMinimized); + assertEquals(4, actualFreeformTasks.get(1).key.id); + assertTrue(actualFreeformTasks.get(1).isMinimized); + assertEquals(5, actualFreeformTasks.get(2).key.id); + assertTrue(actualFreeformTasks.get(2).isMinimized); + } + + private TaskInfo createRecentTaskInfo(int taskId, int displayId) { + RecentTaskInfo recentTaskInfo = new RecentTaskInfo(); + recentTaskInfo.taskId = taskId; + recentTaskInfo.displayId = displayId; + return recentTaskInfo; + } +} diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt similarity index 55% rename from quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt index 80fbce72655..a7370b02e9c 100644 --- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt @@ -1,15 +1,20 @@ package com.android.quickstep -import android.content.Context -import android.testing.AndroidTestingRunner -import androidx.test.core.app.ApplicationProvider +import android.view.Display +import androidx.test.annotation.UiThreadTest import androidx.test.filters.SmallTest +import com.android.launcher3.dagger.LauncherComponentProvider import com.android.launcher3.util.DisplayController.CHANGE_DENSITY import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE import com.android.launcher3.util.DisplayController.CHANGE_ROTATION import com.android.launcher3.util.DisplayController.Info +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR +import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.NavigationMode +import com.android.launcher3.util.SandboxApplication import com.android.quickstep.util.GestureExclusionManager +import com.android.systemui.shared.system.QuickStepContract 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 @@ -22,7 +27,9 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SE 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.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -30,24 +37,43 @@ 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.verifyNoMoreInteractions import org.mockito.kotlin.whenever /** Unit test for [RecentsAnimationDeviceState]. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@UiThreadTest +@RunWith(LauncherMultivalentJUnit::class) class RecentsAnimationDeviceStateTest { + @get:Rule val context = SandboxApplication() + @Mock private lateinit var exclusionManager: GestureExclusionManager @Mock private lateinit var info: Info - private val context = ApplicationProvider.getApplicationContext() as Context private lateinit var underTest: RecentsAnimationDeviceState @Before fun setup() { MockitoAnnotations.initMocks(this) - underTest = RecentsAnimationDeviceState(context, exclusionManager) + + val component = LauncherComponentProvider.get(context) + underTest = + RecentsAnimationDeviceState( + context, + exclusionManager, + component.displayController, + component.contextualSearchStateManager, + component.rotationTouchHelper, + component.settingsCache, + component.daggerSingletonTracker, + ) + } + + @After + fun tearDown() { + UI_HELPER_EXECUTOR.submit {}.get() + MAIN_EXECUTOR.submit {}.get() } @Test @@ -64,7 +90,7 @@ class RecentsAnimationDeviceStateTest { underTest.registerExclusionListener() - verifyZeroInteractions(exclusionManager) + verifyNoMoreInteractions(exclusionManager) } @Test @@ -85,7 +111,7 @@ class RecentsAnimationDeviceStateTest { underTest.unregisterExclusionListener() - verifyZeroInteractions(exclusionManager) + verifyNoMoreInteractions(exclusionManager) } @Test @@ -116,33 +142,33 @@ class RecentsAnimationDeviceStateTest { underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY) - verifyZeroInteractions(exclusionManager) + verifyNoMoreInteractions(exclusionManager) } @Test fun trackpadGesturesNotAllowedForSelectedStates() { - val disablingStates = GESTURE_DISABLING_SYSUI_STATES + - SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED + val disablingStates = + GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED allSysUiStates().forEach { state -> val canStartGesture = !disablingStates.contains(state) - underTest.setSystemUiFlags(state) + underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY) 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 - ) + 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) + underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY) assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed) } } @@ -153,48 +179,77 @@ class RecentsAnimationDeviceStateTest { allSysUiStates().forEach { state -> val canStartGesture = !disablingStates.contains(state) - underTest.setSystemUiFlags(state) + underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY) 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) + 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.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY) assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed) } } + @Test + fun getSystemUiStateFlags_defaultAwake() { + val NOT_EXISTENT_DISPLAY = 2 + assertThat(underTest.getSystemUiStateFlags(NOT_EXISTENT_DISPLAY)) + .isEqualTo(QuickStepContract.SYSUI_STATE_AWAKE) + } + + @Test + fun clearSysUIStateFlagsForDisplay_displayNotReturnedAnymore() { + underTest.setSysUIStateFlagsForDisplay(1, /* displayId= */ 1) + + assertThat(underTest.displaysWithSysUIState).contains(1) + assertThat(underTest.getSystemUiStateFlags(1)).isEqualTo(1) + + underTest.clearSysUIStateFlagsForDisplay(1) + + assertThat(underTest.displaysWithSysUIState).doesNotContain(1) + assertThat(underTest.getSystemUiStateFlags(1)) + .isEqualTo(QuickStepContract.SYSUI_STATE_AWAKE) + } + + @Test + fun setSysUIStateFlagsForDisplay_setsCorrectly() { + underTest.setSysUIStateFlagsForDisplay(1, /* displayId= */ 1) + underTest.setSysUIStateFlagsForDisplay(2, /* displayId= */ 2) + + assertThat(underTest.getSystemUiStateFlags(1)).isEqualTo(1) + assertThat(underTest.getSystemUiStateFlags(2)).isEqualTo(2) + assertThat(underTest.displaysWithSysUIState).containsAtLeast(1, 2) + } + private fun allSysUiStates(): List { // 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 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 } diff --git a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java similarity index 75% rename from quickstep/tests/src/com/android/quickstep/RecentsModelTest.java rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java index 648fa932a60..2eb2e4c2ca9 100644 --- a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java @@ -32,22 +32,30 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; +import android.graphics.Rect; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.launcher3.Flags; import com.android.launcher3.R; +import com.android.launcher3.graphics.ThemeManager; import com.android.launcher3.icons.IconProvider; +import com.android.launcher3.util.DaggerSingletonTracker; +import com.android.launcher3.util.LockedUserState; +import com.android.launcher3.util.SplitConfigurationOptions; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.SplitTask; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.TaskStackChangeListeners; +import com.android.wm.shell.shared.split.SplitScreenConstants; -import org.junit.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.Mock; import org.mockito.MockitoAnnotations; @@ -56,6 +64,7 @@ import java.util.function.Consumer; @SmallTest +@RunWith(AndroidJUnit4.class) public class RecentsModelTest { @Mock private Context mContext; @@ -67,7 +76,13 @@ public class RecentsModelTest { private RecentTasksList mTasksList; @Mock - private TaskThumbnailCache.HighResLoadingState mHighResLoadingState; + private HighResLoadingState mHighResLoadingState; + + @Mock + private LockedUserState mLockedUserState; + + @Mock + private ThemeManager mThemeManager; private RecentsModel mRecentsModel; @@ -94,7 +109,8 @@ public void setup() throws NoSuchFieldException { when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true); mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class), - mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class)); + mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class), + mLockedUserState, () -> mThemeManager, mock(DaggerSingletonTracker.class)); mResource = mock(Resources.class); when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3); @@ -111,10 +127,12 @@ public void preloadOnHighResolutionEnabled() { .updateThumbnailInCache(taskArgs.capture(), /* lowResolution= */ eq(false)); GroupTask expectedGroupTask = mTaskResult.get(0); - assertThat(taskArgs.getAllValues().get(0)).isEqualTo( - expectedGroupTask.task1); - assertThat(taskArgs.getAllValues().get(1)).isEqualTo( - expectedGroupTask.task2); + var taskArgsValues = taskArgs.getAllValues(); + var expectedTasks = expectedGroupTask.getTasks(); + assertThat(taskArgsValues.size()).isEqualTo(expectedTasks.size()); + for (int i = 0; i < expectedTasks.size(); ++i) { + assertThat(taskArgsValues.get(i)).isEqualTo(expectedTasks.get(i)); + } } @Test @@ -157,6 +175,17 @@ public void decreaseCacheSizeAndNotPreload() { .updateThumbnailInCache(any(), anyBoolean()); } + @Test + public void themeCallbackAttachedOnUnlock() { + verify(mThemeManager, never()).addChangeListener(any()); + + ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mLockedUserState).runOnUserUnlocked(callbackCaptor.capture()); + + callbackCaptor.getAllValues().forEach(Runnable::run); + verify(mThemeManager, times(1)).addChangeListener(any()); + } + private RecentTasksList.TaskLoadResult getTaskResult() { RecentTasksList.TaskLoadResult allTasks = new RecentTasksList.TaskLoadResult(0, false, 1); ActivityManager.RecentTaskInfo taskInfo1 = new ActivityManager.RecentTaskInfo(); @@ -167,7 +196,13 @@ private RecentTasksList.TaskLoadResult getTaskResult() { Task.TaskKey taskKey2 = new Task.TaskKey(taskInfo2); Task task2 = Task.from(taskKey2, taskInfo2, false); - allTasks.add(new GroupTask(task1, task2, null)); + allTasks.add( + new SplitTask(task1, task2, new SplitConfigurationOptions.SplitBounds( + /* leftTopBounds = */ new Rect(), + /* rightBottomBounds = */ new Rect(), + /* leftTopTaskId = */ -1, + /* rightBottomTaskId = */ -1, + /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50))); return allTasks; } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java new file mode 100644 index 00000000000..c1be1cee9bb --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.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.quickstep; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppSingleton; +import com.android.launcher3.util.AllModulesForTest; +import com.android.launcher3.util.LauncherMultivalentJUnit; +import com.android.quickstep.fallback.FallbackRecentsView; +import com.android.quickstep.fallback.RecentsState; +import com.android.quickstep.fallback.window.RecentsDisplayModel; +import com.android.quickstep.fallback.window.RecentsWindowManager; +import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler; +import com.android.quickstep.views.RecentsViewContainer; + +import dagger.BindsInstance; +import dagger.Component; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +@SmallTest +@RunWith(LauncherMultivalentJUnit.class) +public class RecentsWindowSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase< + RecentsState, + RecentsWindowManager, + FallbackRecentsView, + RecentsWindowSwipeHandler, + FallbackWindowInterface> { + + @Mock private RecentsDisplayModel mRecentsDisplayModel; + @Mock private FallbackRecentsView mRecentsView; + @Mock private RecentsWindowManager mRecentsWindowManager; + + @Before + public void setRecentsDisplayModel() { + mContext.initDaggerComponent(DaggerRecentsWindowSwipeHandlerTestCase_TestComponent.builder() + .bindRecentsDisplayModel(mRecentsDisplayModel)); + } + + @NonNull + @Override + protected RecentsWindowSwipeHandler createSwipeHandler(long touchTimeMs, + boolean continuingLastGesture) { + return new RecentsWindowSwipeHandler( + mContext, + mTaskAnimationManager, + mGestureState, + touchTimeMs, + continuingLastGesture, + mInputConsumerController, + mMSDLPlayerWrapper); + } + + @NonNull + @Override + protected RecentsViewContainer getRecentsContainer() { + return mRecentsWindowManager; + } + + @NonNull + @Override + protected FallbackRecentsView getRecentsView() { + return mRecentsView; + } + + @LauncherAppSingleton + @Component(modules = {AllModulesForTest.class}) + interface TestComponent extends LauncherAppComponent { + @Component.Builder + interface Builder extends LauncherAppComponent.Builder { + @BindsInstance Builder bindRecentsDisplayModel(RecentsDisplayModel model); + @Override LauncherAppComponent build(); + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java new file mode 100644 index 00000000000..75b59d734a8 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java @@ -0,0 +1,147 @@ +/* + * 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 org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Bundle; +import android.view.Display; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.systemui.shared.system.RecentsAnimationControllerCompat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TaskAnimationManagerTest { + + protected final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + + @Mock + private SystemUiProxy mSystemUiProxy; + + private TaskAnimationManager mTaskAnimationManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTaskAnimationManager = new TaskAnimationManager(mContext, + RecentsAnimationDeviceState.INSTANCE.get(mContext), Display.DEFAULT_DISPLAY) { + @Override + SystemUiProxy getSystemUiProxy() { + return mSystemUiProxy; + } + }; + } + + @Test + public void startRecentsActivity_allowBackgroundLaunch() { + final LauncherActivityInterface activityInterface = mock(LauncherActivityInterface.class); + final GestureState gestureState = mock(GestureState.class); + final RecentsAnimationCallbacks.RecentsAnimationListener listener = + mock(RecentsAnimationCallbacks.RecentsAnimationListener.class); + doReturn(activityInterface).when(gestureState).getContainerInterface(); + runOnMainSync(() -> + mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener)); + final ArgumentCaptor optionsCaptor = + ArgumentCaptor.forClass(ActivityOptions.class); + verify(mSystemUiProxy) + .startRecentsActivity(any(), optionsCaptor.capture(), any(), anyBoolean()); + assertEquals(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS, + optionsCaptor.getValue().getPendingIntentBackgroundActivityStartMode()); + } + + @Test + public void testLauncherDestroyed_whileRecentsAnimationStartPending_finishesAnimation() { + final GestureState gestureState = mock(GestureState.class); + final ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(RecentsAnimationCallbacks.class); + final RecentsAnimationControllerCompat controllerCompat = + mock(RecentsAnimationControllerCompat.class); + final RemoteAnimationTarget remoteAnimationTarget = new RemoteAnimationTarget( + /* taskId= */ 0, + /* mode= */ RemoteAnimationTarget.MODE_CLOSING, + /* leash= */ new SurfaceControl(), + /* isTranslucent= */ false, + /* clipRect= */ null, + /* contentInsets= */ null, + /* prefixOrderIndex= */ 0, + /* position= */ null, + /* localBounds= */ null, + /* screenSpaceBounds= */ null, + new Configuration().windowConfiguration, + /* isNotInRecents= */ false, + /* startLeash= */ null, + /* startBounds= */ null, + /* taskInfo= */ new ActivityManager.RunningTaskInfo(), + /* allowEnterPip= */ false); + + doReturn(mock(LauncherActivityInterface.class)).when(gestureState).getContainerInterface(); + when(mSystemUiProxy + .startRecentsActivity(any(), any(), listenerCaptor.capture(), anyBoolean())) + .thenReturn(true); + when(gestureState.getRunningTaskIds(anyBoolean())).thenReturn(new int[0]); + + runOnMainSync(() -> { + mTaskAnimationManager.startRecentsAnimation( + gestureState, + new Intent(), + mock(RecentsAnimationCallbacks.RecentsAnimationListener.class)); + mTaskAnimationManager.onLauncherDestroyed(); + listenerCaptor.getValue().onAnimationStart( + controllerCompat, + new RemoteAnimationTarget[] { remoteAnimationTarget }, + new RemoteAnimationTarget[] { remoteAnimationTarget }, + new Rect(), + new Rect(), + new Bundle(), + new TransitionInfo(0, 0)); + }); + runOnMainSync(() -> verify(controllerCompat) + .finish(/* toHome= */ eq(false), anyBoolean(), any())); + } + + protected static void runOnMainSync(Runnable runnable) { + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java similarity index 96% rename from quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java rename to quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java index 4e04261e5ce..3686c16ad1d 100644 --- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.res.Resources; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.launcher3.R; @@ -35,12 +36,14 @@ import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; @SmallTest +@RunWith(AndroidJUnit4.class) public class TaskThumbnailCacheTest { @Mock private Context mContext; diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/fallback/RecentsStateUtilsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/fallback/RecentsStateUtilsTest.kt new file mode 100644 index 00000000000..4e9dae8da07 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/fallback/RecentsStateUtilsTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.fallback + +import com.android.launcher3.testing.shared.TestProtocol.BACKGROUND_APP_STATE_ORDINAL +import com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL +import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL +import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL +import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelTablet2023"]) +class RecentsStateUtilsTest { + + @Test + fun testRecentsStateDefault_toLauncherStateOrdinal_isOverviewStateOrdinal() { + assertThat(RecentsState.DEFAULT.toLauncherStateOrdinal()).isEqualTo(OVERVIEW_STATE_ORDINAL) + } + + @Test + fun testRecentsStateModal_toLauncherStateOrdinal_isModalTaskStateOrdinal() { + assertThat(RecentsState.MODAL_TASK.toLauncherStateOrdinal()) + .isEqualTo(OVERVIEW_MODAL_TASK_STATE_ORDINAL) + } + + @Test + fun testRecentsStateBackgroundApp_toLauncherStateOrdinal_isBackgroundAppStateOrdinal() { + assertThat(RecentsState.BACKGROUND_APP.toLauncherStateOrdinal()) + .isEqualTo(BACKGROUND_APP_STATE_ORDINAL) + } + + @Test + fun testRecentsStateHome_toLauncherStateOrdinal_isNormalStateOrdinal() { + assertThat(RecentsState.HOME.toLauncherStateOrdinal()).isEqualTo(NORMAL_STATE_ORDINAL) + } + + @Test + fun testRecentsStateBgLauncher_toLauncherStateOrdinal_isNormalStateOrdinal() { + assertThat(RecentsState.BG_LAUNCHER.toLauncherStateOrdinal()) + .isEqualTo(NORMAL_STATE_ORDINAL) + } + + @Test + fun testRecentsStateOverviewSplitSelect_toLauncherStateOrdinal_isOverviewSplitSelectStateOrdinal() { + assertThat(RecentsState.OVERVIEW_SPLIT_SELECT.toLauncherStateOrdinal()) + .isEqualTo(OVERVIEW_SPLIT_SELECT_ORDINAL) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandlerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandlerTest.java new file mode 100644 index 00000000000..90187753168 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandlerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.inputconsumers; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.quickstep.DeviceConfigWrapper; +import com.android.quickstep.NavHandle; +import com.android.quickstep.util.TestExtensions; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NavHandleLongPressHandlerTest { + + private NavHandleLongPressHandler mLongPressHandler; + @Mock private NavHandle mNavHandle; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mLongPressHandler = new NavHandleLongPressHandler(context); + } + + @Test + public void testStartNavBarAnimation_flagDisabled() { + try (AutoCloseable flag = overrideAnimateLPNHFlag(false)) { + mLongPressHandler.startNavBarAnimation(mNavHandle); + verify(mNavHandle, never()) + .animateNavBarLongPress(anyBoolean(), anyBoolean(), anyLong()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testStartNavBarAnimation_flagEnabled() { + try (AutoCloseable flag = overrideAnimateLPNHFlag(true)) { + mLongPressHandler.startNavBarAnimation(mNavHandle); + verify(mNavHandle).animateNavBarLongPress(anyBoolean(), anyBoolean(), anyLong()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private AutoCloseable overrideAnimateLPNHFlag(boolean value) { + return TestExtensions.overrideNavConfigFlag( + "ANIMATE_LPNH", value, () -> DeviceConfigWrapper.get().getAnimateLpnh()); + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java new file mode 100644 index 00000000000..ee9505cdbbe --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java @@ -0,0 +1,545 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.inputconsumers; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_HOVER_ENTER; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_UP; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR; +import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.quickstep.DeviceConfigWrapper.DEFAULT_LPNH_TIMEOUT_MS; +import static com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer.MIN_TIME_TO_LOG_ABANDON_MS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.os.SystemClock; +import android.view.MotionEvent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppSingleton; +import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.util.AllModulesForTest; +import com.android.launcher3.util.DisplayController; +import com.android.launcher3.util.SandboxContext; +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.TestExtensions; +import com.android.systemui.shared.system.InputMonitorCompat; + +import dagger.BindsInstance; +import dagger.Component; + +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 java.util.concurrent.atomic.AtomicBoolean; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NavHandleLongPressInputConsumerTest { + + private static final float TOUCH_SLOP = 10; + private static final float SQUARED_TOUCH_SLOP = 100; + + private final AtomicBoolean mLongPressTriggered = new AtomicBoolean(); + private final Runnable mLongPressRunnable = () -> mLongPressTriggered.set(true); + private NavHandleLongPressInputConsumer mUnderTest; + private SandboxContext mContext; + private float mScreenWidth; + private long mDownTimeMs; + @Mock InputConsumer mDelegate; + @Mock InputMonitorCompat mInputMonitor; + @Mock RecentsAnimationDeviceState mDeviceState; + @Mock NavHandle mNavHandle; + @Mock GestureState mGestureState; + @Mock NavHandleLongPressHandler mNavHandleLongPressHandler; + @Mock TopTaskTracker mTopTaskTracker; + @Mock TopTaskTracker.CachedTaskInfo mTaskInfo; + @Mock StatsLogManager mStatsLogManager; + @Mock StatsLogManager.StatsLogger mStatsLogger; + @Mock StatsLogManager.StatsLatencyLogger mStatsLatencyLogger; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mTopTaskTracker.getCachedTopTask(anyBoolean(), anyInt())).thenReturn(mTaskInfo); + when(mDeviceState.getSquaredTouchSlop()).thenReturn(SQUARED_TOUCH_SLOP); + when(mDelegate.allowInterceptByParent()).thenReturn(true); + mLongPressTriggered.set(false); + when(mNavHandleLongPressHandler.getLongPressRunnable(any(), anyInt())).thenReturn( + mLongPressRunnable); + when(mStatsLogger.withPackageName(any())).thenReturn(mStatsLogger); + when(mStatsLatencyLogger.withInstanceId(any())).thenReturn(mStatsLatencyLogger); + when(mStatsLatencyLogger.withLatency(anyLong())).thenReturn(mStatsLatencyLogger); + when(mStatsLogManager.logger()).thenReturn(mStatsLogger); + when(mStatsLogManager.latencyLogger()).thenReturn(mStatsLatencyLogger); + initializeObjectUnderTest(); + } + + @After + public void tearDown() throws Exception { + MAIN_EXECUTOR.getHandler().removeCallbacks(mLongPressRunnable); + MAIN_EXECUTOR.submit(() -> null).get(); + mContext.onDestroy(); + } + + @Test + public void testGetType() { + assertThat(mUnderTest.getType() & InputConsumer.TYPE_NAV_HANDLE_LONG_PRESS).isNotEqualTo(0); + } + + @Test + public void testDelegateDisallowsTouchIntercept() { + when(mDelegate.allowInterceptByParent()).thenReturn(false); + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + + verify(mDelegate).onMotionEvent(any()); + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + verify(mNavHandleLongPressHandler, never()).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogManager); + verifyNoMoreInteractions(mStatsLogger); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + @Test + public void testDelegateDisallowsTouchInterceptAfterTouchDown() { + // Touch down and wait the minimum abandonment time. + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + sleep(MIN_TIME_TO_LOG_ABANDON_MS); + + // Delegate should still get touches unless long press is triggered. + verify(mDelegate).onMotionEvent(any()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + + // Child delegate blocks us from intercepting further motion events. + when(mDelegate.allowInterceptByParent()).thenReturn(false); + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_MOVE)); + + // Delegate should still get motion events unless long press is triggered. + verify(mDelegate, times(2)).onMotionEvent(any()); + // But our handler should be cancelled. + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + // Because we handled touch down before the child blocked additional events, log abandon. + verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON); + } + + @Test + public void testLongPressTriggered() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE); + assertTrue(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR); + verifyNoMoreInteractions(mStatsLatencyLogger); + + // Ensure abandon latency is still not logged after long press. + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP)); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + @Test + public void testLongPressTriggeredWithSlightVerticalMovement() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE, 1)); + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE); + assertTrue(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + @Test + public void testLongPressTriggeredWithSlightHorizontalMovement() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE, mScreenWidth / 2f + 1, 0)); + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE); + assertTrue(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + @Test + public void testLongPressTriggeredWithExtendedTwoStageDuration() { + try (AutoCloseable flag = overrideTwoStageFlag(true)) { + // Reinitialize to pick up updated flag state. + initializeObjectUnderTest(); + + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE, + mScreenWidth / 2f - (TOUCH_SLOP - 1), 0)); + // We have entered the second stage, so the normal timeout shouldn't trigger. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + + // After an extended time, the long press should trigger. + float extendedDurationMultiplier = + (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f); + sleep((long) (DEFAULT_LPNH_TIMEOUT_MS + * (extendedDurationMultiplier - 1))); // -1 because we already waited 1x + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE); + assertTrue(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR); + verifyNoMoreInteractions(mStatsLatencyLogger); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testLongPressTriggeredWithNormalDurationInFirstStage() { + try (AutoCloseable flag = overrideTwoStageFlag(true)) { + // Reinitialize to pick up updated flag state. + initializeObjectUnderTest(); + + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + // We have not entered the second stage, so the normal timeout should trigger. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE); + assertTrue(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR); + verifyNoMoreInteractions(mStatsLatencyLogger); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testLongPressAbortedByTouchUp() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + sleep(MIN_TIME_TO_LOG_ABANDON_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP)); + // Wait past the long press timeout, to be extra sure it wouldn't have triggered. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON); + } + + @Test + public void testLongPressAbortedByTouchCancel() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + sleep(MIN_TIME_TO_LOG_ABANDON_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_CANCEL)); + // Wait past the long press timeout, to be extra sure it wouldn't have triggered. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON); + } + + @Test + public void testTouchCancelWithoutTouchDown() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_CANCEL)); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, never()).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + @Test + public void testLongPressAbortedByTouchSlopPassedVertically() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + sleep(MIN_TIME_TO_LOG_ABANDON_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + + mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE, + -(TOUCH_SLOP + 1))); + // Wait past the long press timeout, to be extra sure it wouldn't have triggered. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON); + } + + @Test + public void testLongPressAbortedByTouchSlopPassedHorizontally() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + sleep(MIN_TIME_TO_LOG_ABANDON_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + + mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE, + mScreenWidth / 2f - (TOUCH_SLOP + 1), 0)); + // Wait past the long press timeout, to be extra sure it wouldn't have triggered. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON); + } + + @Test + public void testLongPressAbortedByTouchSlopPassedVertically_twoStageEnabled() { + try (AutoCloseable flag = overrideTwoStageFlag(true)) { + // Reinitialize to pick up updated flag state. + initializeObjectUnderTest(); + + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + // Enter the second stage. + mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE, + -(TOUCH_SLOP - 1))); + // Normal duration shouldn't trigger. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + + // Move out of the second stage. + mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE, + -(TOUCH_SLOP + 1))); + // Wait past the extended long press timeout, to be sure it wouldn't have triggered. + float extendedDurationMultiplier = + (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f); + sleep((long) (DEFAULT_LPNH_TIMEOUT_MS + * (extendedDurationMultiplier - 1))); // -1 because we already waited 1x + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + // Touch cancelled. + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testLongPressAbortedByTouchSlopPassedHorizontally_twoStageEnabled() { + try (AutoCloseable flag = overrideTwoStageFlag(true)) { + // Reinitialize to pick up updated flag state. + initializeObjectUnderTest(); + + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + // Enter the second stage. + mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE, + mScreenWidth / 2f - (TOUCH_SLOP - 1), 0)); + // Normal duration shouldn't trigger. + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + + // Move out of the second stage. + mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE, + mScreenWidth / 2f - (TOUCH_SLOP + 1), 0)); + // Wait past the extended long press timeout, to be sure it wouldn't have triggered. + float extendedDurationMultiplier = + (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f); + sleep((long) (DEFAULT_LPNH_TIMEOUT_MS + * (extendedDurationMultiplier - 1))); // -1 because we already waited 1x + + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any()); + // Touch cancelled. + verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogger); + verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testTouchOutsideNavHandleIgnored() { + // Touch the far left side of the screen. (y=0 is top of navbar region, picked arbitrarily) + mUnderTest.onMotionEvent(generateMotionEvent(ACTION_DOWN, 0, 0)); + sleep(DEFAULT_LPNH_TIMEOUT_MS); + + // Should be ignored because the x position was not centered in the navbar region. + assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE); + assertFalse(mLongPressTriggered.get()); + verify(mNavHandleLongPressHandler, never()).onTouchStarted(any()); + verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any()); + verifyNoMoreInteractions(mStatsLogManager); + verifyNoMoreInteractions(mStatsLogger); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + @Test + public void testHoverPassedToDelegate() { + // Regardless of whether the delegate wants us to intercept, we tell it about hover events. + when(mDelegate.allowInterceptByParent()).thenReturn(false); + mUnderTest.onHoverEvent(generateCenteredMotionEvent(ACTION_HOVER_ENTER)); + + verify(mDelegate).onHoverEvent(any()); + + when(mDelegate.allowInterceptByParent()).thenReturn(true); + mUnderTest.onHoverEvent(generateCenteredMotionEvent(ACTION_HOVER_ENTER)); + + verify(mDelegate, times(2)).onHoverEvent(any()); + + verifyNoMoreInteractions(mStatsLogManager); + verifyNoMoreInteractions(mStatsLogger); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + @Test + public void testNoLogsForShortTouch() { + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN)); + sleep(10); + mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP)); + verifyNoMoreInteractions(mStatsLogManager); + verifyNoMoreInteractions(mStatsLogger); + verifyNoMoreInteractions(mStatsLatencyLogger); + } + + private void initializeObjectUnderTest() { + if (mContext != null) { + mContext.onDestroy(); + } + mContext = new SandboxContext(getApplicationContext()); + mContext.initDaggerComponent( + DaggerNavHandleLongPressInputConsumerTest_TopTaskTrackerComponent + .builder() + .bindTopTaskTracker(mTopTaskTracker)); + mScreenWidth = DisplayController.INSTANCE.get(mContext).getInfo().currentSize.x; + mUnderTest = new NavHandleLongPressInputConsumer(mContext, mDelegate, mInputMonitor, + mDeviceState, mNavHandle, mGestureState); + mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler); + mUnderTest.setStatsLogManager(mStatsLogManager); + mDownTimeMs = 0; + } + + private static void sleep(long sleepMs) { + SystemClock.sleep(sleepMs); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + /** Generate a motion event centered horizontally in the screen. */ + private MotionEvent generateCenteredMotionEvent(int motionAction) { + return generateCenteredMotionEventWithYOffset(motionAction, 0); + } + + /** Generate a motion event centered horizontally in the screen, with y offset. */ + private MotionEvent generateCenteredMotionEventWithYOffset(int motionAction, float y) { + return generateMotionEvent(motionAction, mScreenWidth / 2f, y); + } + + private MotionEvent generateMotionEvent(int motionAction, float x, float y) { + if (motionAction == ACTION_DOWN) { + mDownTimeMs = SystemClock.uptimeMillis(); + } + long eventTime = SystemClock.uptimeMillis(); + return MotionEvent.obtain(mDownTimeMs, eventTime, motionAction, x, y, 0); + } + + private static AutoCloseable overrideTwoStageFlag(boolean value) { + return TestExtensions.overrideNavConfigFlag( + "ENABLE_LPNH_TWO_STAGES", + value, + () -> DeviceConfigWrapper.get().getEnableLpnhTwoStages()); + } + + @LauncherAppSingleton + @Component(modules = AllModulesForTest.class) + public interface TopTaskTrackerComponent extends LauncherAppComponent { + @Component.Builder + interface Builder extends LauncherAppComponent.Builder { + @BindsInstance Builder bindTopTaskTracker(TopTaskTracker topTaskTracker); + + @Override + TopTaskTrackerComponent build(); + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt new file mode 100644 index 00000000000..14570b54ec8 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.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.quickstep.logging + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.ALLOW_ROTATION +import com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY +import com.android.launcher3.graphics.ThemeManager +import com.android.launcher3.logging.InstanceId +import com.android.launcher3.logging.StatsLogManager +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_SUGGESTIONS_ENABLED +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_ROTATION_DISABLED +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_ROTATION_ENABLED +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_THEMED_ICON_DISABLED +import com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY +import com.android.launcher3.util.DaggerSingletonTracker +import com.android.launcher3.util.DisplayController +import com.android.launcher3.util.SettingsCache +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class SettingsChangeLoggerTest { + private val mContext: Context = ApplicationProvider.getApplicationContext() + + private val mInstanceId = InstanceId.fakeInstanceId(1) + + private lateinit var mSystemUnderTest: SettingsChangeLogger + + @Mock private lateinit var mStatsLogManager: StatsLogManager + + @Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger + @Mock private lateinit var mTracker: DaggerSingletonTracker + private var displayController: DisplayController = DisplayController.INSTANCE.get(mContext) + private var settingsCache: SettingsCache = SettingsCache.INSTANCE.get(mContext) + + @Captor private lateinit var mEventCaptor: ArgumentCaptor + + private var mDefaultThemedIcons = false + private var mDefaultAllowRotation = false + + private val themeManager: ThemeManager + get() = ThemeManager.INSTANCE.get(mContext) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(mStatsLogManager.logger()).doReturn(mMockLogger) + whenever(mStatsLogManager.logger().withInstanceId(any())).doReturn(mMockLogger) + mDefaultThemedIcons = themeManager.isMonoThemeEnabled + mDefaultAllowRotation = LauncherPrefs.get(mContext).get(ALLOW_ROTATION) + // To match the default value of THEMED_ICONS + themeManager.isMonoThemeEnabled = false + // To match the default value of ALLOW_ROTATION + LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = false) + + mSystemUnderTest = + SettingsChangeLogger( + mContext, + mStatsLogManager, + mTracker, + displayController, + settingsCache, + ) + } + + @After + fun tearDown() { + themeManager.isMonoThemeEnabled = mDefaultThemedIcons + LauncherPrefs.get(mContext).put(ALLOW_ROTATION, mDefaultAllowRotation) + } + + @Test + fun loggingPrefs_correctDefaultValue() { + val systemUnderTest = + SettingsChangeLogger( + mContext, + mStatsLogManager, + mTracker, + displayController, + settingsCache, + ) + + assertThat(systemUnderTest.loggingPrefs[ALLOW_ROTATION_PREFERENCE_KEY]!!.defaultValue) + .isFalse() + assertThat(systemUnderTest.loggingPrefs[ADD_ICON_PREFERENCE_KEY]!!.defaultValue).isTrue() + assertThat(systemUnderTest.loggingPrefs[OVERVIEW_SUGGESTED_ACTIONS]!!.defaultValue).isTrue() + assertThat(systemUnderTest.loggingPrefs[SMARTSPACE_ON_HOME_SCREEN]!!.defaultValue).isTrue() + assertThat(systemUnderTest.loggingPrefs[KEY_ENABLE_MINUS_ONE]!!.defaultValue).isTrue() + } + + @Test + fun logSnapshot_defaultValue() { + mSystemUnderTest.logSnapshot(mInstanceId) + + verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture()) + val capturedEvents = mEventCaptor.allValues + assertThat(capturedEvents.isNotEmpty()).isTrue() + verifyDefaultEvent(capturedEvents) + assertThat(capturedEvents.any { it.id == LAUNCHER_HOME_SCREEN_ROTATION_DISABLED.id }) + .isTrue() + } + + @Test + fun logSnapshot_updateAllowRotation() { + LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = true) + + // This a new object so the values of mLoggablePrefs will be different + SettingsChangeLogger(mContext, mStatsLogManager, mTracker, displayController, settingsCache) + .logSnapshot(mInstanceId) + + verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture()) + val capturedEvents = mEventCaptor.allValues + assertThat(capturedEvents.isNotEmpty()).isTrue() + verifyDefaultEvent(capturedEvents) + assertThat(capturedEvents.any { it.id == LAUNCHER_HOME_SCREEN_ROTATION_ENABLED.id }) + .isTrue() + } + + private fun verifyDefaultEvent(capturedEvents: MutableList) { + assertThat(capturedEvents.any { it.id == LAUNCHER_NOTIFICATION_DOT_ENABLED.id }).isTrue() + assertThat(capturedEvents.any { it.id == LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON.id }) + .isTrue() + assertThat(capturedEvents.any { it.id == LAUNCHER_THEMED_ICON_DISABLED.id }).isTrue() + assertThat(capturedEvents.any { it.id == LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED.id }) + .isTrue() + assertThat(capturedEvents.any { it.id == LAUNCHER_ALL_APPS_SUGGESTIONS_ENABLED.id }) + .isTrue() + assertThat(capturedEvents.any { it.id == LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED.id }) + .isTrue() + assertThat(capturedEvents.any { it.id == LAUNCHER_GOOGLE_APP_SWIPE_LEFT_ENABLED }).isTrue() + } + + companion object { + private const val KEY_ENABLE_MINUS_ONE = "pref_enable_minus_one" + private const val OVERVIEW_SUGGESTED_ACTIONS = "pref_overview_action_suggestions" + private const val SMARTSPACE_ON_HOME_SCREEN = "pref_smartspace_home_screen" + + private const val LAUNCHER_GOOGLE_APP_SWIPE_LEFT_ENABLED = 617 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt index ea52842e220..66b3b047d7a 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt @@ -43,12 +43,12 @@ class LandscapePagedViewHandlerTest { if (isEnabled) { setFlagsRule.enableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, ) } else { setFlagsRule.disableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, ) } } @@ -62,6 +62,7 @@ class LandscapePagedViewHandlerTest { isRTL, OVERVIEW_TASK_MARGIN_PX, DIVIDER_SIZE_PX, + oneIconHiddenDueToSmallWidth = false, ) } @@ -107,14 +108,8 @@ class LandscapePagedViewHandlerTest { 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) + assertThat(topLeftY).isEqualTo(-316) + assertThat(bottomRightY).isEqualTo(0) } /** Test updateSplitIconsPosition */ diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt index 2bc182c02de..d455b0d1e93 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt @@ -43,12 +43,12 @@ class SeascapePagedViewHandlerTest { if (isEnabled) { setFlagsRule.enableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, ) } else { setFlagsRule.disableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, ) } } @@ -62,6 +62,7 @@ class SeascapePagedViewHandlerTest { isRTL, OVERVIEW_TASK_MARGIN_PX, DIVIDER_SIZE_PX, + oneIconHiddenDueToSmallWidth = false, ) } @@ -109,12 +110,6 @@ class SeascapePagedViewHandlerTest { 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) } @@ -166,7 +161,7 @@ class SeascapePagedViewHandlerTest { `when`(iconView.layoutParams).thenReturn(frameLayout) sut.updateSplitIconsPosition(iconView, expectedTranslationY, false) - assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START) + assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.END) verify(iconView).setSplitTranslationX(0f) verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) } @@ -181,7 +176,7 @@ class SeascapePagedViewHandlerTest { `when`(iconView.layoutParams).thenReturn(frameLayout) sut.updateSplitIconsPosition(iconView, expectedTranslationY, true) - assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END) + assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.START) verify(iconView).setSplitTranslationX(0f) verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt new file mode 100644 index 00000000000..4adf01ef346 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.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.recents.data + +import com.android.quickstep.HighResLoadingState.HighResLoadingStateChangedCallback + +class FakeHighResLoadingStateNotifier : HighResLoadingStateNotifier { + val listeners = mutableListOf() + + override fun addCallback(callback: HighResLoadingStateChangedCallback) { + listeners.add(callback) + } + + override fun removeCallback(callback: HighResLoadingStateChangedCallback) { + listeners.remove(callback) + } +} 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 index eaeb513ea5c..9e99a0bd62b 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt @@ -20,10 +20,12 @@ import com.android.quickstep.util.GroupTask import java.util.function.Consumer class FakeRecentTasksDataSource : RecentTasksDataSource { - var taskList: List = listOf() + private var taskList: List = listOf() override fun getTasks(callback: Consumer>?): Int { - callback?.accept(taskList) + // Makes a copy of the GroupTask to create a new GroupTask instance and to simulate + // RecentsModel::getTasks behavior. + callback?.accept(taskList.map { it.copy() }) return 0 } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt new file mode 100644 index 00000000000..4e909035bb2 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt @@ -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. + */ + +package com.android.quickstep.recents.data + +class FakeRecentsDeviceProfileRepository : RecentsDeviceProfileRepository { + private var recentsDeviceProfile = + RecentsDeviceProfile(isLargeScreen = false, canEnterDesktopMode = false) + + override fun getRecentsDeviceProfile() = recentsDeviceProfile + + fun setRecentsDeviceProfile(newValue: RecentsDeviceProfile) { + recentsDeviceProfile = newValue + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt new file mode 100644 index 00000000000..c328672d9dd --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.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 android.view.Surface + +class FakeRecentsRotationStateRepository : RecentsRotationStateRepository { + private var recentsRotationState = + RecentsRotationState( + activityRotation = Surface.ROTATION_0, + orientationHandlerRotation = Surface.ROTATION_0 + ) + + override fun getRecentsRotationState() = recentsRotationState + + fun setRecentsRotationState(newValue: RecentsRotationState) { + recentsRotationState = newValue + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt new file mode 100644 index 00000000000..f6f158f546d --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.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.quickstep.recents.data + +import android.graphics.drawable.Drawable +import com.android.quickstep.TaskIconCache.TaskCacheEntry +import com.android.quickstep.task.thumbnail.data.TaskIconDataSource +import com.android.systemui.shared.recents.model.Task +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.yield +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class FakeTaskIconDataSource : TaskIconDataSource { + + val taskIdToDrawable: MutableMap = + (0..10).associateWith { mockCopyableDrawable() }.toMutableMap() + private val completionPrevented: MutableSet = mutableSetOf() + + /** Retrieves and sets an icon on [task] from [taskIdToDrawable]. */ + override suspend fun getIcon(task: Task): TaskCacheEntry { + while (task.key.id in completionPrevented) { + yield() + } + return TaskCacheEntry( + taskIdToDrawable.getValue(task.key.id), + "content desc ${task.key.id}", + "title ${task.key.id}", + ) + } + + fun preventIconLoad(taskId: Int) { + completionPrevented.add(taskId) + } + + fun completeLoadingForTask(taskId: Int) { + completionPrevented.remove(taskId) + } + + fun completeLoading() { + completionPrevented.clear() + } + + companion object { + fun mockCopyableDrawable(): Drawable { + val mutableDrawable = mock() + val immutableDrawable = + mock().apply { whenever(mutate()).thenReturn(mutableDrawable) } + val constantState = + mock().apply { + whenever(newDrawable()).thenReturn(immutableDrawable) + } + return mutableDrawable.apply { whenever(this.constantState).thenReturn(constantState) } + } + } +} + +fun Task.assertHasIconDataFromSource(fakeTaskIconDataSource: FakeTaskIconDataSource) { + assertThat(icon).isEqualTo(fakeTaskIconDataSource.taskIdToDrawable[key.id]) + assertThat(titleDescription).isEqualTo("content desc ${key.id}") + assertThat(title).isEqualTo("title ${key.id}") +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt index b66b7351bfd..40d5e0264c3 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt @@ -17,36 +17,41 @@ 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 kotlinx.coroutines.yield import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever class FakeTaskThumbnailDataSource : TaskThumbnailDataSource { - val taskIdToBitmap: Map = (0..10).associateWith { mock() } - val taskIdToUpdatingTask: MutableMap Unit> = mutableMapOf() - var shouldLoadSynchronously: Boolean = true + val taskIdToBitmap: MutableMap = + (0..10).associateWith { mock() }.toMutableMap() + private val completionPrevented: MutableSet = mutableSetOf() + private val getThumbnailCalls = mutableMapOf() + + var highResEnabled = true /** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */ - override fun updateThumbnailInBackground( - task: Task, - callback: Consumer - ): CancellableTask? { - val thumbnailData = mock() - 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 + override suspend fun getThumbnail(task: Task): ThumbnailData { + getThumbnailCalls[task.key.id] = (getThumbnailCalls[task.key.id] ?: 0) + 1 + + while (task.key.id in completionPrevented) { + yield() } - return null + return ThumbnailData( + thumbnail = taskIdToBitmap[task.key.id], + reducedResolution = !highResEnabled, + ) + } + + fun getNumberOfGetThumbnailCalls(taskId: Int): Int = getThumbnailCalls[taskId] ?: 0 + + fun preventThumbnailLoad(taskId: Int) { + completionPrevented.add(taskId) + } + + fun completeLoadingForTask(taskId: Int) { + completionPrevented.remove(taskId) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt new file mode 100644 index 00000000000..765f0d1dfbc --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.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.recents.data + +import com.android.quickstep.util.TaskVisualsChangeListener + +class FakeTaskVisualsChangeNotifier : TaskVisualsChangeNotifier { + val listeners = mutableListOf() + + override fun addThumbnailChangeListener(listener: TaskVisualsChangeListener) { + listeners.add(listener) + } + + override fun removeThumbnailChangeListener(listener: TaskVisualsChangeListener) { + listeners.remove(listener) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt index e160627d750..35af29f2c6e 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt @@ -16,25 +16,55 @@ package com.android.quickstep.recents.data +import android.graphics.drawable.Drawable 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.combine import kotlinx.coroutines.flow.map class FakeTasksRepository : RecentTasksRepository { private var thumbnailDataMap: Map = emptyMap() + private var taskIconDataMap: Map = emptyMap() private var tasks: MutableStateFlow> = MutableStateFlow(emptyList()) - private var visibleTasks: MutableStateFlow> = MutableStateFlow(emptyList()) + private var visibleTasks: MutableStateFlow> = MutableStateFlow(emptySet()) override fun getAllTaskData(forceRefresh: Boolean): Flow> = tasks override fun getTaskDataById(taskId: Int): Flow = - getAllTaskData().map { taskList -> taskList.firstOrNull { it.key.id == taskId } } + combine(getAllTaskData(), visibleTasks) { taskList, visibleTasks -> + taskList.filter { visibleTasks.contains(it.key.id) } + } + .map { taskList -> + val task = taskList.firstOrNull { it.key.id == taskId } ?: return@map null + Task(task).apply { + thumbnail = task.thumbnail + icon = task.icon + titleDescription = task.titleDescription + title = task.title + } + } - override fun setVisibleTasks(visibleTaskIdList: List) { + override fun getThumbnailById(taskId: Int): Flow = + getTaskDataById(taskId).map { it?.thumbnail } + + override fun getCurrentThumbnailById(taskId: Int): ThumbnailData? = + tasks.value.firstOrNull { it.key.id == taskId }?.thumbnail + + override fun setVisibleTasks(visibleTaskIdList: Set) { visibleTasks.value = visibleTaskIdList - tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } } + tasks.value = + tasks.value.map { + it.apply { + thumbnail = thumbnailDataMap[it.key.id] + taskIconDataMap[it.key.id]?.let { data -> + title = data.title + titleDescription = data.titleDescription + icon = data.icon + } + } + } } fun seedTasks(tasks: List) { @@ -44,4 +74,15 @@ class FakeTasksRepository : RecentTasksRepository { fun seedThumbnailData(thumbnailDataMap: Map) { this.thumbnailDataMap = thumbnailDataMap } + + fun seedIconData(id: Int, title: String, contentDescription: String, icon: Drawable) { + val iconData = FakeIconData(icon, contentDescription, title) + this.taskIconDataMap = mapOf(id to iconData) + } + + private data class FakeIconData( + val icon: Drawable, + val titleDescription: String, + val title: String, + ) } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt new file mode 100644 index 00000000000..017f037fee4 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.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 android.view.Surface.ROTATION_270 +import android.view.Surface.ROTATION_90 +import com.android.quickstep.orientation.SeascapePagedViewHandler +import com.android.quickstep.util.RecentsOrientedState +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +/** Test for [RecentsRotationStateRepositoryImpl] */ +class RecentsRotationStateRepositoryImplTest { + private val recentsOrientedState = mock() + + private val systemUnderTest = RecentsRotationStateRepositoryImpl(recentsOrientedState) + + @Test + fun orientedStateMappedCorrectly() { + whenever(recentsOrientedState.recentsActivityRotation).thenReturn(ROTATION_90) + whenever(recentsOrientedState.orientationHandler).thenReturn(SeascapePagedViewHandler()) + + assertThat(systemUnderTest.getRecentsRotationState()) + .isEqualTo( + RecentsRotationState( + activityRotation = ROTATION_90, + orientationHandlerRotation = ROTATION_270 + ) + ) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt new file mode 100644 index 00000000000..b91f8bd6fd8 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt @@ -0,0 +1,213 @@ +/* + * 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 android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback +import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback +import com.android.systemui.shared.recents.model.Task.TaskKey +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions + +@RunWith(AndroidJUnit4::class) +class TaskVisualsChangedDelegateTest { + private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier() + private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier() + + val systemUnderTest = + TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier) + + @Test + fun addingFirstListener_addsListenerToNotifiers() { + systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock()) + + assertThat(taskVisualsChangeNotifier.listeners.single()).isEqualTo(systemUnderTest) + assertThat(highResLoadingStateNotifier.listeners.single()).isEqualTo(systemUnderTest) + } + + @Test + fun addingAndRemovingListener_removesListenerFromNotifiers() { + systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock()) + systemUnderTest.unregisterTaskThumbnailChangedCallback(createTaskKey(id = 1)) + + assertThat(taskVisualsChangeNotifier.listeners).isEmpty() + assertThat(highResLoadingStateNotifier.listeners).isEmpty() + } + + @Test + fun addingTwoAndRemovingOneListener_doesNotRemoveListenerFromNotifiers() { + systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock()) + systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 2), mock()) + systemUnderTest.unregisterTaskThumbnailChangedCallback(createTaskKey(id = 1)) + + assertThat(taskVisualsChangeNotifier.listeners.single()).isEqualTo(systemUnderTest) + assertThat(highResLoadingStateNotifier.listeners.single()).isEqualTo(systemUnderTest) + } + + @Test + fun onTaskIconChangedWithTaskId_notifiesCorrectListenerOnly() { + val expectedListener = mock() + val additionalListener = mock() + systemUnderTest.registerTaskIconChangedCallback(createTaskKey(id = 1), expectedListener) + systemUnderTest.registerTaskIconChangedCallback(createTaskKey(id = 2), additionalListener) + + systemUnderTest.onTaskIconChanged(1) + + verify(expectedListener).onTaskIconChanged() + verifyNoMoreInteractions(additionalListener) + } + + @Test + fun onTaskIconChangedWithoutTaskId_notifiesCorrectListenerOnly() { + val expectedListener = mock() + val listener = mock() + // Correct match + systemUnderTest.registerTaskIconChangedCallback( + createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1), + expectedListener, + ) + // 1 out of 2 match + systemUnderTest.registerTaskIconChangedCallback( + createTaskKey(id = 2, pkg = PACKAGE_NAME, userId = 1), + listener, + ) + systemUnderTest.registerTaskIconChangedCallback( + createTaskKey(id = 3, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 2), + listener, + ) + // 0 out of 2 match + systemUnderTest.registerTaskIconChangedCallback( + createTaskKey(id = 4, pkg = PACKAGE_NAME, userId = 2), + listener, + ) + + systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1)) + + verify(expectedListener).onTaskIconChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun replacedTaskIconChangedCallbacks_notCalled() { + val replacedListener = mock() + val newListener = mock() + systemUnderTest.registerTaskIconChangedCallback( + createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1), + replacedListener, + ) + systemUnderTest.registerTaskIconChangedCallback( + createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1), + newListener, + ) + + systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1)) + + verifyNoMoreInteractions(replacedListener) + verify(newListener).onTaskIconChanged() + } + + @Test + fun onTaskThumbnailChanged_notifiesCorrectListenerOnly() { + val expectedListener = mock() + val additionalListener = mock() + val expectedThumbnailData = ThumbnailData(snapshotId = 12345) + systemUnderTest.registerTaskThumbnailChangedCallback( + createTaskKey(id = 1), + expectedListener, + ) + systemUnderTest.registerTaskThumbnailChangedCallback( + createTaskKey(id = 2), + additionalListener, + ) + + systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData) + + verify(expectedListener).onTaskThumbnailChanged(expectedThumbnailData) + verifyNoMoreInteractions(additionalListener) + } + + @Test + fun onHighResLoadingStateChanged_toEnabled_notifiesAllListeners() { + val expectedListener = mock() + val additionalListener = mock() + systemUnderTest.registerTaskThumbnailChangedCallback( + createTaskKey(id = 1), + expectedListener, + ) + systemUnderTest.registerTaskThumbnailChangedCallback( + createTaskKey(id = 2), + additionalListener, + ) + + systemUnderTest.onHighResLoadingStateChanged(true) + + verify(expectedListener).onHighResLoadingStateChanged(true) + verify(additionalListener).onHighResLoadingStateChanged(true) + } + + @Test + fun onHighResLoadingStateChanged_toDisabled_notifiesAllListeners() { + val expectedListener = mock() + val additionalListener = mock() + systemUnderTest.registerTaskThumbnailChangedCallback( + createTaskKey(id = 1), + expectedListener, + ) + systemUnderTest.registerTaskThumbnailChangedCallback( + createTaskKey(id = 2), + additionalListener, + ) + + systemUnderTest.onHighResLoadingStateChanged(false) + + verify(expectedListener).onHighResLoadingStateChanged(false) + verify(additionalListener).onHighResLoadingStateChanged(false) + } + + @Test + fun replacedTaskThumbnailChangedCallbacks_notCalled() { + val replacedListener1 = mock() + val newListener1 = mock() + val expectedThumbnailData = ThumbnailData(snapshotId = 12345) + systemUnderTest.registerTaskThumbnailChangedCallback( + createTaskKey(id = 1), + replacedListener1, + ) + systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), newListener1) + + systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData) + + verifyNoMoreInteractions(replacedListener1) + verify(newListener1).onTaskThumbnailChanged(expectedThumbnailData) + } + + private fun createTaskKey(id: Int = 1, pkg: String = PACKAGE_NAME, userId: Int = 1) = + TaskKey(id, 0, Intent().setPackage(pkg), ComponentName("", ""), userId, 0) + + private companion object { + const val PACKAGE_NAME = "com.test.test" + const val ALTERNATIVE_PACKAGE_NAME = "com.test.test2" + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt index c28a85a8f88..e22892c9cc8 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt @@ -18,133 +18,455 @@ package com.android.quickstep.recents.data import android.content.ComponentName import android.content.Intent -import com.android.quickstep.TaskIconCache +import android.graphics.Bitmap +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.launcher3.util.TestDispatcherProvider import com.android.quickstep.util.DesktopTask -import com.android.quickstep.util.GroupTask +import com.android.quickstep.util.SingleTask +import com.android.quickstep.util.SplitTask import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.ThumbnailData +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.spy import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::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)) + SingleTask(tasks[0]), + SplitTask( + tasks[1], + tasks[2], + SplitConfigurationOptions.SplitBounds( + /* leftTopBounds = */ Rect(), + /* rightBottomBounds = */ Rect(), + /* leftTopTaskId = */ -1, + /* rightBottomTaskId = */ -1, + /* snapPosition = */ SNAP_TO_2_50_50, + ), + ), + DesktopTask(deskId = 0, DEFAULT_DISPLAY, tasks.subList(3, 6)), ) private val recentsModel = FakeRecentTasksDataSource() private val taskThumbnailDataSource = FakeTaskThumbnailDataSource() - private val taskIconCache = mock() + private val taskIconDataSource = FakeTaskIconDataSource() + private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier() + private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier() + private val taskVisualsChangedDelegate = + spy(TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier)) + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) private val systemUnderTest = - TasksRepository(recentsModel, taskThumbnailDataSource, taskIconCache) + TasksRepository( + recentsModel, + taskThumbnailDataSource, + taskIconDataSource, + taskVisualsChangedDelegate, + testScope.backgroundScope, + TestDispatcherProvider(dispatcher), + ) @Test - fun getAllTaskDataReturnsFlattenedListOfTasks() = runTest { - recentsModel.seedTasks(defaultTaskList) + fun getAllTaskDataReturnsFlattenedListOfTasks() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + assertThat(systemUnderTest.getAllTaskData(forceRefresh = true).first()).isEqualTo(tasks) + } - assertThat(systemUnderTest.getAllTaskData(forceRefresh = true).first()).isEqualTo(tasks) - } + @Test + fun getTaskDataByIdReturnsSpecificTask() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + assertThat(systemUnderTest.getTaskDataById(2).first()).isEqualTo(tasks[2]) + } @Test - fun getTaskDataByIdReturnsSpecificTask() = runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) + fun getThumbnailByIdReturnsNullWithNoLoadedThumbnails() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) - assertThat(systemUnderTest.getTaskDataById(2).first()).isEqualTo(tasks[2]) - } + assertThat(systemUnderTest.getThumbnailById(1).first()).isNull() + } @Test - fun setVisibleTasksPopulatesThumbnails() = runTest { - recentsModel.seedTasks(defaultTaskList) - val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - systemUnderTest.getAllTaskData(forceRefresh = true) + fun getCurrentThumbnailByIdReturnsNullWithNoLoadedThumbnails() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) - systemUnderTest.setVisibleTasks(listOf(1, 2)) + assertThat(systemUnderTest.getCurrentThumbnailById(1)).isNull() + } - // .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 getThumbnailByIdReturnsThumbnailWithLoadedThumbnails() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] + + systemUnderTest.setVisibleTasks(setOf(1)) + + assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) + } @Test - fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = runTest { - recentsModel.seedTasks(defaultTaskList) - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - systemUnderTest.getAllTaskData(forceRefresh = true) + fun whenThumbnailIsLoaded_getAllTaskData_usesPreviousLoadedThumbnailAndIcon() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] - systemUnderTest.setVisibleTasks(listOf(1, 2)) + systemUnderTest.setVisibleTasks(setOf(1)) + assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) - // .drop(1) to ignore initial null content before from thumbnail was loaded. - assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap2) + systemUnderTest.getAllTaskData(forceRefresh = true) + assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) + } - // Prevent new loading of Bitmaps - taskThumbnailDataSource.shouldLoadSynchronously = false - systemUnderTest.setVisibleTasks(listOf(2, 3)) + @Test + fun getAllTaskData_clearsPreviouslyLoadedImagesForRemovedTasks() = + testScope.runTest { + // Setup data + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] - assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap2) - } + // Load images for task 1 + systemUnderTest.setVisibleTasks(setOf(1)) + assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) + + // Remove task 1 from "all data" + recentsModel.seedTasks( + defaultTaskList.filterNot { groupTask -> groupTask.tasks.any { it.key.id == 1 } } + ) + systemUnderTest.getAllTaskData(forceRefresh = true) + + // Assert task 1 was fully removed + assertThat(systemUnderTest.getThumbnailById(1).first()?.thumbnail).isNull() + verify(taskVisualsChangedDelegate).unregisterTaskThumbnailChangedCallback(tasks[1].key) + } @Test - fun retrievedThumbnailsAreDiscardedWhenTaskBecomesInvisible() = runTest { - recentsModel.seedTasks(defaultTaskList) - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - systemUnderTest.getAllTaskData(forceRefresh = true) + fun getCurrentThumbnailByIdReturnsThumbnailWithLoadedThumbnails() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] - systemUnderTest.setVisibleTasks(listOf(1, 2)) + systemUnderTest.setVisibleTasks(setOf(1)) - // .drop(1) to ignore initial null content before from thumbnail was loaded. - assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap2) + assertThat(systemUnderTest.getCurrentThumbnailById(1)?.thumbnail).isEqualTo(bitmap1) + } - // Prevent new loading of Bitmaps - taskThumbnailDataSource.shouldLoadSynchronously = false - systemUnderTest.setVisibleTasks(listOf(0, 1)) + @Test + fun setVisibleTasksPopulatesThumbnails() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + systemUnderTest.getAllTaskData(forceRefresh = true) - assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull() - } + systemUnderTest.setVisibleTasks(setOf(1, 2)) + + assertThat(systemUnderTest.getTaskDataById(1).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap1) + assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap2) + } @Test - fun retrievedThumbnailsCauseEmissionOnTaskDataFlow() = runTest { - // Setup fakes - recentsModel.seedTasks(defaultTaskList) - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - taskThumbnailDataSource.shouldLoadSynchronously = false + fun setVisibleTasksPopulatesIcons() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) - // Setup TasksRepository - systemUnderTest.getAllTaskData(forceRefresh = true) - systemUnderTest.setVisibleTasks(listOf(1, 2)) + systemUnderTest.setVisibleTasks(setOf(1, 2)) - // Assert there is no bitmap in first emission - val taskFlow = systemUnderTest.getTaskDataById(2) - val taskFlowValuesList = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { - taskFlow.toList(taskFlowValuesList) + systemUnderTest + .getTaskDataById(1) + .first()!! + .assertHasIconDataFromSource(taskIconDataSource) + systemUnderTest + .getTaskDataById(2) + .first()!! + .assertHasIconDataFromSource(taskIconDataSource) } - assertThat(taskFlowValuesList[0]!!.thumbnail).isNull() - // Simulate bitmap loading after first emission - taskThumbnailDataSource.taskIdToUpdatingTask.getValue(2).invoke() + @Test + fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + systemUnderTest.getAllTaskData(forceRefresh = true) - // Check for second emission - assertThat(taskFlowValuesList[1]!!.thumbnail!!.thumbnail).isEqualTo(bitmap2) - } + systemUnderTest.setVisibleTasks(setOf(1, 2)) + + assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap2) + + // Prevent new loading of Bitmaps + taskThumbnailDataSource.preventThumbnailLoad(2) + systemUnderTest.setVisibleTasks(setOf(2, 3)) + + assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap2) + } + + @Test + fun changingVisibleTasksContainsAlreadyPopulatedIcons() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(setOf(1, 2)) + + systemUnderTest + .getTaskDataById(2) + .first()!! + .assertHasIconDataFromSource(taskIconDataSource) + + // Prevent new loading of Drawables + taskIconDataSource.preventIconLoad(2) + systemUnderTest.setVisibleTasks(setOf(2, 3)) + + systemUnderTest + .getTaskDataById(2) + .first()!! + .assertHasIconDataFromSource(taskIconDataSource) + } + + @Test + fun retrievedImagesAreDiscardedWhenTaskBecomesInvisible() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(setOf(1, 2)) + + val task2 = systemUnderTest.getTaskDataById(2).first()!! + assertThat(task2.thumbnail!!.thumbnail).isEqualTo(bitmap2) + task2.assertHasIconDataFromSource(taskIconDataSource) + + systemUnderTest.setVisibleTasks(setOf(0, 1)) + + val task2AfterVisibleTasksChanged = systemUnderTest.getTaskDataById(2).first()!! + assertThat(task2AfterVisibleTasksChanged.thumbnail).isNull() + assertThat(task2AfterVisibleTasksChanged.icon).isNull() + assertThat(task2AfterVisibleTasksChanged.titleDescription).isNull() + assertThat(task2AfterVisibleTasksChanged.title).isNull() + } + + @Test + fun retrievedThumbnailsCauseEmissionOnTaskDataFlow() = + testScope.runTest { + // Setup fakes + recentsModel.seedTasks(defaultTaskList) + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + + // Setup TasksRepository + systemUnderTest.getAllTaskData(forceRefresh = true) + + val task2DataFlow = systemUnderTest.getTaskDataById(2) + val task2BitmapValues = mutableListOf() + testScope.backgroundScope.launch { + task2DataFlow.map { it?.thumbnail?.thumbnail }.toList(task2BitmapValues) + } + + // Check for first emission + assertThat(task2BitmapValues.single()).isNull() + + systemUnderTest.setVisibleTasks(setOf(2)) + // Check for second emission + assertThat(task2BitmapValues).isEqualTo(listOf(null, bitmap2)) + } + + @Test + fun onTaskThumbnailChanged_setsNewThumbnailDataOnTask() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + systemUnderTest.setVisibleTasks(setOf(1)) + + val expectedThumbnailData = createThumbnailData() + val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1] + val taskDataFlow = systemUnderTest.getTaskDataById(1) + + val task1ThumbnailValues = mutableListOf() + testScope.backgroundScope.launch { + taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues) + } + taskVisualsChangedDelegate.onTaskThumbnailChanged(1, expectedThumbnailData) + + assertThat(task1ThumbnailValues.first()!!.thumbnail).isEqualTo(expectedPreviousBitmap) + assertThat(task1ThumbnailValues.last()).isEqualTo(expectedThumbnailData) + } + + @Test + fun onHighResLoadingStateChanged_highResReplacesLowResThumbnail() = + testScope.runTest { + taskThumbnailDataSource.highResEnabled = false + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(setOf(1)) + + val expectedBitmap = mock() + val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1] + val taskDataFlow = systemUnderTest.getTaskDataById(1) + + val task1ThumbnailValues = mutableListOf() + testScope.backgroundScope.launch { + taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues) + } + + taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap + taskThumbnailDataSource.highResEnabled = true + taskVisualsChangedDelegate.onHighResLoadingStateChanged(true) + + val firstThumbnailValue = task1ThumbnailValues.first()!! + assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap) + assertThat(firstThumbnailValue.reducedResolution).isTrue() + + val lastThumbnailValue = task1ThumbnailValues.last()!! + assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedBitmap) + assertThat(lastThumbnailValue.reducedResolution).isFalse() + } + + @Test + fun onHighResLoadingStateChanged_invisibleTaskIgnored() = + testScope.runTest { + taskThumbnailDataSource.highResEnabled = false + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(setOf(1)) + + val invisibleTaskId = 2 + val taskDataFlow = systemUnderTest.getTaskDataById(invisibleTaskId) + + val task2ThumbnailValues = mutableListOf() + testScope.backgroundScope.launch { + taskDataFlow.map { it?.thumbnail }.toList(task2ThumbnailValues) + } + + taskThumbnailDataSource.highResEnabled = true + taskVisualsChangedDelegate.onHighResLoadingStateChanged(true) + + assertThat(task2ThumbnailValues.filterNotNull()).isEmpty() + assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(2)).isEqualTo(0) + } + + @Test + fun onHighResLoadingStateChanged_lowResDoesNotReplaceHighResThumbnail() = + testScope.runTest { + taskThumbnailDataSource.highResEnabled = true + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(setOf(1)) + + val expectedBitmap = mock() + val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1] + val taskDataFlow = systemUnderTest.getTaskDataById(1) + + val task1ThumbnailValues = mutableListOf() + testScope.backgroundScope.launch { + taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues) + } + + taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap + taskThumbnailDataSource.highResEnabled = false + taskVisualsChangedDelegate.onHighResLoadingStateChanged(false) + + val firstThumbnailValue = task1ThumbnailValues.first()!! + assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap) + assertThat(firstThumbnailValue.reducedResolution).isFalse() + + val lastThumbnailValue = task1ThumbnailValues.last()!! + assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap) + assertThat(lastThumbnailValue.reducedResolution).isFalse() + } + + @Test + fun onTaskIconChanged_setsNewIconOnTask() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(setOf(1)) + + val expectedIcon = FakeTaskIconDataSource.mockCopyableDrawable() + val expectedPreviousIcon = taskIconDataSource.taskIdToDrawable[1] + val taskDataFlow = systemUnderTest.getTaskDataById(1) + + val task1IconValues = mutableListOf() + testScope.backgroundScope.launch { + taskDataFlow.map { it?.icon }.toList(task1IconValues) + } + taskIconDataSource.taskIdToDrawable[1] = expectedIcon + taskVisualsChangedDelegate.onTaskIconChanged(1) + + assertThat(task1IconValues.first()).isEqualTo(expectedPreviousIcon) + assertThat(task1IconValues.last()).isEqualTo(expectedIcon) + } + + @Test + fun setVisibleTasks_multipleTimesWithDifferentTasks_reusesThumbnailRequests() = + testScope.runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + val taskDataFlow = systemUnderTest.getTaskDataById(1) + val task1IconValues = mutableListOf() + testScope.backgroundScope.launch { + taskDataFlow.map { it?.icon }.toList(task1IconValues) + } + + systemUnderTest.setVisibleTasks(setOf(1)) + assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(1)).isEqualTo(1) + + systemUnderTest.setVisibleTasks(setOf(1, 2)) + assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(1)).isEqualTo(1) + } private fun createTaskWithId(taskId: Int) = Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)) + + private fun createThumbnailData(): ThumbnailData { + val bitmap = mock() + whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH) + whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT) + + return ThumbnailData(thumbnail = bitmap) + } + + companion object { + const val THUMBNAIL_WIDTH = 100 + const val THUMBNAIL_HEIGHT = 200 + } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt new file mode 100644 index 00000000000..d3842562d17 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.recents.domain.usecase + +import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS +import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS +import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS +import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV +import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS +import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV +import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class GetSysUiStatusNavFlagsUseCaseTest { + private val sut: GetSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase() + + @Test + fun onLightStatusBarAppearance_returns_LightTheme() { + val thumbnailData = ThumbnailData(appearance = APPEARANCE_LIGHT_STATUS_BARS) + val flag = sut.invoke(thumbnailData) // 6 + flag.assertContainsFlag(FLAG_LIGHT_STATUS) + flag.assertContainsFlag(FLAG_DARK_NAV) + flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV) + } + + @Test + fun onLightNavBarsAppearance_returns_LightTheme() { + val thumbnailData = ThumbnailData(appearance = APPEARANCE_LIGHT_NAVIGATION_BARS) + val flag = sut.invoke(thumbnailData) + flag.assertContainsFlag(FLAG_DARK_STATUS) + flag.assertContainsFlag(FLAG_LIGHT_NAV) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS) + flag.assertDoesNotContainsFlag(FLAG_DARK_NAV) + } + + @Test + fun onLightStatusBarAndNavBarAppearance_returns_LightTheme() { + val thumbnailData = + ThumbnailData( + appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS + ) + val flag = sut.invoke(thumbnailData) + flag.assertContainsFlag(FLAG_LIGHT_NAV) + flag.assertContainsFlag(FLAG_LIGHT_STATUS) + flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS) + flag.assertDoesNotContainsFlag(FLAG_DARK_NAV) + } + + @Test + fun onLightAppearance_returns_LightTheme() { + val thumbnailData = + ThumbnailData( + appearance = + APPEARANCE_LIGHT_CAPTION_BARS or + APPEARANCE_LIGHT_STATUS_BARS or + APPEARANCE_LIGHT_NAVIGATION_BARS + ) + val flag = sut.invoke(thumbnailData) + flag.assertContainsFlag(FLAG_LIGHT_NAV) + flag.assertContainsFlag(FLAG_LIGHT_STATUS) + flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS) + flag.assertDoesNotContainsFlag(FLAG_DARK_NAV) + } + + @Test + fun onDarkAppearance_returns_DarkTheme() { + val thumbnailData = ThumbnailData(appearance = 0) + val flag = sut.invoke(thumbnailData) + flag.assertContainsFlag(FLAG_DARK_STATUS) + flag.assertContainsFlag(FLAG_DARK_NAV) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS) + } + + @Test + fun onUnrelatedDarkAppearance_returns_DarkTheme() { + val thumbnailData = ThumbnailData(appearance = 1) + val flag = sut.invoke(thumbnailData) + flag.assertContainsFlag(FLAG_DARK_STATUS) + flag.assertContainsFlag(FLAG_DARK_NAV) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS) + } + + @Test + fun whenThumbnailIsNull_returns_default() { + val flag = sut.invoke(null) + flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV) + flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS) + flag.assertDoesNotContainsFlag(FLAG_DARK_NAV) + } + + private fun Int.assertContainsFlag(flag: Int) { + assertThat(this and flag).isNotEqualTo(0) + } + + private fun Int.assertDoesNotContainsFlag(flag: Int) { + assertThat(this and flag).isEqualTo(0) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt new file mode 100644 index 00000000000..b036bceea76 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.recents.domain.usecase + +import android.content.ComponentName +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ShapeDrawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.quickstep.recents.data.FakeTasksRepository +import com.android.quickstep.recents.domain.model.TaskModel +import com.android.systemui.shared.recents.model.Task +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class GetTaskUseCaseTest { + private val unconfinedTestDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(unconfinedTestDispatcher) + + private val tasksRepository = FakeTasksRepository() + private val sut = GetTaskUseCase(repository = tasksRepository) + + @Before + fun setUp() { + tasksRepository.seedTasks(listOf(TASK_1)) + } + + @Test + fun taskNotSeeded_returnsNull() = + testScope.runTest { + val result = sut.invoke(NOT_FOUND_TASK_ID).firstOrNull() + assertThat(result).isNull() + } + + @Test + fun taskNotVisible_returnsNull() = + testScope.runTest { + val result = sut.invoke(TASK_1_ID).firstOrNull() + assertThat(result).isNull() + } + + @Test + fun taskVisible_returnsData() = + testScope.runTest { + tasksRepository.setVisibleTasks(setOf(TASK_1_ID)) + val expectedResult = + TaskModel( + id = TASK_1_ID, + title = "Title $TASK_1_ID", + titleDescription = "Content Description $TASK_1_ID", + icon = TASK_1_ICON, + thumbnail = null, + backgroundColor = Color.BLACK, + isLocked = false, + ) + val result = sut.invoke(TASK_1_ID).firstOrNull() + assertThat(result).isEqualTo(expectedResult) + } + + private companion object { + const val NOT_FOUND_TASK_ID = 404 + private const val TASK_1_ID = 1 + private val TASK_1_ICON = ShapeDrawable() + private val TASK_1 = + Task( + Task.TaskKey( + /* id = */ TASK_1_ID, + /* windowingMode = */ 0, + /* intent = */ Intent(), + /* sourceComponent = */ ComponentName("", ""), + /* userId = */ 0, + /* lastActiveTime = */ 2000, + ) + ) + .apply { + title = "Title 1" + titleDescription = "Content Description 1" + colorBackground = Color.BLACK + icon = TASK_1_ICON + thumbnail = null + isLocked = false + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCaseTest.kt new file mode 100644 index 00000000000..7646e69117f --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCaseTest.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.recents.domain.usecase + +import android.graphics.Bitmap +import android.graphics.Matrix +import android.graphics.Rect +import android.view.Surface.ROTATION_90 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository +import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository +import com.android.systemui.shared.recents.model.ThumbnailData +import com.android.systemui.shared.recents.utilities.PreviewPositionHelper +import com.android.systemui.shared.recents.utilities.PreviewPositionHelper.PreviewPositionHelperFactory +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Test for [GetThumbnailPositionUseCase] */ +@RunWith(AndroidJUnit4::class) +class GetThumbnailPositionUseCaseTest { + private val deviceProfileRepository = FakeRecentsDeviceProfileRepository() + private val rotationStateRepository = FakeRecentsRotationStateRepository() + private val previewPositionHelperFactoryMock = mock() + private val previewPositionHelper = mock() + + private val systemUnderTest = + GetThumbnailPositionUseCase( + deviceProfileRepository = deviceProfileRepository, + rotationStateRepository = rotationStateRepository, + previewPositionHelperFactory = previewPositionHelperFactoryMock, + ) + + @Before + fun setUp() { + whenever(previewPositionHelperFactoryMock.create()).thenReturn(previewPositionHelper) + } + + @Test + fun nullThumbnailData_returnsIdentityMatrix() = runTest { + val expectedResult = ThumbnailPosition(Matrix.IDENTITY_MATRIX, false) + val result = systemUnderTest.invoke(null, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true) + assertThat(result).isEqualTo(expectedResult) + } + + @Test + fun withoutThumbnail_returnsIdentityMatrix() = runTest { + val expectedResult = ThumbnailPosition(Matrix.IDENTITY_MATRIX, false) + val result = + systemUnderTest.invoke(ThumbnailData(), CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true) + assertThat(result).isEqualTo(expectedResult) + } + + @Test + fun visibleTaskWithThumbnailData_returnsTransformedMatrix() = runTest { + val isLargeScreen = true + deviceProfileRepository.setRecentsDeviceProfile( + deviceProfileRepository.getRecentsDeviceProfile().copy(isLargeScreen = isLargeScreen) + ) + val activityRotation = ROTATION_90 + rotationStateRepository.setRecentsRotationState( + rotationStateRepository + .getRecentsRotationState() + .copy(activityRotation = activityRotation) + ) + val isRtl = true + val isRotated = true + + whenever(previewPositionHelper.matrix).thenReturn(MATRIX) + whenever(previewPositionHelper.isOrientationChanged).thenReturn(isRotated) + + val result = systemUnderTest.invoke(THUMBNAIL_DATA, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl) + val expectedResult = ThumbnailPosition(MATRIX, isRotated) + assertThat(result).isEqualTo(expectedResult) + + verify(previewPositionHelper) + .updateThumbnailMatrix( + Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT), + THUMBNAIL_DATA, + CANVAS_WIDTH, + CANVAS_HEIGHT, + isLargeScreen, + activityRotation, + isRtl, + ) + } + + @Test + fun multipleInvocations_usesPreviewPositionHelperFactoryEachTime() = runTest { + whenever(previewPositionHelper.matrix).thenReturn(MATRIX) + + val sut = + GetThumbnailPositionUseCase( + deviceProfileRepository = deviceProfileRepository, + rotationStateRepository = rotationStateRepository, + previewPositionHelperFactory = previewPositionHelperFactoryMock, + ) + verify(previewPositionHelperFactoryMock, times(0)).create() + + sut.invoke(THUMBNAIL_DATA, CANVAS_WIDTH, CANVAS_HEIGHT, /* isRtl= */ true) + sut.invoke(THUMBNAIL_DATA, CANVAS_WIDTH, CANVAS_HEIGHT, /* isRtl= */ false) + + // Each invocation of use case should use a fresh position helper acquired by the factory. + verify(previewPositionHelperFactoryMock, times(2)).create() + } + + private companion object { + const val THUMBNAIL_WIDTH = 100 + const val THUMBNAIL_HEIGHT = 200 + const val CANVAS_WIDTH = 300 + const val CANVAS_HEIGHT = 600 + val MATRIX = + Matrix().apply { + setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f)) + } + + val THUMBNAIL_DATA = + ThumbnailData( + thumbnail = + mock().apply { + whenever(width).thenReturn(THUMBNAIL_WIDTH) + whenever(height).thenReturn(THUMBNAIL_HEIGHT) + } + ) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt new file mode 100644 index 00000000000..e8bca937b23 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.recents.domain.usecase + +import android.graphics.Bitmap +import android.view.Surface +import android.view.Surface.ROTATION_90 +import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class IsThumbnailValidUseCaseTest { + private val recentsRotationStateRepository = FakeRecentsRotationStateRepository() + private val systemUnderTest = IsThumbnailValidUseCase(recentsRotationStateRepository) + + @Test + fun withNullThumbnail_returnsInvalid() = runTest { + val isThumbnailValid = systemUnderTest(thumbnailData = null, viewWidth = 0, viewHeight = 0) + assertThat(isThumbnailValid).isEqualTo(false) + } + + @Test + fun sameAspectRatio_sameRotation_returnsValid() = runTest { + val isThumbnailValid = + systemUnderTest.invoke( + thumbnailData = createThumbnailData(), + viewWidth = THUMBNAIL_WIDTH * 2, + viewHeight = THUMBNAIL_HEIGHT * 2, + ) + assertThat(isThumbnailValid).isEqualTo(true) + } + + @Test + fun differentAspectRatio_sameRotation_returnsInvalid() = runTest { + val isThumbnailValid = + systemUnderTest.invoke( + thumbnailData = createThumbnailData(), + viewWidth = THUMBNAIL_WIDTH, + viewHeight = THUMBNAIL_HEIGHT * 2, + ) + assertThat(isThumbnailValid).isEqualTo(false) + } + + @Test + fun sameAspectRatio_differentRotation_returnsInvalid() = runTest { + val isThumbnailValid = + systemUnderTest.invoke( + thumbnailData = createThumbnailData(rotation = ROTATION_90), + viewWidth = THUMBNAIL_WIDTH * 2, + viewHeight = THUMBNAIL_HEIGHT * 2, + ) + assertThat(isThumbnailValid).isEqualTo(false) + } + + @Test + fun differentAspectRatio_differentRotation_returnsInvalid() = runTest { + val isThumbnailValid = + systemUnderTest.invoke( + thumbnailData = createThumbnailData(rotation = ROTATION_90), + viewWidth = THUMBNAIL_WIDTH, + viewHeight = THUMBNAIL_HEIGHT * 2, + ) + assertThat(isThumbnailValid).isEqualTo(false) + } + + private fun createThumbnailData( + rotation: Int = Surface.ROTATION_0, + width: Int = THUMBNAIL_WIDTH, + height: Int = THUMBNAIL_HEIGHT, + ): ThumbnailData { + val bitmap = mock() + whenever(bitmap.width).thenReturn(width) + whenever(bitmap.height).thenReturn(height) + return ThumbnailData(thumbnail = bitmap, rotation = rotation) + } + + companion object { + const val THUMBNAIL_WIDTH = 100 + const val THUMBNAIL_HEIGHT = 200 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt new file mode 100644 index 00000000000..7ca194afcf0 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.recents.ui.mapper + +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.ShapeDrawable +import android.platform.test.annotations.EnableFlags +import android.view.Surface +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.Flags +import com.android.quickstep.recents.ui.viewmodel.TaskData +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TaskUiStateMapperTest { + + @Test + fun taskData_isNull_returns_Uninitialized() { + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = null, + isLiveTile = false, + hasHeader = false, + clickCloseListener = null, + ) + assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized) + } + + @Test + fun taskData_isLiveTile_returns_LiveTile() { + val inputs = + listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true)) + inputs.forEach { input -> + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = input, + isLiveTile = true, + hasHeader = false, + clickCloseListener = null, + ) + assertThat(result).isEqualTo(LiveTile.WithoutHeader) + } + } + + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW) + @Test + fun taskData_isLiveTileWithHeader_returns_LiveTileWithHeader() { + val inputs = + listOf( + TASK_DATA, + TASK_DATA.copy(thumbnailData = null), + TASK_DATA.copy(isLocked = true), + TASK_DATA.copy(title = null), + ) + val closeCallback = View.OnClickListener {} + val expected = + LiveTile.WithHeader( + header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback) + ) + inputs.forEach { taskData -> + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = taskData, + isLiveTile = true, + hasHeader = true, + clickCloseListener = closeCallback, + ) + assertThat(result).isEqualTo(expected) + } + } + + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW) + @Test + fun taskData_isLiveTileWithHeader_missingHeaderData_returns_LiveTileWithoutHeader() { + val inputs = + listOf( + TASK_DATA.copy(icon = null), + TASK_DATA.copy(titleDescription = null), + TASK_DATA.copy(icon = null, titleDescription = null), + ) + + inputs.forEach { taskData -> + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = taskData, + isLiveTile = true, + hasHeader = true, + clickCloseListener = {}, + ) + assertThat(result).isEqualTo(LiveTile.WithoutHeader) + } + } + + @Test + fun taskData_isStaticTile_returns_SnapshotSplash() { + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = TASK_DATA, + isLiveTile = false, + hasHeader = false, + clickCloseListener = null, + ) + + val expected = + TaskThumbnailUiState.SnapshotSplash( + snapshot = + Snapshot.WithoutHeader( + backgroundColor = TASK_BACKGROUND_COLOR, + bitmap = TASK_THUMBNAIL, + thumbnailRotation = Surface.ROTATION_0, + ), + splash = TASK_ICON, + ) + + assertThat(result).isEqualTo(expected) + } + + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW) + @Test + fun taskData_isStaticTile_withHeader_returns_SnapshotSplashWithHeader() { + val inputs = listOf(TASK_DATA, TASK_DATA.copy(title = null)) + val closeCallback = View.OnClickListener {} + val expected = + TaskThumbnailUiState.SnapshotSplash( + snapshot = + Snapshot.WithHeader( + backgroundColor = TASK_BACKGROUND_COLOR, + bitmap = TASK_THUMBNAIL, + thumbnailRotation = Surface.ROTATION_0, + header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback), + ), + splash = TASK_ICON, + ) + inputs.forEach { taskData -> + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = taskData, + isLiveTile = false, + hasHeader = true, + clickCloseListener = closeCallback, + ) + assertThat(result).isEqualTo(expected) + } + } + + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW) + @Test + fun taskData_isStaticTile_missingHeaderData_returns_SnapshotSplashWithoutHeader() { + val inputs = + listOf( + TASK_DATA.copy(titleDescription = null, icon = null), + TASK_DATA.copy(titleDescription = null), + TASK_DATA.copy(icon = null), + ) + val expected = + Snapshot.WithoutHeader( + backgroundColor = TASK_BACKGROUND_COLOR, + thumbnailRotation = Surface.ROTATION_0, + bitmap = TASK_THUMBNAIL, + ) + inputs.forEach { taskData -> + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = taskData, + isLiveTile = false, + hasHeader = true, + clickCloseListener = {}, + ) + + assertThat(result).isInstanceOf(TaskThumbnailUiState.SnapshotSplash::class.java) + result as TaskThumbnailUiState.SnapshotSplash + assertThat(result.snapshot).isEqualTo(expected) + } + } + + @Test + fun taskData_thumbnailIsNull_returns_BackgroundOnly() { + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = TASK_DATA.copy(thumbnailData = null), + isLiveTile = false, + hasHeader = false, + clickCloseListener = null, + ) + + val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR) + assertThat(result).isEqualTo(expected) + } + + @Test + fun taskData_isLocked_returns_BackgroundOnly() { + val result = + TaskUiStateMapper.toTaskThumbnailUiState( + taskData = TASK_DATA.copy(isLocked = true), + isLiveTile = false, + hasHeader = false, + clickCloseListener = null, + ) + + val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR) + assertThat(result).isEqualTo(expected) + } + + private companion object { + const val TASK_TITLE_DESCRIPTION = "Title Description 1" + var TASK_ID = 1 + val TASK_ICON = ShapeDrawable() + val TASK_THUMBNAIL = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + val TASK_THUMBNAIL_DATA = + ThumbnailData(thumbnail = TASK_THUMBNAIL, rotation = Surface.ROTATION_0) + val TASK_BACKGROUND_COLOR = Color.rgb(1, 2, 3) + val TASK_DATA = + TaskData.Data( + TASK_ID, + title = "Task 1", + titleDescription = TASK_TITLE_DESCRIPTION, + icon = TASK_ICON, + thumbnailData = TASK_THUMBNAIL_DATA, + backgroundColor = TASK_BACKGROUND_COLOR, + isLocked = false, + ) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt new file mode 100644 index 00000000000..333c2856e6d --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.recents.ui.viewmodel + +import android.graphics.Color +import android.graphics.drawable.ShapeDrawable +import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS +import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS +import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV +import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS +import com.android.launcher3.util.TestDispatcherProvider +import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository +import com.android.quickstep.recents.domain.model.TaskModel +import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase +import com.android.quickstep.recents.domain.usecase.GetTaskUseCase +import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase +import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase +import com.android.quickstep.recents.viewmodel.RecentsViewData +import com.android.quickstep.views.TaskViewType +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class TaskViewModelTest { + private val unconfinedTestDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(unconfinedTestDispatcher) + + private val recentsViewData = RecentsViewData() + private val getTaskUseCase = mock() + private val getThumbnailPositionUseCase = mock() + private val isThumbnailValidUseCase = + spy(IsThumbnailValidUseCase(FakeRecentsRotationStateRepository())) + private lateinit var sut: TaskViewModel + + @Before + fun setUp() { + sut = createTaskViewModel(TaskViewType.SINGLE) + whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) }) + whenever(getTaskUseCase.invoke(TASK_MODEL_2.id)).thenReturn(flow { emit(TASK_MODEL_2) }) + whenever(getTaskUseCase.invoke(TASK_MODEL_3.id)).thenReturn(flow { emit(TASK_MODEL_3) }) + whenever(getTaskUseCase.invoke(INVALID_TASK_ID)).thenReturn(flow { emit(null) }) + recentsViewData.runningTaskIds.value = emptySet() + } + + @Test + fun singleTaskRetrieved_when_validTaskId() = + testScope.runTest { + sut.bind(TASK_MODEL_1.id) + val expectedResult = + TaskTileUiState( + tasks = listOf(TASK_MODEL_1.toUiState()), + isLiveTile = false, + hasHeader = false, + sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME, + taskOverlayEnabled = false, + isCentralTask = false, + ) + assertThat(sut.state.first()).isEqualTo(expectedResult) + } + + @Test + fun hasHeader_when_taskViewTypeIsDesktop() = + testScope.runTest { + val expectedResults = + mapOf( + TaskViewType.SINGLE to false, + TaskViewType.GROUPED to false, + TaskViewType.DESKTOP to true, + ) + + expectedResults.forEach { (type, expectedResult) -> + sut = + TaskViewModel( + taskViewType = type, + recentsViewData = recentsViewData, + getTaskUseCase = getTaskUseCase, + getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(), + isThumbnailValidUseCase = isThumbnailValidUseCase, + getThumbnailPositionUseCase = getThumbnailPositionUseCase, + dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher), + ) + sut.bind(TASK_MODEL_1.id) + assertThat(sut.state.first().hasHeader).isEqualTo(expectedResult) + } + } + + @Test + fun multipleTasksRetrieved_when_validTaskIds() = + testScope.runTest { + sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id, INVALID_TASK_ID) + val expectedResult = + TaskTileUiState( + tasks = + listOf( + TASK_MODEL_1.toUiState(), + TASK_MODEL_2.toUiState(), + TASK_MODEL_3.toUiState(), + TaskData.NoData(INVALID_TASK_ID), + ), + isLiveTile = false, + hasHeader = false, + sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME, + taskOverlayEnabled = false, + isCentralTask = false, + ) + assertThat(sut.state.first()).isEqualTo(expectedResult) + } + + @Test + fun isLiveTile_when_runningTasksMatchTasks() = + testScope.runTest { + recentsViewData.runningTaskShowScreenshot.value = false + recentsViewData.runningTaskIds.value = + setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id) + sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id) + val expectedResult = + TaskTileUiState( + tasks = + listOf( + TASK_MODEL_1.toUiState(), + TASK_MODEL_2.toUiState(), + TASK_MODEL_3.toUiState(), + ), + isLiveTile = true, + hasHeader = false, + sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME, + taskOverlayEnabled = false, + isCentralTask = false, + ) + assertThat(sut.state.first()).isEqualTo(expectedResult) + } + + @Test + fun isNotLiveTile_when_runningTaskShowScreenshotIsTrue() = + testScope.runTest { + recentsViewData.runningTaskShowScreenshot.value = true + recentsViewData.runningTaskIds.value = + setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id) + sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id) + val expectedResult = + TaskTileUiState( + tasks = + listOf( + TASK_MODEL_1.toUiState(), + TASK_MODEL_2.toUiState(), + TASK_MODEL_3.toUiState(), + ), + isLiveTile = false, + hasHeader = false, + sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME, + taskOverlayEnabled = false, + isCentralTask = false, + ) + assertThat(sut.state.first()).isEqualTo(expectedResult) + } + + @Test + fun isNotLiveTile_when_runningTasksMatchPartialTasks_lessRunningTasks() = + testScope.runTest { + recentsViewData.runningTaskShowScreenshot.value = false + recentsViewData.runningTaskIds.value = setOf(TASK_MODEL_1.id, TASK_MODEL_2.id) + sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id) + val expectedResult = + TaskTileUiState( + tasks = + listOf( + TASK_MODEL_1.toUiState(), + TASK_MODEL_2.toUiState(), + TASK_MODEL_3.toUiState(), + ), + isLiveTile = false, + hasHeader = false, + sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME, + taskOverlayEnabled = false, + isCentralTask = false, + ) + assertThat(sut.state.first()).isEqualTo(expectedResult) + } + + @Test + fun isNotLiveTile_when_runningTasksMatchPartialTasks_moreRunningTasks() = + testScope.runTest { + recentsViewData.runningTaskShowScreenshot.value = false + recentsViewData.runningTaskIds.value = + setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id) + sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id) + val expectedResult = + TaskTileUiState( + tasks = listOf(TASK_MODEL_1.toUiState(), TASK_MODEL_2.toUiState()), + isLiveTile = false, + hasHeader = false, + sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME, + taskOverlayEnabled = false, + isCentralTask = false, + ) + assertThat(sut.state.first()).isEqualTo(expectedResult) + } + + @Test + fun noDataAvailable_when_InvalidTaskId() = + testScope.runTest { + sut.bind(INVALID_TASK_ID) + val expectedResult = + TaskTileUiState( + listOf(TaskData.NoData(INVALID_TASK_ID)), + isLiveTile = false, + hasHeader = false, + sysUiStatusNavFlags = FLAGS_APPEARANCE_DEFAULT, + taskOverlayEnabled = false, + isCentralTask = false, + ) + assertThat(sut.state.first()).isEqualTo(expectedResult) + } + + @Test + fun taskOverlayEnabled_when_OverlayIsEnabledForVisibleSingleTask() = + testScope.runTest { + sut.bind(TASK_MODEL_1.id) + recentsViewData.overlayEnabled.value = true + recentsViewData.settledFullyVisibleTaskIds.value = setOf(1) + + assertThat(sut.state.first().taskOverlayEnabled).isTrue() + } + + @Test + fun taskOverlayDisabled_when_OverlayIsEnabledForInvisibleTask() = + testScope.runTest { + sut.bind(TASK_MODEL_1.id) + recentsViewData.overlayEnabled.value = true + recentsViewData.settledFullyVisibleTaskIds.value = setOf(2) + + assertThat(sut.state.first().taskOverlayEnabled).isFalse() + } + + @Test + fun taskOverlayDisabled_when_OverlayIsDisabledForVisibleTask() = + testScope.runTest { + sut.bind(TASK_MODEL_1.id) + recentsViewData.overlayEnabled.value = false + recentsViewData.settledFullyVisibleTaskIds.value = setOf(1) + + assertThat(sut.state.first().taskOverlayEnabled).isFalse() + } + + @Test + fun isCentralTask_when_CentralTaskIdsMatchTaskIds() = + testScope.runTest { + sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id) + recentsViewData.centralTaskIds.value = setOf(TASK_MODEL_1.id, TASK_MODEL_2.id) + + assertThat(sut.state.first().isCentralTask).isTrue() + } + + @Test + fun isNotCentralTask_when_CentralTaskIdsDoMatchTaskIds() = + testScope.runTest { + sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id) + recentsViewData.centralTaskIds.value = setOf(TASK_MODEL_3.id) + + assertThat(sut.state.first().isCentralTask).isFalse() + } + + @Test + fun shouldShowSplash_calls_useCase() { + sut.isThumbnailValid(null, 0, 0) + verify(isThumbnailValidUseCase).invoke(anyOrNull(), anyInt(), anyInt()) + } + + private fun TaskModel.toUiState() = + TaskData.Data( + taskId = id, + title = title, + titleDescription = titleDescription, + icon = icon!!, + thumbnailData = thumbnail, + backgroundColor = backgroundColor, + isLocked = isLocked, + ) + + private fun createTaskViewModel(taskViewType: TaskViewType) = + TaskViewModel( + taskViewType = taskViewType, + recentsViewData = recentsViewData, + getTaskUseCase = getTaskUseCase, + getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(), + isThumbnailValidUseCase = isThumbnailValidUseCase, + getThumbnailPositionUseCase = getThumbnailPositionUseCase, + dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher), + ) + + private companion object { + const val INVALID_TASK_ID = -1 + const val FLAGS_APPEARANCE_LIGHT_THEME = FLAG_LIGHT_STATUS or FLAG_LIGHT_NAV + const val FLAGS_APPEARANCE_DEFAULT = 0 + const val APPEARANCE_LIGHT_THEME = + APPEARANCE_LIGHT_CAPTION_BARS or + APPEARANCE_LIGHT_STATUS_BARS or + APPEARANCE_LIGHT_NAVIGATION_BARS + + val TASK_MODEL_1 = + TaskModel( + 1, + "Title 1", + "Content Description 1", + ShapeDrawable(), + ThumbnailData(appearance = APPEARANCE_LIGHT_THEME), + Color.BLACK, + false, + ) + val TASK_MODEL_2 = + TaskModel( + 2, + "Title 2", + "Content Description 2", + ShapeDrawable(), + ThumbnailData(appearance = APPEARANCE_LIGHT_THEME), + Color.RED, + true, + ) + val TASK_MODEL_3 = + TaskModel( + 3, + "Title 3", + "Content Description 3", + ShapeDrawable(), + ThumbnailData(appearance = APPEARANCE_LIGHT_THEME), + Color.BLUE, + false, + ) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt new file mode 100644 index 00000000000..a32e07dedf1 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt @@ -0,0 +1,138 @@ +/* + * 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 android.content.ComponentName +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.view.Surface +import com.android.quickstep.recents.data.FakeTasksRepository +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class RecentsViewModelTest { + private val tasksRepository = FakeTasksRepository() + private val recentsViewData = RecentsViewData() + private val systemUnderTest = RecentsViewModel(tasksRepository, recentsViewData) + + private val tasks = (0..5).map(::createTaskWithId) + + @Test + fun taskVisibilityControlThumbnailsAvailability() = runTest { + val thumbnailData1 = createThumbnailData() + val thumbnailData2 = createThumbnailData() + tasksRepository.seedTasks(tasks) + tasksRepository.seedThumbnailData(mapOf(1 to thumbnailData1, 2 to thumbnailData2)) + + val thumbnailDataFlow1 = tasksRepository.getThumbnailById(1) + val thumbnailDataFlow2 = tasksRepository.getThumbnailById(2) + + systemUnderTest.refreshAllTaskData() + + assertThat(thumbnailDataFlow1.first()).isNull() + assertThat(thumbnailDataFlow2.first()).isNull() + + systemUnderTest.updateVisibleTasks(listOf(1, 2)) + + assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1) + assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2) + + systemUnderTest.updateVisibleTasks(listOf(1)) + + assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1) + assertThat(thumbnailDataFlow2.first()).isNull() + + systemUnderTest.onReset() + + assertThat(thumbnailDataFlow1.first()).isNull() + assertThat(thumbnailDataFlow2.first()).isNull() + } + + @Test + fun updatesRunningTaskShowScreenshot() = runTest { + systemUnderTest.setRunningTaskShowScreenshot(true) + systemUnderTest.waitForRunningTaskShowScreenshotToUpdate() + } + + @Test + fun waitForThumbnailsToUpdate() = runTest { + // Given taskRepository with visible 2 tasks containing thumbnailData + val thumbnailData1 = createThumbnailData().apply { snapshotId = 1 } + val thumbnailData2 = createThumbnailData().apply { snapshotId = 2 } + tasksRepository.seedTasks(tasks) + tasksRepository.seedThumbnailData(mapOf(1 to thumbnailData1, 2 to thumbnailData2)) + systemUnderTest.updateVisibleTasks(listOf(1, 2)) + + val thumbnailDataFlow1 = tasksRepository.getThumbnailById(1) + val thumbnailDataFlow2 = tasksRepository.getThumbnailById(2) + + // Then getThumbnailById should initially contains correct thumbnailData + assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1) + assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2) + + // When thumbnailData is updated in taskRepository + tasksRepository.seedThumbnailData( + mapOf(1 to thumbnailData1, 2 to createThumbnailData().apply { snapshotId = 3 }) + ) + // setVisibleTasks forces FakeTasksRepository to update the flows returned by + // getThumbnailById + tasksRepository.setVisibleTasks(setOf(1, 2)) + + // Then wait for thumbnailData should complete, and the previous getThumbnailById flow + // should return updated values + systemUnderTest.waitForThumbnailsToUpdate( + mapOf(2 to createThumbnailData().apply { snapshotId = 3 }) + ) + assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1) + assertThat(thumbnailDataFlow2.first()?.snapshotId).isEqualTo(3) + } + + @Test + fun waitForThumbnailsToUpdate_emptyMap() = runTest { + systemUnderTest.waitForThumbnailsToUpdate(emptyMap()) + } + + @Test + fun waitForThumbnailsToUpdate_null() = runTest { + systemUnderTest.waitForThumbnailsToUpdate(null) + } + + 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(rotation: Int = Surface.ROTATION_0): ThumbnailData { + val bitmap = mock() + whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH) + whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT) + + return ThumbnailData(thumbnail = bitmap, rotation = rotation) + } + + companion object { + const val THUMBNAIL_WIDTH = 100 + const val THUMBNAIL_HEIGHT = 200 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt new file mode 100644 index 00000000000..01196796818 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt @@ -0,0 +1,118 @@ +/* + * 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.window + +import android.graphics.Point +import android.hardware.display.DisplayManager +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.Display +import android.view.DisplayAdjustments +import android.view.Surface +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW +import com.android.launcher3.Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.window.CachedDisplayInfo +import com.android.quickstep.fallback.window.RecentsDisplayModel +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW) +class RecentsDisplayModelTest { + @get:Rule val setFlagsRule = SetFlagsRule() + + // initiate dagger components for injection + private val launcherModelHelper = LauncherModelHelper() + private val context = spy(launcherModelHelper.sandboxContext) + private val displayManager: DisplayManager = context.spyService(DisplayManager::class.java) + private val display: Display = mock() + + private lateinit var recentsDisplayModel: RecentsDisplayModel + + private val width = 2208 + private val height = 1840 + + @Before + fun setup() { + // Mock display + val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0) + whenever(display.rotation).thenReturn(displayInfo.rotation) + whenever(display.displayAdjustments).thenReturn(DisplayAdjustments()) + whenever(context.display).thenReturn(display) + + // Mock displayManager + whenever(displayManager.getDisplay(anyInt())).thenReturn(display) + + runOnMainSync { recentsDisplayModel = RecentsDisplayModel.INSTANCE.get(context) } + } + + @Test + fun testEnsureSingleton() { + val recentsDisplayModel2 = RecentsDisplayModel.INSTANCE.get(context) + assert(recentsDisplayModel == recentsDisplayModel2) + } + + @Test + fun testDefaultDisplayCreation() { + Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)) + Assert.assertNotNull( + recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY) + ) + } + + @Test + fun testCreateSeparateInstances() { + val displayId = Display.DEFAULT_DISPLAY + 1 + runOnMainSync { recentsDisplayModel.storeDisplayResource(displayId) } + + val defaultManager = recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY) + val secondaryManager = recentsDisplayModel.getRecentsWindowManager(displayId) + Assert.assertNotSame(defaultManager, secondaryManager) + + val defaultInterface = + recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY) + val secondInterface = recentsDisplayModel.getFallbackWindowInterface(displayId) + Assert.assertNotSame(defaultInterface, secondInterface) + } + + @Test + fun testDestroy() { + Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)) + Assert.assertNotNull( + recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY) + ) + recentsDisplayModel.destroy() + Assert.assertNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)) + Assert.assertNull(recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)) + } + + private fun runOnMainSync(f: Runnable) { + InstrumentationRegistry.getInstrumentation().runOnMainSync { f.run() } + } +} 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 deleted file mode 100644 index 3b8754c200b..00000000000 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt +++ /dev/null @@ -1,178 +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.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() - 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/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt similarity index 94% rename from quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt rename to quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt index 4d10f0f51e4..26418d876ab 100644 --- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt @@ -55,11 +55,10 @@ class TaskbarPinningControllerTest : TaskbarBaseTestCase() { private val taskbarDragLayer = mock() private val taskbarSharedState = mock() private var isInDesktopMode = false - private val isInDesktopModeProvider = { isInDesktopMode } private val launcherPrefs = - mock { - on { get(TASKBAR_PINNING) } doReturn false - on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false + mock().apply { + doReturn(false).whenever(this).get(TASKBAR_PINNING) + doReturn(false).whenever(this).get(TASKBAR_PINNING_IN_DESKTOP_MODE) } private val statsLogger = mock() private val statsLogManager = mock { on { logger() } doReturn statsLogger } @@ -71,8 +70,13 @@ class TaskbarPinningControllerTest : TaskbarBaseTestCase() { whenever(taskbarActivityContext.launcherPrefs).thenReturn(launcherPrefs) whenever(taskbarActivityContext.dragLayer).thenReturn(taskbarDragLayer) whenever(taskbarActivityContext.statsLogManager).thenReturn(statsLogManager) - pinningController = - spy(TaskbarPinningController(taskbarActivityContext, isInDesktopModeProvider)) + whenever( + taskbarControllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview( + taskbarActivityContext.displayId + ) + ) + .thenAnswer { _ -> isInDesktopMode } + pinningController = spy(TaskbarPinningController(taskbarActivityContext)) pinningController.init(taskbarControllers, taskbarSharedState) } diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt similarity index 83% rename from quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt rename to quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt index b637e7d4cf4..d66197a6fba 100644 --- a/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt @@ -16,6 +16,7 @@ package com.android.quickstep.taskbar.customization +import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator import com.android.launcher3.taskbar.customization.TaskbarIconSpecs import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator @@ -32,15 +33,26 @@ import org.mockito.kotlin.whenever class TaskbarSpecsEvaluatorTest { private val taskbarFeatureEvaluator = mock() - private val taskbarSpecsEvaluator = spy(TaskbarSpecsEvaluator(taskbarFeatureEvaluator)) + private val taskbarActivityContext = mock() + private var taskbarSpecsEvaluator = + spy(TaskbarSpecsEvaluator(taskbarActivityContext, taskbarFeatureEvaluator, 0, 0)) @Test - fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumn() { + fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumnInLandscape() { doReturn(true).whenever(taskbarFeatureEvaluator).isTransient - assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5)) + doReturn(true).whenever(taskbarFeatureEvaluator).isLandscape + assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(4, 4)) .isEqualTo(TaskbarIconSpecs.iconSize52dp) } + @Test + fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumnInPortrait() { + doReturn(true).whenever(taskbarFeatureEvaluator).isTransient + doReturn(false).whenever(taskbarFeatureEvaluator).isLandscape + assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(4, 4)) + .isEqualTo(TaskbarIconSpecs.iconSize48dp) + } + @Test fun testGetIconSizeByGrid_whenTaskbarIsTransient_withInvalidRowAndColumn() { doReturn(true).whenever(taskbarFeatureEvaluator).isTransient diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt new file mode 100644 index 00000000000..b4c236e9964 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util + +import android.hardware.input.InputManager +import android.view.InputDevice +import android.view.InputDevice.SOURCE_MOUSE +import android.view.InputDevice.SOURCE_TOUCHPAD +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.launcher3.util.IntArray +import com.android.launcher3.util.SandboxApplication +import com.android.launcher3.util.TestUtil +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class ActiveTrackpadListTest { + + @get:Rule val context = SandboxApplication() + + private val inputDeviceIds = IntArray() + private lateinit var inputManager: InputManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + inputManager = context.spyService(InputManager::class.java) + doAnswer { inputDeviceIds.toArray() }.whenever(inputManager).inputDeviceIds + + doReturn(null).whenever(inputManager).getInputDevice(eq(1)) + doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD)) + .whenever(inputManager) + .getInputDevice(eq(2)) + doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD)) + .whenever(inputManager) + .getInputDevice(eq(3)) + doReturn(mockDevice(SOURCE_MOUSE)).whenever(inputManager).getInputDevice(eq(4)) + } + + @Test + fun `initialize correct devices`() { + inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4)) + + val list = ActiveTrackpadList(context) {} + assertEquals(2, list.size()) + assertTrue(list.contains(2)) + assertTrue(list.contains(3)) + } + + @Test + fun `update callback not called in constructor`() { + inputDeviceIds.addAll(IntArray.wrap(2, 3)) + + var updateCalled = false + val list = ActiveTrackpadList(context) { updateCalled = true } + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + + assertEquals(2, list.size()) + assertFalse(updateCalled) + } + + @Test + fun `update called on add only once`() { + var updateCalled = false + val list = ActiveTrackpadList(context) { updateCalled = true } + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + + assertFalse(updateCalled) + assertEquals(0, list.size()) + + list.onInputDeviceAdded(1) + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + assertFalse(updateCalled) + assertEquals(0, list.size()) + + list.onInputDeviceAdded(2) + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + assertTrue(updateCalled) + assertEquals(1, list.size()) + + updateCalled = false + list.onInputDeviceAdded(3) + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + assertFalse(updateCalled) + assertEquals(2, list.size()) + } + + @Test + fun `update called on remove only once`() { + var updateCalled = false + inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4)) + val list = ActiveTrackpadList(context) { updateCalled = true } + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + assertEquals(2, list.size()) + + list.onInputDeviceRemoved(2) + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + assertEquals(1, list.size()) + assertFalse(updateCalled) + + list.onInputDeviceRemoved(3) + TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + assertEquals(0, list.size()) + assertTrue(updateCalled) + } + + private fun mockDevice(sources: Int) = + mock(InputDevice::class.java).apply { doReturn(sources).whenever(this).sources } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt index ece67aff62c..c325af4d3e8 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt @@ -16,7 +16,10 @@ package com.android.quickstep.util -import android.content.Context +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.res.Resources +import android.view.Display +import android.view.Display.DEFAULT_DISPLAY import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.apppairs.AppPairIcon import com.android.launcher3.logging.StatsLogManager @@ -24,13 +27,15 @@ import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.taskbar.TaskbarActivityContext 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.views.ActivityContext import com.android.quickstep.TopTaskTracker import com.android.quickstep.TopTaskTracker.CachedTaskInfo import com.android.systemui.shared.recents.model.Task import com.android.systemui.shared.recents.model.Task.TaskKey -import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70 -import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50 -import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30 +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66 +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33 import java.util.function.Consumer import org.junit.Assert.assertEquals import org.junit.Before @@ -53,32 +58,34 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppPairsControllerTest { - @Mock lateinit var context: Context + @Mock lateinit var context: ActivityContext + @Mock lateinit var resources: Resources @Mock lateinit var splitSelectStateController: SplitSelectStateController @Mock lateinit var statsLogManager: StatsLogManager private lateinit var appPairsController: AppPairsController - private val left30: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_30_70) + private val left33: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_33_66) } private val left50: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_50_50) + appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_50_50) } - private val left70: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_70_30) + private val left66: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_66_33) } - private val right30: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_30_70) + private val right33: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_33_66) } private val right50: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_50_50) + appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_50_50) } - private val right70: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_70_30) + private val right66: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_66_33) } @Mock lateinit var mockAppPairIcon: AppPairIcon + @Mock lateinit var mockDisplay: Display @Mock lateinit var mockTaskbarActivityContext: TaskbarActivityContext @Mock lateinit var mockTopTaskTracker: TopTaskTracker @Mock lateinit var mockCachedTaskInfo: CachedTaskInfo @@ -101,38 +108,42 @@ class AppPairsControllerTest { // Stub methods on appPairsController so that they return mocks spyAppPairsController = spy(appPairsController) whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext) + whenever(mockAppPairIcon.display).thenReturn(mockDisplay) + whenever(mockDisplay.displayId).thenReturn(DEFAULT_DISPLAY) doReturn(mockTopTaskTracker).whenever(spyAppPairsController).topTaskTracker - whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo) + whenever(mockTopTaskTracker.getCachedTopTask(any(), any())).thenReturn(mockCachedTaskInfo) whenever(mockTask1.getKey()).thenReturn(mockTaskKey1) whenever(mockTask2.getKey()).thenReturn(mockTaskKey2) doNothing().whenever(spyAppPairsController).launchAppPair(any(), any()) doNothing() .whenever(spyAppPairsController) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + whenever(mockAppPairIcon.context.resources).thenReturn(resources) + whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(false) } @Test fun shouldEncodeRankCorrectly() { - assertEquals("left + 30-70 should encode as 0 (0b0)", 0, left30) + assertEquals("left + 33-66 should encode as 0 (0b0)", 0, left33) assertEquals("left + 50-50 should encode as 1 (0b1)", 1, left50) - assertEquals("left + 70-30 should encode as 2 (0b10)", 2, left70) + assertEquals("left + 66-33 should encode as 2 (0b10)", 2, left66) // See AppPairsController#BITMASK_SIZE and BITMASK_FOR_SNAP_POSITION for context - assertEquals("right + 30-70 should encode as 1 followed by 16 0s", 1 shl 16, right30) + assertEquals("right + 33-66 should encode as 1 followed by 16 0s", 1 shl 16, right33) assertEquals("right + 50-50 should encode as the above value + 1", (1 shl 16) + 1, right50) - assertEquals("right + 70-30 should encode as the above value + 2", (1 shl 16) + 2, right70) + assertEquals("right + 66-33 should encode as the above value + 2", (1 shl 16) + 2, right66) } @Test fun shouldDecodeRankCorrectly() { assertEquals( - "left + 30-70 should decode to left", + "left + 33-66 should decode to left", STAGE_POSITION_TOP_OR_LEFT, - AppPairsController.convertRankToStagePosition(left30), + AppPairsController.convertRankToStagePosition(left33), ) assertEquals( - "left + 30-70 should decode to 30-70", - SNAP_TO_30_70, - AppPairsController.convertRankToSnapPosition(left30), + "left + 33-66 should decode to 33-66", + SNAP_TO_2_33_66, + AppPairsController.convertRankToSnapPosition(left33), ) assertEquals( @@ -142,30 +153,30 @@ class AppPairsControllerTest { ) assertEquals( "left + 50-50 should decode to 50-50", - SNAP_TO_50_50, + SNAP_TO_2_50_50, AppPairsController.convertRankToSnapPosition(left50), ) assertEquals( - "left + 70-30 should decode to left", + "left + 66-33 should decode to left", STAGE_POSITION_TOP_OR_LEFT, - AppPairsController.convertRankToStagePosition(left70), + AppPairsController.convertRankToStagePosition(left66), ) assertEquals( - "left + 70-30 should decode to 70-30", - SNAP_TO_70_30, - AppPairsController.convertRankToSnapPosition(left70), + "left + 66-33 should decode to 66-33", + SNAP_TO_2_66_33, + AppPairsController.convertRankToSnapPosition(left66), ) assertEquals( - "right + 30-70 should decode to right", + "right + 33-66 should decode to right", STAGE_POSITION_BOTTOM_OR_RIGHT, - AppPairsController.convertRankToStagePosition(right30), + AppPairsController.convertRankToStagePosition(right33), ) assertEquals( - "right + 30-70 should decode to 30-70", - SNAP_TO_30_70, - AppPairsController.convertRankToSnapPosition(right30), + "right + 33-66 should decode to 33-66", + SNAP_TO_2_33_66, + AppPairsController.convertRankToSnapPosition(right33), ) assertEquals( @@ -175,19 +186,19 @@ class AppPairsControllerTest { ) assertEquals( "right + 50-50 should decode to 50-50", - SNAP_TO_50_50, + SNAP_TO_2_50_50, AppPairsController.convertRankToSnapPosition(right50), ) assertEquals( - "right + 70-30 should decode to right", + "right + 66-33 should decode to right", STAGE_POSITION_BOTTOM_OR_RIGHT, - AppPairsController.convertRankToStagePosition(right70), + AppPairsController.convertRankToStagePosition(right66), ) assertEquals( - "right + 70-30 should decode to 70-30", - SNAP_TO_70_30, - AppPairsController.convertRankToSnapPosition(right70), + "right + 66-33 should decode to 66-33", + SNAP_TO_2_66_33, + AppPairsController.convertRankToSnapPosition(right66), ) } @@ -202,7 +213,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -226,7 +237,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -250,7 +261,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -274,7 +285,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -298,7 +309,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -322,7 +333,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -341,12 +352,16 @@ class AppPairsControllerTest { whenever(mockTaskKey1.getId()).thenReturn(1) whenever(mockTaskKey2.getId()).thenReturn(2) // ... with app 1 already on screen - whenever(mockCachedTaskInfo.taskId).thenReturn(1) + if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { + whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(1))).thenReturn(true) + } else { + whenever(mockCachedTaskInfo.taskId).thenReturn(1) + } // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -365,12 +380,16 @@ class AppPairsControllerTest { whenever(mockTaskKey1.getId()).thenReturn(1) whenever(mockTaskKey2.getId()).thenReturn(2) // ... with app 2 already on screen - whenever(mockCachedTaskInfo.taskId).thenReturn(2) + if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { + whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(2))).thenReturn(true) + } else { + whenever(mockCachedTaskInfo.taskId).thenReturn(2) + } // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -383,18 +402,84 @@ class AppPairsControllerTest { .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } + @Test + fun handleAppPairLaunchInApp_freeformTask1IsOnScreen_shouldLaunchAppPair() { + whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true) + /// Test launching apps 1 and 2 from app pair + whenever(mockTaskKey1.getId()).thenReturn(1) + whenever(mockTaskKey2.getId()).thenReturn(2) + // Task 1 is in freeform windowing mode + mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM + // ... and app 1 is already on screen + if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { + whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(1))).thenReturn(true) + } else { + whenever(mockCachedTaskInfo.taskId).thenReturn(1) + } + + // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback + spyAppPairsController.handleAppPairLaunchInApp( + mockAppPairIcon, + listOf(mockItemInfo1, mockItemInfo2), + ) + verify(splitSelectStateController) + .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) + val callback: Consumer> = callbackCaptor.value + callback.accept(arrayOf(mockTask1, mockTask2)) + + // Verify that launchAppPair was called + verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) + verify(spyAppPairsController, never()) + .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + } + + @Test + fun handleAppPairLaunchInApp_freeformTask2IsOnScreen_shouldLaunchAppPair() { + whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true) + /// Test launching apps 1 and 2 from app pair + whenever(mockTaskKey1.getId()).thenReturn(1) + whenever(mockTaskKey2.getId()).thenReturn(2) + // Task 2 is in freeform windowing mode + mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM + // ... and app 2 is already on screen + if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { + whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(2))).thenReturn(true) + } else { + whenever(mockCachedTaskInfo.taskId).thenReturn(2) + } + + // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback + spyAppPairsController.handleAppPairLaunchInApp( + mockAppPairIcon, + listOf(mockItemInfo1, mockItemInfo2), + ) + verify(splitSelectStateController) + .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) + val callback: Consumer> = callbackCaptor.value + callback.accept(arrayOf(mockTask1, mockTask2)) + + // Verify that launchAppPair was called + verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) + verify(spyAppPairsController, never()) + .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + } + @Test fun handleAppPairLaunchInApp_shouldLaunchAppPairNormallyWhenUnrelatedSingleAppIsFullscreen() { // Test launching apps 1 and 2 from app pair whenever(mockTaskKey1.getId()).thenReturn(1) whenever(mockTaskKey2.getId()).thenReturn(2) // ... with app 3 already on screen - whenever(mockCachedTaskInfo.taskId).thenReturn(3) + if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { + whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(3))).thenReturn(true) + } else { + whenever(mockCachedTaskInfo.taskId).thenReturn(3) + } // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2) + listOf(mockItemInfo1, mockItemInfo2), ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/CachedEventDispatcherTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/CachedEventDispatcherTest.kt new file mode 100644 index 00000000000..296171b866b --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/CachedEventDispatcherTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util + +import android.os.SystemClock +import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import java.util.function.Consumer +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidJUnit4::class) +class CachedEventDispatcherTest { + + private lateinit var underTest: CachedEventDispatcher + + @Mock private lateinit var consumer: Consumer + @Captor private lateinit var motionEventCaptor: ArgumentCaptor + + private lateinit var motionEvent: MotionEvent + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + motionEvent = + MotionEvent.obtain( + 0L, + SystemClock.elapsedRealtime(), + MotionEvent.ACTION_DOWN, + 0f, + 0f, + 0, + ) + underTest = CachedEventDispatcher() + } + + @Test + fun testInitialState() { + assertFalse(underTest.hasConsumer()) + } + + @Test + fun dispatchMotionEvents() { + underTest.setConsumer(consumer) + + underTest.dispatchEvent(motionEvent) + + assertTrue(underTest.hasConsumer()) + verify(consumer).accept(motionEventCaptor.capture()) + assertTrue(isMotionEventSame(motionEventCaptor.value, motionEvent)) + } + + @Test + fun dispatchMotionEvents_after_settingConsumer() { + underTest.dispatchEvent(motionEvent) + + underTest.setConsumer(consumer) + + verify(consumer).accept(motionEventCaptor.capture()) + assertTrue(isMotionEventSame(motionEventCaptor.value, motionEvent)) + } + + @Test + fun clearConsumer_notDispatchToConsumer() { + underTest.setConsumer(consumer) + underTest.dispatchEvent(motionEvent) + reset(consumer) + + underTest.clearConsumer() + + assertFalse(underTest.hasConsumer()) + underTest.dispatchEvent(motionEvent) + verifyNoMoreInteractions(consumer) + } + + private fun isMotionEventSame(e1: MotionEvent, e2: MotionEvent): Boolean { + return e1.action == e2.action && + e1.eventTime == e2.eventTime && + e1.x == e2.x && + e1.y == e2.y + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java new file mode 100644 index 00000000000..61971b1fc86 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED; +import static com.android.quickstep.util.ContextualSearchInvoker.KEYGUARD_SHOWING_SYSUI_FLAGS; +import static com.android.quickstep.util.ContextualSearchInvoker.SHADE_EXPANDED_SYSUI_FLAGS; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.app.contextualsearch.ContextualSearchManager; +import android.content.Context; +import android.content.pm.PackageManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.logging.StatsLogManager; +import com.android.quickstep.BaseContainerInterface; +import com.android.quickstep.DeviceConfigWrapper; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TopTaskTracker; +import com.android.quickstep.orientation.RecentsPagedOrientationHandler; +import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.RecentsViewContainer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Robolectric unit tests for {@link ContextualSearchInvoker} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ContextualSearchInvokerTest { + + private static final int CONTEXTUAL_SEARCH_ENTRY_POINT = 123; + + private @Mock PackageManager mMockPackageManager; + private @Mock ContextualSearchStateManager mMockStateManager; + private @Mock TopTaskTracker mMockTopTaskTracker; + private @Mock SystemUiProxy mMockSystemUiProxy; + private @Mock StatsLogManager mMockStatsLogManager; + private @Mock StatsLogManager.StatsLogger mMockStatsLogger; + private @Mock ContextualSearchHapticManager mMockContextualSearchHapticManager; + private @Mock ContextualSearchManager mMockContextualSearchManager; + private @Mock BaseContainerInterface mMockContainerInterface; + private @Mock RecentsViewContainer mMockRecentsViewContainer; + private @Mock RecentsView mMockRecentsView; + private @Mock RecentsPagedOrientationHandler mMockOrientationHandler; + private ContextualSearchInvoker mContextualSearchInvoker; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockPackageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)).thenReturn(true); + Context context = spy(getApplicationContext()); + doReturn(mMockPackageManager).when(context).getPackageManager(); + when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(0L); + when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{}); + when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(true); + when(mMockStateManager.isContextualSearchSettingEnabled()).thenReturn(true); + when(mMockStatsLogManager.logger()).thenReturn(mMockStatsLogger); + when(mMockContainerInterface.getCreatedContainer()).thenReturn(mMockRecentsViewContainer); + when(mMockRecentsViewContainer.getOverviewPanel()).thenReturn(mMockRecentsView); + + mContextualSearchInvoker = spy(new ContextualSearchInvoker(context, mMockStateManager, + mMockTopTaskTracker, mMockSystemUiProxy, mMockStatsLogManager, + mMockContextualSearchHapticManager, mMockContextualSearchManager + )); + doReturn(mMockContainerInterface).when(mContextualSearchInvoker) + .getRecentsContainerInterface(); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_contextualSearchFeatureIsNotAvailable() { + when(mMockPackageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)).thenReturn(false); + + assertFalse("Expected invocation to fail when feature is unavailable", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_contextualSearchIntentIsAvailable() { + assertTrue("Expected invocation checks to succeed", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + verifyNoMoreInteractions(mMockStatsLogManager); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_contextualSearchIntentIsNotAvailable() { + when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(false); + + assertFalse("Expected invocation to fail when feature is unavailable", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_settingDisabled() { + when(mMockStateManager.isContextualSearchSettingEnabled()).thenReturn(false); + + assertFalse("Expected invocation checks to fail when setting is disabled", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_notificationShadeIsShowing() { + when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(SHADE_EXPANDED_SYSUI_FLAGS); + + assertFalse("Expected invocation checks to fail when notification shade is showing", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_keyguardIsShowing() { + when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn( + KEYGUARD_SHOWING_SYSUI_FLAGS); + + assertFalse("Expected invocation checks to fail when keyguard is showing", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_isInSplitScreen_disallowed() { + when(mMockStateManager.isInvocationAllowedInSplitscreen()).thenReturn(false); + when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{1, 2, 3}); + + assertFalse("Expected invocation checks to fail over split screen", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + // Attempt is logged regardless. + verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_isInSplitScreen_allowed() { + when(mMockStateManager.isInvocationAllowedInSplitscreen()).thenReturn(true); + when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{1, 2, 3}); + + assertTrue("Expected invocation checks to succeed over split screen", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + + // Attempt is logged regardless. + verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN); + } + + @Test + public void runContextualSearchInvocationChecksAndLogFailures_isFakeLandscape() { + when(mMockRecentsView.getPagedOrientationHandler()).thenReturn(mMockOrientationHandler); + when(mMockOrientationHandler.isLayoutNaturalToLauncher()).thenReturn(false); + assertFalse("Expect invocation checks to fail in fake landscape.", + mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()); + verifyNoMoreInteractions(mMockStatsLogManager); + } + + @Test + public void invokeContextualSearchUncheckedWithHaptic_cssIsAvailable_commitHapticEnabled() { + try (AutoCloseable flag = overrideSearchHapticCommitFlag(true)) { + assertTrue("Expected invocation unchecked to succeed", + mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic( + CONTEXTUAL_SEARCH_ENTRY_POINT)); + verify(mMockContextualSearchHapticManager).vibrateForSearch(); + verify(mMockContextualSearchManager).startContextualSearch( + CONTEXTUAL_SEARCH_ENTRY_POINT); + verifyNoMoreInteractions(mMockStatsLogManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void invokeContextualSearchUncheckedWithHaptic_cssIsAvailable_commitHapticDisabled() { + try (AutoCloseable flag = overrideSearchHapticCommitFlag(false)) { + assertTrue("Expected invocation unchecked to succeed", + mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic( + CONTEXTUAL_SEARCH_ENTRY_POINT)); + verify(mMockContextualSearchHapticManager, never()).vibrateForSearch(); + verify(mMockContextualSearchManager).startContextualSearch( + CONTEXTUAL_SEARCH_ENTRY_POINT); + verifyNoMoreInteractions(mMockStatsLogManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void invokeContextualSearchUncheckedWithHaptic_cssIsNotAvailable_commitHapticEnabled() { + when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(false); + + try (AutoCloseable flag = overrideSearchHapticCommitFlag(true)) { + // Still expect true since this method doesn't run the checks. + assertTrue("Expected invocation unchecked to succeed", + mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic( + CONTEXTUAL_SEARCH_ENTRY_POINT)); + // Still vibrate based on the flag. + verify(mMockContextualSearchHapticManager).vibrateForSearch(); + verify(mMockContextualSearchManager).startContextualSearch( + CONTEXTUAL_SEARCH_ENTRY_POINT); + verifyNoMoreInteractions(mMockStatsLogManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + @Test + public void invokeContextualSearchUncheckedWithHaptic_cssIsNotAvailable_commitHapticDisabled() { + when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(false); + + try (AutoCloseable flag = overrideSearchHapticCommitFlag(false)) { + // Still expect true since this method doesn't run the checks. + assertTrue("Expected ContextualSearch invocation unchecked to succeed", + mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic( + CONTEXTUAL_SEARCH_ENTRY_POINT)); + // Still don't vibrate based on the flag. + verify(mMockContextualSearchHapticManager, never()).vibrateForSearch(); + verify(mMockContextualSearchManager).startContextualSearch( + CONTEXTUAL_SEARCH_ENTRY_POINT); + verifyNoMoreInteractions(mMockStatsLogManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void invokeContextualSearchUncheckedWithHaptic_liveTile() { + when(mMockContainerInterface.isInLiveTileMode()).thenReturn(true); + ArgumentCaptor switchToScreenshotCaptor = ArgumentCaptor.forClass(Runnable.class); + ArgumentCaptor finishRecentsAnimationCaptor = + ArgumentCaptor.forClass(Runnable.class); + + assertTrue("Expected invocation unchecked to succeed", + mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic( + CONTEXTUAL_SEARCH_ENTRY_POINT)); + verify(mMockRecentsView).switchToScreenshot(switchToScreenshotCaptor.capture()); + switchToScreenshotCaptor.getValue().run(); + verify(mMockRecentsView).finishRecentsAnimation(anyBoolean(), anyBoolean(), + finishRecentsAnimationCaptor.capture()); + finishRecentsAnimationCaptor.getValue().run(); + verify(mMockContextualSearchManager).startContextualSearch(CONTEXTUAL_SEARCH_ENTRY_POINT); + verifyNoMoreInteractions(mMockStatsLogManager); + } + + @Test + public void invokeContextualSearchUncheckedWithHaptic_liveTile_failsToSwitchToScreenshot() { + when(mMockContainerInterface.isInLiveTileMode()).thenReturn(true); + ArgumentCaptor switchToScreenshotCaptor = ArgumentCaptor.forClass(Runnable.class); + ArgumentCaptor finishRecentsAnimationCaptor = + ArgumentCaptor.forClass(Runnable.class); + + assertTrue("Expected invocation unchecked to succeed", + mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic( + CONTEXTUAL_SEARCH_ENTRY_POINT)); + verify(mMockRecentsView).switchToScreenshot(switchToScreenshotCaptor.capture()); + + // Don't run switchToScreenshot's callback. Therefore, recents animation should not finish. + verify(mMockRecentsView, never()).finishRecentsAnimation(anyBoolean(), anyBoolean(), + finishRecentsAnimationCaptor.capture()); + // And ContextualSearch should not start. + verify(mMockContextualSearchManager, never()).startContextualSearch(anyInt()); + verifyNoMoreInteractions(mMockStatsLogManager); + } + + @Test + public void invokeContextualSearchUncheckedWithHaptic_liveTile_failsToFinishRecentsAnimation() { + when(mMockContainerInterface.isInLiveTileMode()).thenReturn(true); + ArgumentCaptor switchToScreenshotCaptor = ArgumentCaptor.forClass(Runnable.class); + ArgumentCaptor finishRecentsAnimationCaptor = + ArgumentCaptor.forClass(Runnable.class); + + assertTrue("Expected invocation unchecked to succeed", + mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic( + CONTEXTUAL_SEARCH_ENTRY_POINT)); + verify(mMockRecentsView).switchToScreenshot(switchToScreenshotCaptor.capture()); + switchToScreenshotCaptor.getValue().run(); + verify(mMockRecentsView).finishRecentsAnimation(anyBoolean(), anyBoolean(), + finishRecentsAnimationCaptor.capture()); + // Don't run finishRecentsAnimation's callback. Therefore ContextualSearch should not start. + verify(mMockContextualSearchManager, never()).startContextualSearch(anyInt()); + verifyNoMoreInteractions(mMockStatsLogManager); + } + + private AutoCloseable overrideSearchHapticCommitFlag(boolean value) { + return TestExtensions.overrideNavConfigFlag( + "ENABLE_SEARCH_HAPTIC_COMMIT", + value, + () -> DeviceConfigWrapper.get().getEnableSearchHapticCommit()); + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt new file mode 100644 index 00000000000..15da4d4deff --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt @@ -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.quickstep.util + +import android.content.ComponentName +import android.content.Intent +import android.view.Display.DEFAULT_DISPLAY +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.systemui.shared.recents.model.Task +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +class DesktopTaskTest { + + @Test + fun testDesktopTask_sameInstance_isEqual() { + val task = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1)) + assertThat(task).isEqualTo(task) + } + + @Test + fun testDesktopTask_identicalConstructor_isEqual() { + val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1)) + val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1)) + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testDesktopTask_copy_isEqual() { + val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1)) + val task2 = task1.copy() + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testDesktopTask_differentDeskIds_isNotEqual() { + val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1)) + val task2 = DesktopTask(deskId = 1, DEFAULT_DISPLAY, createTasks(1)) + assertThat(task1).isNotEqualTo(task2) + } + + @Test + fun testDesktopTask_differentTaskIds_isNotEqual() { + val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1)) + val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(2)) + assertThat(task1).isNotEqualTo(task2) + } + + @Test + fun testDesktopTask_differentLength_isNotEqual() { + val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1)) + val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1, 2)) + assertThat(task1).isNotEqualTo(task2) + } + + private fun createTasks(vararg ids: Int): List { + return ids.map { Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 0)) } + } +} diff --git a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt similarity index 90% rename from quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt index c190cfebab9..555e62bead8 100644 --- a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt @@ -18,11 +18,12 @@ package com.android.quickstep.util import android.graphics.Rect import android.graphics.Region -import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager +import androidx.test.annotation.UiThreadTest import androidx.test.filters.SmallTest import com.android.launcher3.util.Executors +import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.quickstep.util.GestureExclusionManager.ExclusionListener import org.junit.Before import org.junit.Test @@ -31,11 +32,12 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.reset import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions /** Unit test for [GestureExclusionManager]. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@UiThreadTest +@RunWith(LauncherMultivalentJUnit::class) class GestureExclusionManagerTest { @Mock private lateinit var windowManager: IWindowManager @@ -72,7 +74,7 @@ class GestureExclusionManagerTest { underTest.addListener(listener2) awaitTasksCompleted() - verifyZeroInteractions(windowManager) + verifyNoMoreInteractions(windowManager) } @Test @@ -98,7 +100,7 @@ class GestureExclusionManagerTest { underTest.removeListener(listener1) awaitTasksCompleted() - verifyZeroInteractions(windowManager) + verifyNoMoreInteractions(windowManager) } @Test @@ -119,12 +121,12 @@ class GestureExclusionManagerTest { awaitTasksCompleted() underTest.addListener(listener1) awaitTasksCompleted() - verifyZeroInteractions(listener1) + verifyNoMoreInteractions(listener1) underTest.addListener(listener2) awaitTasksCompleted() - verifyZeroInteractions(listener1) + verifyNoMoreInteractions(listener1) verify(listener2).onGestureExclusionChanged(r1, r2) } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt new file mode 100644 index 00000000000..9f491717c41 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util + +import android.content.ComponentName +import android.content.Intent +import android.graphics.Rect +import android.view.Display.DEFAULT_DISPLAY +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.systemui.shared.recents.model.Task +import com.android.wm.shell.shared.split.SplitScreenConstants +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +class GroupTaskTest { + + @Test + fun testGroupTask_sameInstance_isEqual() { + val task = SingleTask(createTask(1)) + assertThat(task).isEqualTo(task) + } + + @Test + fun testGroupTask_identicalConstructor_isEqual() { + val task1 = SingleTask(createTask(1)) + val task2 = SingleTask(createTask(1)) + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testGroupTask_copy_isEqual() { + val task1 = SingleTask(createTask(1)) + val task2 = task1.copy() + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testGroupTask_differentId_isNotEqual() { + val task1 = SingleTask(createTask(1)) + val task2 = SingleTask(createTask(2)) + assertThat(task1).isNotEqualTo(task2) + } + + @Test + fun testGroupTask_equalSplitTasks_isEqual() { + val splitBounds = + SplitConfigurationOptions.SplitBounds( + Rect(), + Rect(), + 1, + 2, + SplitScreenConstants.SNAP_TO_2_50_50, + ) + val task1 = SplitTask(createTask(1), createTask(2), splitBounds) + val task2 = SplitTask(createTask(1), createTask(2), splitBounds) + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testGroupTask_differentSplitTasks_isNotEqual() { + val splitBounds1 = + SplitConfigurationOptions.SplitBounds( + Rect(), + Rect(), + 1, + 2, + SplitScreenConstants.SNAP_TO_2_50_50, + ) + val splitBounds2 = + SplitConfigurationOptions.SplitBounds( + Rect(), + Rect(), + 1, + 2, + SplitScreenConstants.SNAP_TO_2_33_66, + ) + val task1 = SplitTask(createTask(1), createTask(2), splitBounds1) + val task2 = SplitTask(createTask(1), createTask(2), splitBounds2) + assertThat(task1).isNotEqualTo(task2) + } + + @Test + fun testGroupTask_differentType_isNotEqual() { + val task1 = SingleTask(createTask(1)) + val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, listOf(createTask(1))) + assertThat(task1).isNotEqualTo(task2) + } + + private fun createTask(id: Int): Task { + return Task(Task.TaskKey(id, 0, Intent(), ComponentName("", ""), 0, 0)) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt new file mode 100644 index 00000000000..3cdf6080591 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util + +import android.view.Surface +import android.view.Surface.ROTATION_0 +import android.view.Surface.ROTATION_180 +import android.view.Surface.ROTATION_90 +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.quickstep.FallbackActivityInterface +import com.android.quickstep.orientation.RecentsPagedOrientationHandler +import com.android.quickstep.orientation.RecentsPagedOrientationHandler.Companion.PORTRAIT +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +/** + * Test all possible inputs to RecentsOrientedState.updateHandler. It tests all possible + * combinations of rotations and relevant methods (two methods that return boolean values) but it + * only provides the expected result when the final rotation is different from ROTATION_0 for + * simplicity. So any case not shown in resultMap you can assume results in ROTATION_0. + */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class RecentOrientedStateHandlerTests { + + data class TestCase( + val recentsRotation: Int, + val displayRotation: Int, + val touchRotation: Int, + val isRotationAllowed: Boolean, + val isFixedLandscape: Boolean, + ) { + override fun toString(): String { + return "TestCase(recentsRotation=${Surface.rotationToString(recentsRotation)}, " + + "displayRotation=${Surface.rotationToString(displayRotation)}, " + + "touchRotation=${Surface.rotationToString(touchRotation)}, " + + "isRotationAllowed=$isRotationAllowed, " + + "isFixedLandscape=$isFixedLandscape)" + } + } + + private fun runTestCase(testCase: TestCase, expectedHandler: RecentsPagedOrientationHandler) { + val recentOrientedState = + spy( + RecentsOrientedState( + ApplicationProvider.getApplicationContext(), + FallbackActivityInterface.INSTANCE, + ) {} + ) + whenever(recentOrientedState.isRecentsActivityRotationAllowed).thenAnswer { + testCase.isRotationAllowed + } + whenever(recentOrientedState.isLauncherFixedLandscape).thenAnswer { + testCase.isFixedLandscape + } + + recentOrientedState.update(testCase.displayRotation, testCase.touchRotation) + val rotation = recentOrientedState.orientationHandler.rotation + assertWithMessage("$testCase to ${Surface.rotationToString(rotation)},") + .that(rotation) + .isEqualTo(expectedHandler.rotation) + } + + @Test + fun `test fixed landscape when device is portrait`() { + runTestCase( + TestCase( + recentsRotation = ROTATION_0, + displayRotation = -1, + touchRotation = ROTATION_0, + isRotationAllowed = false, + isFixedLandscape = true, + ), + PORTRAIT, + ) + } + + @Test + fun `test fixed landscape when device is landscape`() { + runTestCase( + TestCase( + recentsRotation = ROTATION_90, + displayRotation = -1, + touchRotation = ROTATION_0, + isRotationAllowed = false, + isFixedLandscape = true, + ), + PORTRAIT, + ) + } + + @Test + fun `test fixed landscape when device is seascape`() { + runTestCase( + TestCase( + recentsRotation = ROTATION_180, + displayRotation = -1, + touchRotation = ROTATION_0, + isRotationAllowed = false, + isFixedLandscape = true, + ), + PORTRAIT, + ) + } + + @Test + fun `test fixed landscape when device is portrait and display rotation is portrait`() { + runTestCase( + TestCase( + recentsRotation = ROTATION_0, + displayRotation = ROTATION_0, + touchRotation = ROTATION_0, + isRotationAllowed = false, + isFixedLandscape = true, + ), + PORTRAIT, + ) + } + + @Test + fun `test fixed landscape when device is landscape and display rotation is landscape `() { + runTestCase( + TestCase( + recentsRotation = ROTATION_90, + displayRotation = ROTATION_90, + touchRotation = ROTATION_0, + isRotationAllowed = false, + isFixedLandscape = true, + ), + PORTRAIT, + ) + } + + @Test + fun `test fixed landscape when device is seascape and display rotation is seascape`() { + runTestCase( + TestCase( + recentsRotation = ROTATION_180, + displayRotation = ROTATION_180, + touchRotation = ROTATION_0, + isRotationAllowed = false, + isFixedLandscape = true, + ), + PORTRAIT, + ) + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt index d40f8ab389a..c9d7e1dee9f 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt @@ -27,15 +27,15 @@ import android.view.View import android.window.TransitionInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.apppairs.AppPairIcon +import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.statehandlers.DepthController import com.android.launcher3.statemanager.StateManager import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.util.SplitConfigurationOptions import com.android.quickstep.views.GroupedTaskView import com.android.quickstep.views.IconView -import com.android.quickstep.views.TaskThumbnailViewDeprecated +import com.android.quickstep.views.TaskContainer import com.android.quickstep.views.TaskView -import com.android.quickstep.views.TaskView.TaskContainer import com.android.systemui.shared.recents.model.Task import org.junit.Assert.assertEquals import org.junit.Before @@ -59,7 +59,7 @@ class SplitAnimationControllerTest { private val mockSplitSelectStateController: SplitSelectStateController = mock() // TaskView private val mockTaskView: TaskView = mock() - private val mockThumbnailView: TaskThumbnailViewDeprecated = mock() + private val mockSnapshotView: View = mock() private val mockBitmap: Bitmap = mock() private val mockIconView: IconView = mock() private val mockTaskViewDrawable: Drawable = mock() @@ -77,6 +77,7 @@ class SplitAnimationControllerTest { private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock() private val mockSplitSourceDrawable: Drawable = mock() private val mockSplitSourceView: View = mock() + private val mockItemInfo: ItemInfo = mock() private val stateManager: StateManager<*, *> = mock() private val depthController: DepthController = mock() @@ -87,14 +88,17 @@ class SplitAnimationControllerTest { @Before fun setup() { - whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView) - whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap) + whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView) + whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap) whenever(mockTaskContainer.iconView).thenReturn(mockIconView) + whenever(mockTaskContainer.task).thenReturn(mockTask) whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable) whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer }) + whenever(mockTaskView.firstTaskContainer).thenReturn(mockTaskContainer) whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable) whenever(splitSelectSource.view).thenReturn(mockSplitSourceView) + whenever(splitSelectSource.itemInfo).thenReturn(mockItemInfo) splitAnimationController = SplitAnimationController(mockSplitSelectStateController) } @@ -114,7 +118,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not fallback to use splitSource icon drawable", mockSplitSourceDrawable, - splitAnimInitProps.iconDrawable + splitAnimInitProps.iconDrawable, ) } @@ -130,7 +134,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not use taskView icon drawable", mockTaskViewDrawable, - splitAnimInitProps.iconDrawable + splitAnimInitProps.iconDrawable, ) } @@ -149,7 +153,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not use taskView icon drawable", mockTaskViewDrawable, - splitAnimInitProps.iconDrawable + splitAnimInitProps.iconDrawable, ) } @@ -165,7 +169,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not use splitSource icon drawable", mockSplitSourceDrawable, - splitAnimInitProps.iconDrawable + splitAnimInitProps.iconDrawable, ) } @@ -180,7 +184,6 @@ class SplitAnimationControllerTest { 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) @@ -188,13 +191,13 @@ class SplitAnimationControllerTest { val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = splitAnimationController.getFirstAnimInitViews( { mockGroupedTaskView }, - { splitSelectSource } + { splitSelectSource }, ) assertEquals( "Did not use splitSource icon drawable", mockSplitSourceDrawable, - splitAnimInitProps.iconDrawable + splitAnimInitProps.iconDrawable, ) } @@ -212,7 +215,7 @@ class SplitAnimationControllerTest { any(), any(), any(), - any() + any(), ) spySplitAnimationController.playSplitLaunchAnimation( @@ -227,7 +230,8 @@ class SplitAnimationControllerTest { depthController, null /* info */, null /* t */, - {} /* finishCallback */ + {} /* finishCallback */, + 1f, /* cornerRadius */ ) verify(spySplitAnimationController) @@ -240,7 +244,7 @@ class SplitAnimationControllerTest { any(), any(), any(), - any() + any(), ) } @@ -263,7 +267,8 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */ + {} /* finishCallback */, + 1f, /* cornerRadius */ ) verify(spySplitAnimationController) @@ -276,7 +281,7 @@ class SplitAnimationControllerTest { whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper) doNothing() .whenever(spySplitAnimationController) - .composeIconSplitLaunchAnimator(any(), any(), any(), any()) + .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any()) doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any()) spySplitAnimationController.playSplitLaunchAnimation( @@ -291,11 +296,12 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */ + {} /* finishCallback */, + 1f, /* cornerRadius */ ) verify(spySplitAnimationController) - .composeIconSplitLaunchAnimator(any(), any(), any(), any()) + .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any()) } @Test @@ -319,7 +325,8 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */ + {} /* finishCallback */, + 1f, /* cornerRadius */ ) verify(spySplitAnimationController) @@ -346,7 +353,8 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */ + {} /* finishCallback */, + 1f, /* cornerRadius */ ) verify(spySplitAnimationController) @@ -373,7 +381,8 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */ + {} /* finishCallback */, + 1f, /* cornerRadius */ ) verify(spySplitAnimationController) @@ -385,7 +394,7 @@ class SplitAnimationControllerTest { val spySplitAnimationController = spy(splitAnimationController) doNothing() .whenever(spySplitAnimationController) - .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any()) + .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) spySplitAnimationController.playSplitLaunchAnimation( null /* launchingTaskView */, @@ -399,10 +408,11 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */ + {} /* finishCallback */, + 1f, /* cornerRadius */ ) verify(spySplitAnimationController) - .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any()) + .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt index bab84ef28b7..e4bdba510f1 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt @@ -22,7 +22,6 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Intent import android.graphics.Rect -import android.os.Handler import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.LauncherState @@ -37,9 +36,10 @@ 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.RecentsView 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 com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import java.util.function.Consumer import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -52,6 +52,7 @@ import org.mockito.Mockito.any import org.mockito.Mockito.`when` import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -63,11 +64,11 @@ class SplitSelectStateControllerTest { private val statsLogManager: StatsLogManager = mock() private val statsLogger: StatsLogger = mock() private val stateManager: StateManager> = mock() - private val handler: Handler = mock() private val context: RecentsViewContainer = mock() private val recentsModel: RecentsModel = mock() private val pendingIntent: PendingIntent = mock() private val splitFromDesktopController: SplitFromDesktopController = mock() + private val recentsView: RecentsView<*, *> = mock() private lateinit var splitSelectStateController: SplitSelectStateController @@ -75,6 +76,7 @@ class SplitSelectStateControllerTest { private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10) private var taskIdCounter = 0 + private fun getUniqueId(): Int { return ++taskIdCounter } @@ -87,13 +89,12 @@ class SplitSelectStateControllerTest { splitSelectStateController = SplitSelectStateController( context, - handler, stateManager, depthController, statsLogManager, systemUiProxy, recentsModel, - null /*activityBackCallback*/ + null, /*activityBackCallback*/ ) } @@ -101,14 +102,14 @@ class SplitSelectStateControllerTest { fun activeTasks_noMatchingTasks() { val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle) val groupTask1 = - generateGroupTask( + generateSplitTask( ComponentName("pomegranate", "juice"), - ComponentName("pumpkin", "pie") + ComponentName("pumpkin", "pie"), ) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("hotdog", "juice"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -125,7 +126,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonMatchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -142,14 +143,14 @@ class SplitSelectStateControllerTest { val matchingComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateGroupTask( + generateSplitTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pomegranate", "juice") + ComponentName("pomegranate", "juice"), ) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -162,14 +163,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) - assertEquals(it[0], groupTask1.task1) + assertEquals(it[0], groupTask1.topLeftTask) } // Capture callback from recentsModel#getTasks() @@ -178,7 +179,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -195,14 +196,14 @@ class SplitSelectStateControllerTest { val nonPrimaryUserComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle) val groupTask1 = - generateGroupTask( + generateSplitTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pomegranate", "juice") + ComponentName("pomegranate", "juice"), ) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -219,7 +220,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonPrimaryUserComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -236,16 +237,16 @@ class SplitSelectStateControllerTest { val nonPrimaryUserComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle) val groupTask1 = - generateGroupTask( + generateSplitTask( ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle, ComponentName("pomegranate", "juice"), - nonPrimaryUserHandle + nonPrimaryUserHandle, ) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -258,15 +259,15 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier) - assertEquals(it[0], groupTask1.task1) + assertEquals(it[0], groupTask1.topLeftTask) } // Capture callback from recentsModel#getTasks() @@ -275,7 +276,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonPrimaryUserComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -292,14 +293,14 @@ class SplitSelectStateControllerTest { val matchingComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateGroupTask( + generateSplitTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pumpkin", "pie") + ComponentName("pumpkin", "pie"), ) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -312,14 +313,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) - assertEquals(it[0], groupTask1.task1) + assertEquals(it[0], groupTask1.topLeftTask) } // Capture callback from recentsModel#getTasks() @@ -328,7 +329,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -347,11 +348,11 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) + generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -366,14 +367,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[1].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[1].key.baseIntent.component?.className, - matchingClass + matchingClass, ) - assertEquals(it[1], groupTask2.task2) + assertEquals(it[1], groupTask2.bottomRightTask) } // Capture callback from recentsModel#getTasks() @@ -382,7 +383,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonMatchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -400,11 +401,11 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) + generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -418,14 +419,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) - assertEquals(it[0], groupTask2.task2) + assertEquals(it[0], groupTask2.bottomRightTask) assertNull("No tasks should have matched", it[1] /*task*/) } @@ -435,7 +436,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -453,14 +454,14 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateGroupTask( + generateSplitTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pumpkin", "pie") + ComponentName("pumpkin", "pie"), ) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -474,25 +475,25 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) - assertEquals(it[0], groupTask1.task1) + assertEquals(it[0], groupTask1.topLeftTask) assertEquals( "ComponentName package mismatched", it[1].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[1].key.baseIntent.component?.className, - matchingClass + matchingClass, ) - assertEquals(it[1], groupTask2.task2) + assertEquals(it[1], groupTask2.bottomRightTask) } // Capture callback from recentsModel#getTasks() @@ -501,7 +502,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -523,16 +524,16 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage2, matchingClass2), primaryUserHandle) val groupTask1 = - generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) + generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) val groupTask2 = - generateGroupTask( + generateSplitTask( ComponentName(matchingPackage2, matchingClass2), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val groupTask3 = - generateGroupTask( + generateSplitTask( ComponentName("hotdog", "pie"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask3) @@ -544,7 +545,7 @@ class SplitSelectStateControllerTest { val taskConsumer = Consumer> { assertEquals("Expected array length 2", 2, it.size) - assertEquals("Found wrong task", it[0], groupTask2.task1) + assertEquals("Found wrong task", it[0], groupTask2.topLeftTask) } // Capture callback from recentsModel#getTasks() @@ -553,7 +554,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent2, matchingComponent), true /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -570,7 +571,7 @@ class SplitSelectStateControllerTest { -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - 10 /*alreadyRunningTask*/ + 10, /*alreadyRunningTask*/ ) assertTrue(splitSelectStateController.isSplitSelectActive) } @@ -582,21 +583,23 @@ class SplitSelectStateControllerTest { -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - -1 /*alreadyRunningTask*/ + -1, /*alreadyRunningTask*/ ) assertTrue(splitSelectStateController.isSplitSelectActive) } @Test fun resetAfterInitial() { + whenever(context.getOverviewPanel>()).thenReturn(recentsView) splitSelectStateController.setInitialTaskSelect( Intent() /*intent*/, -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - -1 + -1, ) splitSelectStateController.resetState() + verify(recentsView, times(1)).resetDesktopTaskFromSplitSelectState() assertFalse(splitSelectStateController.isSplitSelectActive) } @@ -622,11 +625,26 @@ class SplitSelectStateControllerTest { verify(splitFromDesktopController).onDestroy() } - // Generate GroupTask with default userId. - private fun generateGroupTask( + @Test + fun splitSelectStateControllerDestroyed_doNotResetDeskTopTasks() { + whenever(context.getOverviewPanel>()).thenReturn(recentsView) + splitSelectStateController.setInitialTaskSelect( + Intent(), /*intent*/ + -1, /*stagePosition*/ + ItemInfo(), + null, /*splitEvent*/ + -1, + ) + splitSelectStateController.onDestroy() + splitSelectStateController.resetState() + verify(recentsView, times(0)).resetDesktopTaskFromSplitSelectState() + } + + /** Generates a [SplitTask] with default userId. */ + private fun generateSplitTask( task1ComponentName: ComponentName, - task2ComponentName: ComponentName - ): GroupTask { + task2ComponentName: ComponentName, + ): SplitTask { val task1 = Task() var taskInfo = ActivityManager.RunningTaskInfo() taskInfo.taskId = getUniqueId() @@ -642,20 +660,26 @@ class SplitSelectStateControllerTest { intent.component = task2ComponentName taskInfo.baseIntent = intent task2.key = Task.TaskKey(taskInfo) - return GroupTask( + return SplitTask( task1, task2, - SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50) + SplitConfigurationOptions.SplitBounds( + /* leftTopBounds = */ Rect(), + /* rightBottomBounds = */ Rect(), + /* leftTopTaskId = */ task1.key.id, + /* rightBottomTaskId = */ task2.key.id, + /* snapPosition = */ SNAP_TO_2_50_50, + ), ) } - // Generate GroupTask with custom user handles. - private fun generateGroupTask( + /** Generates a [SplitTask] with custom user handles. */ + private fun generateSplitTask( task1ComponentName: ComponentName, userHandle1: UserHandle, task2ComponentName: ComponentName, - userHandle2: UserHandle - ): GroupTask { + userHandle2: UserHandle, + ): SplitTask { val task1 = Task() var taskInfo = ActivityManager.RunningTaskInfo() taskInfo.taskId = getUniqueId() @@ -674,10 +698,16 @@ class SplitSelectStateControllerTest { intent.component = task2ComponentName taskInfo.baseIntent = intent task2.key = Task.TaskKey(taskInfo) - return GroupTask( + return SplitTask( task1, task2, - SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50) + SplitConfigurationOptions.SplitBounds( + /* leftTopBounds = */ Rect(), + /* rightBottomBounds = */ Rect(), + /* leftTopTaskId = */ task1.key.id, + /* rightBottomTaskId = */ task2.key.id, + /* snapPosition = */ SNAP_TO_2_50_50, + ), ) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java deleted file mode 100644 index 7ef4910ce52..00000000000 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.util; - -import static com.android.quickstep.util.TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID; -import static com.android.quickstep.util.TaskGridNavHelper.INVALID_FOCUSED_TASK_ID; - -import static org.junit.Assert.assertEquals; - -import com.android.launcher3.util.IntArray; - -import org.junit.Test; - -public class TaskGridNavHelperTest { - - @Test - public void equalLengthRows_noFocused_onTop_pressDown_goesToBottom() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 1; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 2, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onTop_pressUp_goesToBottom() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 1; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 2, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_pressDown_goesToTop() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 2; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 1, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_pressUp_goesToTop() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 2; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 1, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onTop_pressLeft_goesLeft() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 1; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 3, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_pressLeft_goesLeft() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 2; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 4, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onTop_secondItem_pressRight_goesRight() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 3; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 1, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_secondItem_pressRight_goesRight() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 4; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 2, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onTop_pressRight_cycleToClearAll() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 1; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_pressRight_cycleToClearAll() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 2; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onTop_lastItem_pressLeft_toClearAll() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 5; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_lastItem_pressLeft_toClearAll() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 6; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onClearAll_pressLeft_cycleToFirst() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 1, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onClearAll_pressRight_toLastInBottom() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 6, nextGridPage); - } - - @Test - public void equalLengthRows_withFocused_onFocused_pressLeft_toTop() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int focusedTaskId = 99; - int currentPageTaskViewId = focusedTaskId; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 1, nextGridPage); - } - - @Test - public void equalLengthRows_withFocused_onFocused_pressUp_stayOnFocused() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int focusedTaskId = 99; - int currentPageTaskViewId = focusedTaskId; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage); - } - - @Test - public void equalLengthRows_withFocused_onFocused_pressDown_stayOnFocused() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int focusedTaskId = 99; - int currentPageTaskViewId = focusedTaskId; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage); - } - - @Test - public void equalLengthRows_withFocused_onFocused_pressRight_cycleToClearAll() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int focusedTaskId = 99; - int currentPageTaskViewId = focusedTaskId; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); - } - - @Test - public void equalLengthRows_withFocused_onClearAll_pressLeft_cycleToFocusedTask() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int focusedTaskId = 99; - int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage); - } - - @Test - public void longerTopRow_noFocused_atEndTopBeyondBottom_pressDown_stayTop() { - IntArray topIds = IntArray.wrap(1, 3, 5, 7); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 7; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 7, nextGridPage); - } - - @Test - public void longerTopRow_noFocused_atEndTopBeyondBottom_pressUp_stayTop() { - IntArray topIds = IntArray.wrap(1, 3, 5, 7); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 7; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 7, nextGridPage); - } - - @Test - public void longerTopRow_noFocused_atEndBottom_pressLeft_goToTop() { - IntArray topIds = IntArray.wrap(1, 3, 5, 7); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 6; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 7, nextGridPage); - } - - @Test - public void longerTopRow_noFocused_atClearAll_pressRight_goToLonger() { - IntArray topIds = IntArray.wrap(1, 3, 5, 7); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 7, nextGridPage); - } - - @Test - public void longerBottomRow_noFocused_atClearAll_pressRight_goToLonger() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6, 7); - int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 7, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onTop_pressTab_goesToBottom() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 1; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 2, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_pressTab_goesToNextTop() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 2; - int delta = 1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 3, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onTop_pressTabWithShift_goesToPreviousBottom() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 3; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 2, nextGridPage); - } - - @Test - public void equalLengthRows_noFocused_onBottom_pressTabWithShift_goesToTop() { - IntArray topIds = IntArray.wrap(1, 3, 5); - IntArray bottomIds = IntArray.wrap(2, 4, 6); - int currentPageTaskViewId = 2; - int delta = -1; - @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; - boolean cycle = true; - TaskGridNavHelper taskGridNavHelper = - new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); - - int nextGridPage = - taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - - assertEquals("Wrong next page returned.", 1, nextGridPage); - } -} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt new file mode 100644 index 00000000000..cb088fda564 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util + +import com.android.launcher3.util.IntArray +import com.android.quickstep.util.TaskGridNavHelper.Companion.ADD_DESK_PLACEHOLDER_ID +import com.android.quickstep.util.TaskGridNavHelper.Companion.CLEAR_ALL_PLACEHOLDER_ID +import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.DOWN +import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.LEFT +import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.RIGHT +import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.TAB +import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.UP +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class TaskGridNavHelperTest { + + /* + 5 3 1 + CLEAR_ALL ↓ + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_pressDown_goesToBottom() { + assertThat(getNextGridPage(currentPageTaskViewId = 1, DOWN, delta = 1)).isEqualTo(2) + } + + /* ↑----→ + 5 3 1 | + CLEAR_ALL | + 6 4 2←---| + */ + @Test + fun equalLengthRows_noFocused_onTop_pressUp_goesToBottom() { + assertThat(getNextGridPage(currentPageTaskViewId = 1, UP, delta = 1)).isEqualTo(2) + } + + /* ↓----↑ + 5 3 1 | + CLEAR_ALL | + 6 4 2 | + ↓----→ + */ + @Test + fun equalLengthRows_noFocused_onBottom_pressDown_goesToTop() { + assertThat(getNextGridPage(currentPageTaskViewId = 2, DOWN, delta = 1)).isEqualTo(1) + } + + /* + 5 3 1 + CLEAR_ALL ↑ + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onBottom_pressUp_goesToTop() { + assertThat(getNextGridPage(currentPageTaskViewId = 2, UP, delta = 1)).isEqualTo(1) + } + + /* + 5 3<--1 + CLEAR_ALL + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_pressLeft_goesLeft() { + assertThat(getNextGridPage(currentPageTaskViewId = 1, LEFT, delta = 1)).isEqualTo(3) + } + + /* + 5 3 1 + CLEAR_ALL + 6 4<--2 + */ + @Test + fun equalLengthRows_noFocused_onBottom_pressLeft_goesLeft() { + assertThat(getNextGridPage(currentPageTaskViewId = 2, LEFT, delta = 1)).isEqualTo(4) + } + + /* + 5 3-->1 + CLEAR_ALL + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_secondItem_pressRight_goesRight() { + assertThat(getNextGridPage(currentPageTaskViewId = 3, RIGHT, delta = -1)).isEqualTo(1) + } + + /* + 5 3 1 + CLEAR_ALL + 6 4-->2 + */ + @Test + fun equalLengthRows_noFocused_onBottom_secondItem_pressRight_goesRight() { + assertThat(getNextGridPage(currentPageTaskViewId = 4, RIGHT, delta = -1)).isEqualTo(2) + } + + /* + ↓------------------← + | | + ↓ 5 3 1---→ + CLEAR_ALL + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_pressRight_cycleToClearAll() { + assertThat(getNextGridPage(currentPageTaskViewId = 1, RIGHT, delta = -1)) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + ↓------------------← + | ↑ + ↓ 5 3 1 | + CLEAR_ALL ↑ + 6 4 2---→ + */ + @Test + fun equalLengthRows_noFocused_onBottom_pressRight_cycleToClearAll() { + assertThat(getNextGridPage(currentPageTaskViewId = 2, RIGHT, delta = -1)) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + ←----5 3 1 + ↓ + CLEAR_ALL + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_lastItem_pressLeft_toClearAll() { + assertThat(getNextGridPage(currentPageTaskViewId = 5, LEFT, delta = 1)) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL + ↑ + ←---6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onBottom_lastItem_pressLeft_toClearAll() { + assertThat(getNextGridPage(currentPageTaskViewId = 6, LEFT, delta = 1)) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + |→-----------------------| + | ↓ + ↑ 5 3 1 + ←------CLEAR_ALL + + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onClearAll_pressLeft_cycleToFirst() { + assertThat( + getNextGridPage(currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, LEFT, delta = 1) + ) + .isEqualTo(1) + } + + /* + 5 3 1 + CLEAR_ALL--↓ + | + |--→6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onClearAll_pressRight_toLastInBottom() { + assertThat( + getNextGridPage(currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, RIGHT, delta = -1) + ) + .isEqualTo(6) + } + + /* + 5 3 1←--- + ↑ + CLEAR_ALL ←--FOCUSED_TASK + 6 4 2 + */ + @Test + fun equalLengthRows_withFocused_onFocused_pressLeft_toTop() { + assertThat( + getNextGridPage( + currentPageTaskViewId = FOCUSED_TASK_ID, + LEFT, + delta = 1, + largeTileIds = listOf(FOCUSED_TASK_ID), + ) + ) + .isEqualTo(1) + } + + /* + 5 3 1 + ←--↑ + CLEAR_ALL ↓-→FOCUSED_TASK + 6 4 2 + */ + @Test + fun equalLengthRows_withFocused_onFocused_pressUp_stayOnFocused() { + assertThat( + getNextGridPage( + currentPageTaskViewId = FOCUSED_TASK_ID, + UP, + delta = 1, + largeTileIds = listOf(FOCUSED_TASK_ID), + ) + ) + .isEqualTo(FOCUSED_TASK_ID) + } + + /* + 5 3 1 + CLEAR_ALL ↑--→FOCUSED_TASK + ↑←--↓ + 6 4 2 + */ + + @Test + fun equalLengthRows_withFocused_onFocused_pressDown_stayOnFocused() { + + assertThat( + getNextGridPage( + currentPageTaskViewId = FOCUSED_TASK_ID, + DOWN, + delta = 1, + largeTileIds = listOf(FOCUSED_TASK_ID), + ) + ) + .isEqualTo(FOCUSED_TASK_ID) + } + + /* + ↓-------------------------------←| + | ↑ + ↓ 5 3 1 | + CLEAR_ALL FOCUSED_TASK--→ + 6 4 2 + */ + @Test + fun equalLengthRows_withFocused_onFocused_pressRight_cycleToClearAll() { + + assertThat( + getNextGridPage( + currentPageTaskViewId = FOCUSED_TASK_ID, + RIGHT, + delta = -1, + largeTileIds = listOf(FOCUSED_TASK_ID), + ) + ) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + |→---------------------------| + | | + ↑ 5 3 1 ↓ + ←------CLEAR_ALL FOCUSED_TASK + + 6 4 2 + */ + @Test + fun equalLengthRows_withFocused_onClearAll_pressLeft_cycleToFocusedTask() { + + assertThat( + getNextGridPage( + currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, + LEFT, + delta = 1, + largeTileIds = listOf(FOCUSED_TASK_ID), + ) + ) + .isEqualTo(FOCUSED_TASK_ID) + } + + /* + 7←-↑ 5 3 1 + ↓--→ + CLEAR_ALL + 6 4 2 + */ + @Test + fun longerTopRow_noFocused_atEndTopBeyondBottom_pressDown_stayTop() { + assertThat( + getNextGridPage( + currentPageTaskViewId = 7, + DOWN, + delta = 1, + topIds = IntArray.wrap(1, 3, 5, 7), + ) + ) + .isEqualTo(7) + } + + /* + ←--↑ + ↓-→7 5 3 1 + CLEAR_ALL + 6 4 2 + */ + @Test + fun longerTopRow_noFocused_atEndTopBeyondBottom_pressUp_stayTop() { + assertThat( + getNextGridPage( + /* topIds = */ currentPageTaskViewId = 7, + UP, + delta = 1, + topIds = IntArray.wrap(1, 3, 5, 7), + ) + ) + .isEqualTo(7) + } + + /* + 7 5 3 1 + CLEAR_ALL ↑ + ←----6 4 2 + */ + @Test + fun longerTopRow_noFocused_atEndBottom_pressLeft_goToTop() { + assertThat( + getNextGridPage( + /* topIds = */ currentPageTaskViewId = 6, + LEFT, + delta = 1, + topIds = IntArray.wrap(1, 3, 5, 7), + ) + ) + .isEqualTo(7) + } + + /* + 7 5 3 1 + ↑ + CLEAR_ALL-----→ + 6 4 2 + */ + @Test + fun longerTopRow_noFocused_atClearAll_pressRight_goToLonger() { + assertThat( + getNextGridPage( + /* topIds = */ currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, + RIGHT, + delta = -1, + topIds = IntArray.wrap(1, 3, 5, 7), + ) + ) + .isEqualTo(7) + } + + /* + 5 3 1 + CLEAR_ALL-----→ + ↓ + 7 6 4 2 + */ + @Test + fun longerBottomRow_noFocused_atClearAll_pressRight_goToLonger() { + assertThat( + getNextGridPage( + currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, + RIGHT, + delta = -1, + bottomIds = IntArray.wrap(2, 4, 6, 7), + ) + ) + .isEqualTo(7) + } + + /* + 5 3 1 + CLEAR_ALL ↓ + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_pressTab_goesToBottom() { + assertThat(getNextGridPage(currentPageTaskViewId = 1, TAB, delta = 1)).isEqualTo(2) + } + + /* + 5 3 1 + CLEAR_ALL ↑ + ←---↑ + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onBottom_pressTab_goesToNextTop() { + assertThat(getNextGridPage(currentPageTaskViewId = 2, TAB, delta = 1)).isEqualTo(3) + } + + /* + 5 3 1 + CLEAR_ALL ↓ + ----→ + ↓ + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_pressTabWithShift_goesToPreviousBottom() { + assertThat(getNextGridPage(currentPageTaskViewId = 3, TAB, delta = -1)).isEqualTo(2) + } + + /* + 5 3 1 + CLEAR_ALL ↑ + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onBottom_pressTabWithShift_goesToTop() { + assertThat(getNextGridPage(currentPageTaskViewId = 2, TAB, delta = -1)).isEqualTo(1) + } + + /* + 5 3 [1] + CLEAR_ALL + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onTop_pressTabWithShift_noCycle_staysOnTop() { + assertThat(getNextGridPage(currentPageTaskViewId = 1, TAB, delta = -1, cycle = false)) + .isEqualTo(1) + } + + /* + 5 3 1 + [CLEAR_ALL] + 6 4 2 + */ + @Test + fun equalLengthRows_noFocused_onClearAll_pressTab_noCycle_staysOnClearAll() { + assertThat( + getNextGridPage( + currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, + TAB, + delta = 1, + cycle = false, + ) + ) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL FOCUSED_TASK←--DESKTOP + 6 4 2 + */ + @Test + fun withLargeTile_pressLeftFromDesktopTask_goesToFocusedTask() { + assertThat( + getNextGridPage( + currentPageTaskViewId = DESKTOP_TASK_ID, + LEFT, + delta = 1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(FOCUSED_TASK_ID) + } + + /* + 5 3 1 + CLEAR_ALL FOCUSED_TASK--→DESKTOP + 6 4 2 + */ + @Test + fun withLargeTile_pressRightFromFocusedTask_goesToDesktopTask() { + assertThat( + getNextGridPage( + currentPageTaskViewId = FOCUSED_TASK_ID, + RIGHT, + delta = -1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(DESKTOP_TASK_ID) + } + + /* + ↓-----------------------------------------←| + | | + ↓ 5 3 1 ↑ + CLEAR_ALL FOCUSED_TASK DESKTOP--→ + 6 4 2 + */ + @Test + fun withLargeTile_pressRightFromDesktopTask_goesToClearAll() { + assertThat( + getNextGridPage( + currentPageTaskViewId = DESKTOP_TASK_ID, + RIGHT, + delta = -1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + |→-------------------------------------------| + | | + ↑ 5 3 1 ↓ + ←------CLEAR_ALL FOCUSED_TASK DESKTOP + + 6 4 2 + */ + @Test + fun withLargeTile_pressLeftFromClearAll_goesToDesktopTask() { + assertThat( + getNextGridPage( + currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, + LEFT, + delta = 1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(DESKTOP_TASK_ID) + } + + /* + 5 3 1 + CLEAR_ALL FOCUSED_TASK DESKTOP + ↑ + 6 4 2→----↑ + */ + @Test + fun withLargeTile_pressRightFromBottom_goesToLargeTile() { + assertThat( + getNextGridPage( + currentPageTaskViewId = 2, + RIGHT, + delta = -1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(FOCUSED_TASK_ID) + } + + /* + 5 3 1→----| + ↓ + CLEAR_ALL FOCUSED_TASK DESKTOP + 6 4 2 + */ + @Test + fun withLargeTile_pressRightFromTop_goesToLargeTile() { + assertThat( + getNextGridPage( + currentPageTaskViewId = 1, + RIGHT, + delta = -1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(FOCUSED_TASK_ID) + } + + /* + 5 3 1 + + CLEAR_ALL FOCUSED_TASK←---DESKTOP + 6 4 2 + */ + @Test + fun withLargeTile_pressTabFromDeskTop_goesToFocusedTask() { + assertThat( + getNextGridPage( + currentPageTaskViewId = DESKTOP_TASK_ID, + TAB, + delta = 1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(FOCUSED_TASK_ID) + } + + /* + CLEAR_ALL FOCUSED_TASK DESKTOP + ↓ + 2←----↓ + */ + @Test + fun withLargeTile_pressLeftFromLargeTile_goesToBottom() { + assertThat( + getNextGridPage( + currentPageTaskViewId = FOCUSED_TASK_ID, + LEFT, + delta = 1, + topIds = IntArray(), + bottomIds = IntArray.wrap(2), + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(2) + } + + /* + ↓-----------------------------------------←| + | | + ↓ 5 3 1 ↑ + CLEAR_ALL FOCUSED_TASK DESKTOP--→ + 6 4 2 + */ + @Test + fun withLargeTile_pressShiftTabFromDeskTop_goesToClearAll() { + assertThat( + getNextGridPage( + currentPageTaskViewId = DESKTOP_TASK_ID, + TAB, + delta = -1, + largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID), + ) + ) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + 5 3 1→----| + ↓ + CLEAR_ALL ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressRightFromTop_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = 1, + RIGHT, + delta = -1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL ADD_DESKTOP + ↑ + 6 4 2→----↑ + */ + @Test + fun withAddDesktopButton_pressRightFromBottom_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = 2, + RIGHT, + delta = -1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + ↓-------------------------------←| + | ↑ + ↓ 5 3 1 | + CLEAR_ALL ADD_DESKTOP--→ + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressRightFromAddDesktopButton_goesToClearAllButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + RIGHT, + delta = -1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + |→--------------------------------| + | | + ↑ 5 3 1 ↓ + ←------CLEAR_ALL ADD_DESKTOP + + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressLeftFromClearAllButton_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, + LEFT, + delta = 1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + ←--↑ + CLEAR_ALL ↓-→ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressUpOnAddDesktop_stayOnAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + UP, + delta = 1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL ↑--→ADD_DESKTOP + ↑←--↓ + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressDownOnAddDesktop_stayOnAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + DOWN, + delta = 1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL DESKTOP--→ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressRightFromDesktopTask_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + LEFT, + delta = 1, + largeTileIds = listOf(DESKTOP_TASK_ID), + hasAddDesktopButton = true, + ) + ) + .isEqualTo(DESKTOP_TASK_ID) + } + + /* + 5 3 1 + CLEAR_ALL DESKTOP←--ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressLeftFromAddDesktopButton_goesToDesktopTask() { + assertThat( + getNextGridPage( + currentPageTaskViewId = DESKTOP_TASK_ID, + RIGHT, + delta = -1, + largeTileIds = listOf(DESKTOP_TASK_ID), + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + // Col offset: 0 1 2 + // ----------- + // ID grid: 4 2 0 start + // end [5] 3 1 + @Test + fun gridTaskViewIdOffsetPairInTabOrderSequence_towardsStart() { + val expected = listOf(Pair(4, 0), Pair(3, 1), Pair(2, 1), Pair(1, 2), Pair(0, 2)) + assertThat( + gridTaskViewIdOffsetPairInTabOrderSequence( + initialTaskViewId = 5, + towardsStart = true, + ) + .toList() + ) + .isEqualTo(expected) + } + + // Col offset: 2 1 0 + // ----------- + // ID grid: 4 2 [0] start + // end 5 3 1 + @Test + fun gridTaskViewIdOffsetPairInTabOrderSequence_towardsEnd() { + val expected = listOf(Pair(1, 0), Pair(2, 1), Pair(3, 1), Pair(4, 2), Pair(5, 2)) + assertThat( + gridTaskViewIdOffsetPairInTabOrderSequence( + initialTaskViewId = 0, + towardsStart = false, + ) + .toList() + ) + .isEqualTo(expected) + } + + private fun getNextGridPage( + currentPageTaskViewId: Int, + direction: TaskGridNavHelper.TaskNavDirection, + delta: Int, + topIds: IntArray = IntArray.wrap(1, 3, 5), + bottomIds: IntArray = IntArray.wrap(2, 4, 6), + largeTileIds: List = emptyList(), + hasAddDesktopButton: Boolean = false, + cycle: Boolean = true, + ): Int { + val taskGridNavHelper = + TaskGridNavHelper(topIds, bottomIds, largeTileIds, hasAddDesktopButton) + return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle) + } + + private fun gridTaskViewIdOffsetPairInTabOrderSequence( + initialTaskViewId: Int, + towardsStart: Boolean, + topIds: IntArray = IntArray.wrap(0, 2, 4), + bottomIds: IntArray = IntArray.wrap(1, 3, 5), + largeTileIds: List = emptyList(), + hasAddDesktopButton: Boolean = false, + ): Sequence> { + val taskGridNavHelper = + TaskGridNavHelper(topIds, bottomIds, largeTileIds, hasAddDesktopButton) + return taskGridNavHelper.gridTaskViewIdOffsetPairInTabOrderSequence( + initialTaskViewId, + towardsStart, + ) + } + + private companion object { + const val FOCUSED_TASK_ID = 99 + const val DESKTOP_TASK_ID = 100 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java index 72cfd92b867..be76f9e86d3 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java @@ -35,6 +35,9 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppSingleton; +import com.android.launcher3.util.AllModulesMinusWMProxy; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.LauncherModelHelper; @@ -46,6 +49,9 @@ import com.android.quickstep.FallbackActivityInterface; import com.android.quickstep.util.SurfaceTransaction.MockProperties; +import dagger.BindsInstance; +import dagger.Component; + import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.Assert; @@ -159,6 +165,11 @@ TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) { void verifyNoTransforms() { LauncherModelHelper helper = new LauncherModelHelper(); try { + DisplayController mockController = mock(DisplayController.class); + + helper.sandboxContext.initDaggerComponent( + DaggerTaskViewSimulatorTest_TaskViewSimulatorTestComponent.builder() + .bindDisplayController(mockController)); int rotation = mDisplaySize.x > mDisplaySize.y ? Surface.ROTATION_90 : Surface.ROTATION_0; CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation); @@ -192,17 +203,14 @@ void verifyNoTransforms() { DisplayController.Info info = new Info( configurationContext, wmProxy, perDisplayBoundsCache); - - DisplayController mockController = mock(DisplayController.class); when(mockController.getInfo()).thenReturn(info); - helper.sandboxContext.putObject(DisplayController.INSTANCE, mockController); mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext) .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation); mDeviceProfile.updateInsets(mLauncherInsets); TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext, - FallbackActivityInterface.INSTANCE); + FallbackActivityInterface.INSTANCE, false, 0); tvs.setDp(mDeviceProfile); int launcherRotation = info.rotation; @@ -271,4 +279,18 @@ public void describeTo(Description description) { description.appendValue(mExpected); } } + + @LauncherAppSingleton + @Component(modules = {AllModulesMinusWMProxy.class}) + interface TaskViewSimulatorTestComponent extends LauncherAppComponent { + + @Component.Builder + interface Builder extends LauncherAppComponent.Builder { + + @BindsInstance + Builder bindDisplayController(DisplayController controller); + + TaskViewSimulatorTestComponent build(); + } + } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TestExtensions.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TestExtensions.kt new file mode 100644 index 00000000000..6c526a4855b --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TestExtensions.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util + +import com.android.launcher3.BuildConfig +import com.android.launcher3.util.SafeCloseable +import com.android.quickstep.DeviceConfigWrapper.Companion.configHelper +import com.android.quickstep.util.DeviceConfigHelper.Companion.prefs +import java.util.concurrent.CountDownLatch +import java.util.function.BooleanSupplier +import org.junit.Assert +import org.junit.Assume + +/** Helper methods for testing */ +object TestExtensions { + + @JvmStatic + fun overrideNavConfigFlag( + key: String, + value: Boolean, + targetValue: BooleanSupplier + ): AutoCloseable { + Assume.assumeTrue(BuildConfig.IS_DEBUG_DEVICE) + if (targetValue.asBoolean == value) { + return AutoCloseable {} + } + + navConfigEditWatcher().let { + prefs.edit().putBoolean(key, value).commit() + it.close() + } + Assert.assertEquals(value, targetValue.asBoolean) + + val watcher = navConfigEditWatcher() + return AutoCloseable { + prefs.edit().remove(key).commit() + watcher.close() + } + } + + private fun navConfigEditWatcher(): SafeCloseable { + val wait = CountDownLatch(1) + val listener = Runnable { wait.countDown() } + configHelper.addChangeListener(listener) + + return SafeCloseable { + wait.await() + configHelper.removeChangeListener(listener) + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt new file mode 100644 index 00000000000..6dbb667a229 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.RemoteAnimationTarget +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_OPEN +import android.window.TransitionInfo +import android.window.TransitionInfo.Change +import android.window.TransitionInfo.FLAG_NONE +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.quickstep.RemoteAnimationTargets +import com.android.quickstep.util.TransformParams.BuilderProxy.NO_OP +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TransformParamsTest { + private val surfaceTransaction = mock() + private val transaction = mock() + private val transformParams = TransformParams(::surfaceTransaction) + + private val freeformTaskInfo1 = + createTaskInfo(taskId = 1, windowingMode = WINDOWING_MODE_FREEFORM) + private val freeformTaskInfo2 = + createTaskInfo(taskId = 2, windowingMode = WINDOWING_MODE_FREEFORM) + private val fullscreenTaskInfo1 = + createTaskInfo(taskId = 1, windowingMode = WINDOWING_MODE_FULLSCREEN) + + @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() + + @Before + fun setUp() { + whenever(surfaceTransaction.transaction).thenReturn(transaction) + whenever(surfaceTransaction.forSurface(anyOrNull())) + .thenReturn(mock()) + transformParams.setCornerRadius(CORNER_RADIUS) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_freeformTasks_overridesCornerRadius() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash1 = mock() + val leash2 = mock() + transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1)) + transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2))) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction).setCornerRadius(leash1, 0f) + verify(transaction).setCornerRadius(leash2, 0f) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_freeformTasks_overridesCornerRadiusOnlyOnce() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash1 = mock() + val leash2 = mock() + transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1)) + transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2))) + transformParams.createSurfaceParams(NO_OP) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction).setCornerRadius(leash1, 0f) + verify(transaction).setCornerRadius(leash2, 0f) + } + + @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_flagDisabled_doesntOverrideCornerRadius() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash1 = mock() + val leash2 = mock() + transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1)) + transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2))) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction, never()).setCornerRadius(leash1, 0f) + verify(transaction, never()).setCornerRadius(leash2, 0f) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_fullscreenTasks_doesntOverrideCornerRadius() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash = mock() + transitionInfo.addChange(createChange(fullscreenTaskInfo1, leash = leash)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(fullscreenTaskInfo1))) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction, never()).setCornerRadius(leash, 0f) + } + + private fun createTargetSet(taskInfos: List): RemoteAnimationTargets { + val remoteAnimationTargets = mutableListOf() + taskInfos.map { remoteAnimationTargets.add(createRemoteAnimationTarget(it)) } + return RemoteAnimationTargets( + remoteAnimationTargets.toTypedArray(), + /* wallpapers= */ null, + /* nonApps= */ null, + /* targetMode= */ TRANSIT_OPEN, + ) + } + + private fun createRemoteAnimationTarget(taskInfo: RunningTaskInfo): RemoteAnimationTarget { + val windowConfig = mock() + whenever(windowConfig.activityType).thenReturn(ACTIVITY_TYPE_STANDARD) + return RemoteAnimationTarget( + taskInfo.taskId, + /* mode= */ TRANSIT_OPEN, + /* leash= */ null, + /* isTranslucent= */ false, + /* clipRect= */ null, + /* contentInsets= */ null, + /* prefixOrderIndex= */ 0, + /* position= */ null, + /* localBounds= */ null, + /* screenSpaceBounds= */ null, + windowConfig, + /* isNotInRecents= */ false, + /* startLeash= */ null, + /* startBounds= */ null, + taskInfo, + /* allowEnterPip= */ false, + ) + } + + private fun createTaskInfo(taskId: Int, windowingMode: Int): RunningTaskInfo { + val taskInfo = RunningTaskInfo() + taskInfo.taskId = taskId + taskInfo.configuration.windowConfiguration.windowingMode = windowingMode + return taskInfo + } + + private fun createChange(taskInfo: RunningTaskInfo, leash: SurfaceControl): Change { + val taskInfo = createTaskInfo(taskInfo.taskId, taskInfo.windowingMode) + val change = Change(taskInfo.token, mock()) + change.mode = TRANSIT_OPEN + change.taskInfo = taskInfo + change.leash = leash + return change + } + + private companion object { + private const val CORNER_RADIUS = 30f + } +} diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java index 7b57c81b74c..59ce6370ead 100644 --- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java +++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java @@ -20,6 +20,7 @@ import static android.os.Process.myUserHandle; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION; +import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.TestUtil.runOnExecutorSync; @@ -41,11 +42,12 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; +import android.os.Process; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.text.TextUtils; -import androidx.test.core.content.pm.ApplicationInfoBuilder; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -62,6 +64,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.List; @@ -71,6 +75,9 @@ @RunWith(AndroidJUnit4.class) public final class WidgetsPredicationUpdateTaskTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -80,6 +87,7 @@ public final class WidgetsPredicationUpdateTaskTest { private AppWidgetProviderInfo mApp4Provider1; private AppWidgetProviderInfo mApp4Provider2; private AppWidgetProviderInfo mApp5Provider1; + private AppWidgetProviderInfo mApp6PinOnlyProvider1; private List allWidgets; private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback(); @@ -106,16 +114,22 @@ public void setup() throws Exception { ComponentName.createRelative("app4", ".provider2")); mApp5Provider1 = createAppWidgetProviderInfo( ComponentName.createRelative("app5", "provider1")); + mApp6PinOnlyProvider1 = createAppWidgetProviderInfo( + ComponentName.createRelative("app6", "provider1"), + /*hideFromPicker=*/ true + ); + + allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1, - mApp4Provider1, mApp4Provider2, mApp5Provider1); + mApp4Provider1, mApp4Provider2, mApp5Provider1, mApp6PinOnlyProvider1); mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class); doAnswer(i -> { String pkg = i.getArgument(0); - ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder() - .setPackageName(pkg) - .setName("App " + pkg) - .build(); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = pkg; + applicationInfo.name = "App " + pkg; + applicationInfo.uid = Process.myUid(); applicationInfo.category = CATEGORY_PRODUCTIVITY; applicationInfo.flags = FLAG_INSTALLED; return applicationInfo; @@ -145,6 +159,7 @@ public void tearDown() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER) // Flag off public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() { // Run on model executor so that no other task runs in the middle. runOnExecutorSync(MODEL_EXECUTOR, () -> { @@ -184,6 +199,7 @@ public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredict } @Test + @DisableFlags(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER) // Flag off public void widgetsRecommendationRan_shouldReturnEmptyWidgetsWhenEmpty() { runOnExecutorSync(MODEL_EXECUTOR, () -> { @@ -213,6 +229,32 @@ public void widgetsRecommendationRan_shouldReturnEmptyWidgetsWhenEmpty() { }); } + @Test + public void widgetsRecommendations_excludesWidgetsHiddenForPicker() { + runOnExecutorSync(MODEL_EXECUTOR, () -> { + + // Not installed widget - hence eligible + AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1", + mUserHandle); + // Provider marked as hidden from picker - hence not eligible + AppTarget widget6 = new AppTarget(new AppTargetId("app6"), "app6", "provider1", + mUserHandle); + + mCallback.mRecommendedWidgets = null; + mModelHelper.getModel().enqueueModelUpdateTask( + newWidgetsPredicationTask(List.of(widget1, widget6))); + runOnExecutorSync(MAIN_EXECUTOR, () -> { }); + + // Only widget 1 (and no widget 6 as its meant to be hidden from picker). + List recommendedWidgets = mCallback.mRecommendedWidgets.items + .stream() + .map(itemInfo -> (PendingAddWidgetInfo) itemInfo) + .collect(Collectors.toList()); + assertThat(recommendedWidgets).hasSize(1); + assertThat(recommendedWidgets.get(0).componentName.getPackageName()).isEqualTo("app1"); + }); + } + private void assertWidgetInfo( LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) { assertThat(actual.provider).isEqualTo(expected.provider); @@ -221,7 +263,8 @@ private void assertWidgetInfo( private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List appTargets) { return new WidgetsPredictionUpdateTask( - new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"), + new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction", + DEFAULT_LOOKUP_FLAG), appTargets); } diff --git a/quickstep/tests/src/com/android/launcher3/statehandlers/DepthControllerTest.kt b/quickstep/tests/src/com/android/launcher3/statehandlers/DepthControllerTest.kt new file mode 100644 index 00000000000..17cca0b7d91 --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/statehandlers/DepthControllerTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.statehandlers + +import android.content.res.Resources +import android.view.ViewTreeObserver +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.Launcher +import com.android.launcher3.R +import com.android.launcher3.dragndrop.DragLayer +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.same +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DepthControllerTest { + + private lateinit var underTest: DepthController + @Mock private lateinit var launcher: Launcher + @Mock private lateinit var resource: Resources + @Mock private lateinit var dragLayer: DragLayer + @Mock private lateinit var viewTreeObserver: ViewTreeObserver + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + `when`(launcher.resources).thenReturn(resource) + `when`(resource.getInteger(R.integer.max_depth_blur_radius)).thenReturn(30) + `when`(launcher.dragLayer).thenReturn(dragLayer) + `when`(dragLayer.viewTreeObserver).thenReturn(viewTreeObserver) + + underTest = DepthController(launcher) + } + + @Test + fun setActivityStarted_add_onDrawListener() { + underTest.setActivityStarted(true) + + verify(viewTreeObserver).addOnDrawListener(same(underTest.mOnDrawListener)) + } + + @Test + fun setActivityStopped_not_remove_onDrawListener() { + underTest.setActivityStarted(false) + + // Because underTest.mOnDrawListener is never added + verifyNoMoreInteractions(viewTreeObserver) + } + + @Test + fun setActivityStared_then_stopped_remove_onDrawListener() { + underTest.setActivityStarted(true) + reset(viewTreeObserver) + + underTest.setActivityStarted(false) + + verify(viewTreeObserver).removeOnDrawListener(same(underTest.mOnDrawListener)) + } + + @Test + fun setActivityStared_then_stopped_multiple_times_remove_onDrawListener_once() { + underTest.setActivityStarted(true) + reset(viewTreeObserver) + + underTest.setActivityStarted(false) + underTest.setActivityStarted(false) + underTest.setActivityStarted(false) + + // Should just remove mOnDrawListener once + verify(viewTreeObserver).removeOnDrawListener(same(underTest.mOnDrawListener)) + } + + @Test + fun test_onInvalidSurface_multiple_times_add_onDrawListener_once() { + underTest.onInvalidSurface() + underTest.onInvalidSurface() + underTest.onInvalidSurface() + + // We should only call addOnDrawListener 1 time + verify(viewTreeObserver).addOnDrawListener(same(underTest.mOnDrawListener)) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt b/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt new file mode 100644 index 00000000000..4b8f2a23489 --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.statehandlers + +import android.content.Context +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession +import com.android.launcher3.util.DaggerSingletonTracker +import com.android.quickstep.SystemUiProxy +import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND +import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +/** + * Tests the behavior of [DesktopVisibilityController] in regards to multiple desktops and multiple + * displays. + */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class DesktopVisibilityControllerTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + + private val mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(DesktopModeStatus::class.java) + .startMocking() + + private val context = mock() + private val systemUiProxy = mock() + private val lifeCycleTracker = mock() + private lateinit var desktopVisibilityController: DesktopVisibilityController + + @Before + fun setUp() { + whenever(context.resources).thenReturn(mock()) + whenever(DesktopModeStatus.enableMultipleDesktops(context)).thenReturn(true) + desktopVisibilityController = + DesktopVisibilityController(context, systemUiProxy, lifeCycleTracker) + } + + @After + fun tearDown() { + mockitoSession.finishMocking() + } + + @Test + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND) + fun noCrashWhenCheckingNonExistentDisplay() { + assertFalse(desktopVisibilityController.isInDesktopMode(displayId = 500)) + assertFalse(desktopVisibilityController.isInDesktopModeAndNotInOverview(displayId = 300)) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java deleted file mode 100644 index 9ed39066ddd..00000000000 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java +++ /dev/null @@ -1,219 +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.taskbar; - -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; - -import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS; - -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.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.Display; -import android.view.MotionEvent; - -import androidx.test.filters.SmallTest; - -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.model.data.FolderInfo; -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; -import org.mockito.stubbing.Answer; - -/** - * Tests for TaskbarHoverToolTipController. - */ -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase { - - private TaskbarHoverToolTipController mTaskbarHoverToolTipController; - private TestableLooper mTestableLooper; - - @Mock private TaskbarView mTaskbarView; - @Mock private MotionEvent mMotionEvent; - @Mock private BubbleTextView mHoverBubbleTextView; - @Mock private FolderIcon mHoverFolderIcon; - @Mock private Display mDisplay; - @Mock private TaskbarDragLayer mTaskbarDragLayer; - private Folder mSpyFolderView; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - Context context = getApplicationContext(); - - doAnswer((Answer) invocation -> context.getSystemService( - (String) invocation.getArgument(0))) - .when(taskbarActivityContext).getSystemService(anyString()); - when(taskbarActivityContext.getResources()).thenReturn(context.getResources()); - when(taskbarActivityContext.getApplicationInfo()).thenReturn( - context.getApplicationInfo()); - when(taskbarActivityContext.getDragLayer()).thenReturn(mTaskbarDragLayer); - when(taskbarActivityContext.getMainLooper()).thenReturn(context.getMainLooper()); - when(taskbarActivityContext.getDisplay()).thenReturn(mDisplay); - - when(mTaskbarDragLayer.getChildCount()).thenReturn(1); - mSpyFolderView = spy(new Folder(new ActivityContextWrapper(context), null)); - when(mTaskbarDragLayer.getChildAt(anyInt())).thenReturn(mSpyFolderView); - doReturn(false).when(mSpyFolderView).isOpen(); - - when(mHoverBubbleTextView.getText()).thenReturn("tooltip"); - doAnswer((Answer) invocation -> { - Object[] args = invocation.getArguments(); - ((int[]) args[0])[0] = 0; - ((int[]) args[0])[1] = 0; - return null; - }).when(mHoverBubbleTextView).getLocationOnScreen(any(int[].class)); - when(mHoverBubbleTextView.getWidth()).thenReturn(100); - when(mHoverBubbleTextView.getHeight()).thenReturn(100); - - mHoverFolderIcon.mInfo = new FolderInfo(); - mHoverFolderIcon.mInfo.title = "tooltip"; - doAnswer((Answer) invocation -> { - Object[] args = invocation.getArguments(); - ((int[]) args[0])[0] = 0; - ((int[]) args[0])[1] = 0; - return null; - }).when(mHoverFolderIcon).getLocationOnScreen(any(int[].class)); - when(mHoverFolderIcon.getWidth()).thenReturn(100); - when(mHoverFolderIcon.getHeight()).thenReturn(100); - - when(mTaskbarView.getTop()).thenReturn(200); - - mTaskbarHoverToolTipController = new TaskbarHoverToolTipController( - taskbarActivityContext, mTaskbarView, mHoverBubbleTextView); - mTestableLooper = TestableLooper.get(this); - } - - @Test - public void onHover_hoverEnterIcon_revealToolTip() { - when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); - when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); - - boolean hoverHandled = - mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent); - waitForIdleSync(); - - assertThat(hoverHandled).isTrue(); - verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, - true); - } - - @Test - public void onHover_hoverExitIcon_closeToolTip() { - when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); - when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); - - boolean hoverHandled = - mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent); - waitForIdleSync(); - - assertThat(hoverHandled).isTrue(); - verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, - false); - } - - @Test - public void onHover_hoverEnterFolderIcon_revealToolTip() { - when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); - when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); - - boolean hoverHandled = - mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); - waitForIdleSync(); - - assertThat(hoverHandled).isTrue(); - verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, - true); - } - - @Test - public void onHover_hoverExitFolderIcon_closeToolTip() { - when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); - when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); - - boolean hoverHandled = - mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); - waitForIdleSync(); - - assertThat(hoverHandled).isTrue(); - verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, - false); - } - - @Test - public void onHover_hoverExitFolderOpen_closeToolTip() { - when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); - when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); - doReturn(true).when(mSpyFolderView).isOpen(); - - boolean hoverHandled = - mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); - waitForIdleSync(); - - assertThat(hoverHandled).isTrue(); - verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, - false); - } - - @Test - public void onHover_hoverEnterFolderOpen_noToolTip() { - when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); - when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); - doReturn(true).when(mSpyFolderView).isOpen(); - - boolean hoverHandled = - mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); - - assertThat(hoverHandled).isFalse(); - } - - @Test - public void onHover_hoverMove_noUpdate() { - when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_MOVE); - when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_MOVE); - - boolean hoverHandled = - mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); - - assertThat(hoverHandled).isFalse(); - } - - private void waitForIdleSync() { - mTestableLooper.processAllMessages(); - } -} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt deleted file mode 100644 index 104263af5b0..00000000000 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.taskbar - -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): List { - return packageNames.map { createTestAppInfo(packageName = it) } - } - - private fun createDesktopTasksFromPackageNames( - packageNames: List - ): ArrayList { - 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/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt new file mode 100644 index 00000000000..8f267950459 --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt @@ -0,0 +1,1638 @@ +/* + * 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.PointF +import android.graphics.Rect +import android.graphics.drawable.ColorDrawable +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.widget.FrameLayout +import android.widget.TextView +import androidx.core.animation.AnimatorTestRule +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.TaskbarInsetsController +import com.android.launcher3.taskbar.bubbles.BubbleBarBubble +import com.android.launcher3.taskbar.bubbles.BubbleBarOverflow +import com.android.launcher3.taskbar.bubbles.BubbleBarParentViewHeightUpdateNotifier +import com.android.launcher3.taskbar.bubbles.BubbleBarView +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController +import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController +import com.android.launcher3.taskbar.bubbles.BubbleView +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage +import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner +import com.android.launcher3.taskbar.bubbles.flyout.FlyoutCallbacks +import com.android.launcher3.taskbar.bubbles.flyout.FlyoutScheduler +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState +import com.android.wm.shell.shared.animation.PhysicsAnimator +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils +import com.android.wm.shell.shared.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.bubbles.BubbleInfo +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleBarViewAnimatorTest { + + @get:Rule val animatorTestRule = AnimatorTestRule() + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler + private lateinit var bubbleBarParentViewController: TestBubbleBarParentViewHeightUpdateNotifier + private lateinit var overflowView: BubbleView + private lateinit var bubbleView: BubbleView + private lateinit var bubble: BubbleBarBubble + private lateinit var bubbleBarView: BubbleBarView + private lateinit var flyoutContainer: FrameLayout + private lateinit var bubbleStashController: FakeBubbleStashController + private lateinit var flyoutController: BubbleBarFlyoutController + private val emptyRunnable = Runnable {} + + private val flyoutView: View? + get() = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view) + + @Before + fun setUp() { + animatorScheduler = TestBubbleBarViewAnimatorScheduler() + bubbleBarParentViewController = TestBubbleBarParentViewHeightUpdateNotifier() + bubbleStashController = FakeBubbleStashController() + PhysicsAnimatorTestUtils.prepareForTest() + setupFlyoutController() + } + + @Test + fun animateBubbleInForStashed() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // 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(animator.isAnimating).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + // execute the hide bubble animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(handle.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + fun animateBubbleInForStashed_tapAnimatingBubble() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // 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(animator.isAnimating).isTrue() + + assertThat(bubbleStashController.taskbarTouchRegionUpdated).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { animator.interruptForTouch() } + + waitForFlyoutToHide() + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(animatorScheduler.delayedBlock).isNull() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.scaleX).isEqualTo(1) + assertThat(bubbleBarView.scaleY).isEqualTo(1) + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(animator.isAnimating).isFalse() + } + + @Test + fun animateBubbleInForStashed_touchTaskbarArea_whileShowing() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true } + + handleAnimator.assertIsRunning() + assertThat(animator.isAnimating).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.onStashStateChangingWhileAnimating() + } + + // wait for the animation to cancel + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( + handleAnimator, + DynamicAnimation.TRANSLATION_Y, + ) + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.animationInterrupted).isTrue() + assertThat(bubbleBarView.scaleX).isEqualTo(1) + assertThat(bubbleBarView.scaleY).isEqualTo(1) + assertThat(bubbleStashController.isStashed).isTrue() + + // 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() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + // execute the hide bubble animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + // wait for the hide animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + handleAnimator.assertIsRunning() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.onStashStateChangingWhileAnimating() + } + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.animationInterrupted).isTrue() + + // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait + // again + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + handleAnimator.assertIsNotRunning() + } + + @Test + fun animateBubbleInForStashed_autoExpanding() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + var notifiedExpanded = false + val onExpanded = Runnable { notifiedExpanded = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = true) + } + + // wait for the animation to start + 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(animator.isAnimating).isFalse() + assertThat(bubbleBarView.isExpanded).isTrue() + + // verify there is no hide animation + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(bubbleStashController.isStashed).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateBubbleInForStashed_expandedWhileAnimatingIn() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + var notifiedExpanded = false + val onExpanded = Runnable { notifiedExpanded = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true } + + handleAnimator.assertIsRunning() + assertThat(animator.isAnimating).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // let the animation finish + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY) + .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(animator.isAnimating).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateBubbleInForStashed_expandedWhileFullyIn() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + var notifiedExpanded = false + val onExpanded = Runnable { notifiedExpanded = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // wait for the animation to end + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(animator.isAnimating).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + waitForFlyoutToHide() + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY) + .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(animator.isAnimating).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateToInitialState_inApp() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.IN_APP + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + var notifiedBubbleBarVisible = false + val onBubbleBarVisible = Runnable { notifiedBubbleBarVisible = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = onBubbleBarVisible, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleBarView.visibility = INVISIBLE + animator.animateToInitialState(bubble, isInApp = true, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + barAnimator.assertIsNotRunning() + assertThat(animator.isAnimating).isTrue() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(handle.translationY).isEqualTo(0) + assertThat(handle.alpha).isEqualTo(1) + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(notifiedBubbleBarVisible).isTrue() + + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + fun animateToInitialState_whileDragging_inApp() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.IN_APP + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + var notifiedBubbleBarVisible = false + val onBubbleBarVisible = Runnable { notifiedBubbleBarVisible = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = onBubbleBarVisible, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleBarView.visibility = INVISIBLE + animator.animateToInitialState( + bubble, + isInApp = true, + isExpanding = false, + isDragging = true, + ) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + barAnimator.assertIsNotRunning() + assertThat(animator.isAnimating).isTrue() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(notifiedBubbleBarVisible).isTrue() + } + + @Test + fun animateToInitialState_inApp_autoExpanding() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.IN_APP + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + var notifiedExpanded = false + val onExpanded = Runnable { notifiedExpanded = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = true, isExpanding = true) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + barAnimator.assertIsNotRunning() + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + + assertThat(animatorScheduler.delayedBlock).isNull() + assertThat(bubbleStashController.isStashed).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateToInitialState_inHome() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.HOME + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = false, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + barAnimator.assertIsNotRunning() + assertThat(animator.isAnimating).isTrue() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + assertThat(bubbleStashController.isStashed).isFalse() + } + + @Test + fun animateToInitialState_expandedWhileAnimatingIn() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.HOME + + var notifiedExpanded = false + val onExpanded = Runnable { notifiedExpanded = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = false, isExpanding = false) + } + + val bubbleBarAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(bubbleBarAnimator) { true } + + bubbleBarAnimator.assertIsRunning() + assertThat(animator.isAnimating).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // let the animation finish + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.isStashed).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateToInitialState_expandedWhileFullyIn() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.HOME + + var notifiedExpanded = false + val onExpanded = Runnable { notifiedExpanded = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = false, isExpanding = false) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(animator.isAnimating).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + waitForFlyoutToHide() + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(animator.isAnimating).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateBubbleBarForCollapsed() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.HOME + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleBarForCollapsed(bubble, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // verify we started animating + assertThat(animator.isAnimating).isTrue() + + // advance the animation handler by the duration of the initial lift + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + + // the lift animation is complete; the spring back animation should start now + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + barAnimator.assertIsRunning() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + // the bubble bar translation y should be back to its initial value + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleStashController.isStashed).isFalse() + } + + @Test + fun animateBubbleBarForCollapsed_autoExpanding() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.HOME + + val semaphore = Semaphore(0) + var notifiedExpanded = false + val onExpanded = Runnable { + notifiedExpanded = true + semaphore.release() + } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleBarForCollapsed(bubble, isExpanding = true) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // verify we started animating + assertThat(animator.isAnimating).isTrue() + + // advance the animation handler by the duration of the initial lift + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + + // the lift animation is complete; the spring back animation should start now + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + // we should be expanded now + assertThat(bubbleBarView.isExpanded).isTrue() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify there is no hide animation + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleStashController.isStashed).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateBubbleBarForCollapsed_expandingWhileAnimatingIn() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.HOME + + val semaphore = Semaphore(0) + var notifiedExpanded = false + val onExpanded = Runnable { + notifiedExpanded = true + semaphore.release() + } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleBarForCollapsed(bubble, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // verify we started animating + assertThat(animator.isAnimating).isTrue() + + // advance the animation handler by the duration of the initial lift + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(100) + } + + // verify there is a pending hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + assertThat(animator.isAnimating).isTrue() + + // send the expand signal in the middle of the lift animation + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // let the lift animation complete + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(150) + } + + // the lift animation is complete; the spring back animation should start now. wait for it + // to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarView.isExpanded).isTrue() + assertThat(bubbleStashController.isStashed).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun animateBubbleBarForCollapsed_expandingWhileFullyIn() { + setUpBubbleBar() + bubbleStashController.launcherState = BubbleLauncherState.HOME + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + var notifiedExpanded = false + val onExpanded = Runnable { notifiedExpanded = true } + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = onExpanded, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleBarForCollapsed(bubble, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // verify we started animating + assertThat(animator.isAnimating).isTrue() + + // advance the animation handler by the duration of the initial lift + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + + // the lift animation is complete; the spring back animation should start now + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + barAnimator.assertIsRunning() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify there is a pending hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + assertThat(animator.isAnimating).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + waitForFlyoutToHide() + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarView.isExpanded).isTrue() + assertThat(bubbleStashController.isStashed).isFalse() + assertThat(notifiedExpanded).isTrue() + } + + @Test + fun interruptAnimation_whileAnimatingIn() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // let the animation start and wait until the first frame + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true } + + handleAnimator.assertIsRunning() + assertThat(animator.isAnimating).isTrue() + + val updatedBubble = + bubble.copy(flyoutMessage = bubble.flyoutMessage!!.copy(message = "updated message")) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleView.setBubble(updatedBubble) + animator.animateBubbleInForStashed(updatedBubble, isExpanding = false) + } + + 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(animator.isAnimating).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + assertThat(flyoutView!!.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("updated message") + + // run the hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(handle.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + fun interruptAnimation_whileIn() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // 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(animator.isAnimating).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(flyoutView!!.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("message") + + // verify the hide animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + val updatedBubble = + bubble.copy(flyoutMessage = bubble.flyoutMessage!!.copy(message = "updated message")) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleView.setBubble(updatedBubble) + animator.animateBubbleInForStashed(updatedBubble, isExpanding = false) + } + + // verify the hide animation was rescheduled + assertThat(animatorScheduler.canceledBlock).isNotNull() + assertThat(animatorScheduler.delayedBlock).isNotNull() + + waitForFlyoutToFadeOutAndBackIn() + + assertThat(flyoutView!!.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("updated message") + + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(handle.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + fun interruptAnimation_whileAnimatingOut_whileCollapsingFlyout() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // 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(animator.isAnimating).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(flyoutView!!.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("message") + + // run the hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + // interrupt the animation while the flyout is collapsing + val updatedBubble = + bubble.copy(flyoutMessage = bubble.flyoutMessage!!.copy(message = "updated message")) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(100) + bubbleView.setBubble(updatedBubble) + animator.animateBubbleInForStashed(updatedBubble, isExpanding = false) + + // the flyout should now reverse and expand + animatorTestRule.advanceTimeBy(400) + } + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + + assertThat(flyoutView!!.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("updated message") + + 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) + + // verify the hide animation was rescheduled and run it + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(3) + assertThat(handle.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + fun interruptAnimation_whileAnimatingOut_barToHandle() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // 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(animator.isAnimating).isTrue() + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + waitForFlyoutToShow() + + assertThat(flyoutView!!.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("message") + + // run the hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + // interrupt the animation while the bar is animating to the handle + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { + bubbleBarView.alpha < 0.5 + } + // we're about to interrupt the animation which will cancel the current animation and start + // a new one. pause the scheduler to delay starting the new animation. this allows us to run + // the test deterministically + animatorScheduler.pauseScheduler = true + + val updatedBubble = + bubble.copy(flyoutMessage = bubble.flyoutMessage!!.copy(message = "updated message")) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleView.setBubble(updatedBubble) + animator.animateBubbleInForStashed(updatedBubble, isExpanding = false) + } + + // since animation was interrupted there shouldn't be additional calls to adjust window + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1) + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + // verify there's a new job scheduled and start it. this is starting the animation from the + // handle back to the bar + assertThat(animatorScheduler.pausedBlock).isNotNull() + animatorScheduler.pauseScheduler = false + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.pausedBlock!!) + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + waitForFlyoutToShow() + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2) + assertThat(flyoutView!!.findViewById(R.id.bubble_flyout_text).text) + .isEqualTo("updated message") + 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) + + // run the hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify the hide animation was rescheduled and run it + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + waitForFlyoutToHide() + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(3) + assertThat(handle.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.isStashed).isTrue() + } + + @Test + fun interruptForIme() { + setUpBubbleBar() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + bubbleStashController.handleAnimator = handleAnimator + + val animator = + BubbleBarViewAnimator( + bubbleBarView, + bubbleStashController, + flyoutController, + bubbleBarParentViewController, + onExpanded = emptyRunnable, + onBubbleBarVisible = emptyRunnable, + animatorScheduler, + ) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true } + + handleAnimator.assertIsRunning() + assertThat(animator.isAnimating).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { animator.interruptForIme() } + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + assertThat(animator.isAnimating).isFalse() + assertThat(bubbleStashController.animationInterrupted).isTrue() + assertThat(bubbleStashController.isStashed).isTrue() + + // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait + // again + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + handleAnimator.assertIsNotRunning() + } + + 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, + true, + null, + ) + bubbleView = + inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView + bubble = + BubbleBarBubble( + bubbleInfo, + bubbleView, + bitmap, + bitmap, + Color.WHITE, + Path(), + "", + BubbleBarFlyoutMessage(icon = null, title = "title", message = "message"), + ) + bubbleView.setBubble(bubble) + bubbleBarView.addView(bubbleView) + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } + + private fun setupFlyoutController() { + flyoutContainer = FrameLayout(context) + val flyoutPositioner = + object : BubbleBarFlyoutPositioner { + override val isOnLeft = true + override val targetTy = 100f + override val distanceToCollapsedPosition = PointF(0f, 0f) + override val collapsedSize = 30f + override val collapsedColor = Color.BLUE + override val collapsedElevation = 1f + override val distanceToRevealTriangle = 10f + } + val flyoutCallbacks = + object : FlyoutCallbacks { + override fun flyoutClicked() {} + } + val flyoutScheduler = FlyoutScheduler { block -> block.invoke() } + flyoutController = + BubbleBarFlyoutController( + flyoutContainer, + flyoutPositioner, + flyoutCallbacks, + flyoutScheduler, + ) + } + + private fun verifyBubbleBarIsExpandedWithTranslation(ty: Float) { + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(bubbleBarView.scaleX).isEqualTo(1) + assertThat(bubbleBarView.scaleY).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(ty) + assertThat(bubbleBarView.isExpanded).isTrue() + } + + private fun waitForFlyoutToShow() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(400) + } + assertThat(flyoutView).isNotNull() + } + + private fun waitForFlyoutToHide() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(350) + } + assertThat(flyoutView).isNull() + } + + private fun waitForFlyoutToFadeOutAndBackIn() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(750) + } + assertThat(flyoutView).isNotNull() + } + + private fun PhysicsAnimator.assertIsRunning() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + assertThat(isRunning()).isTrue() + } + } + + private fun PhysicsAnimator.assertIsNotRunning() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + assertThat(isRunning()).isFalse() + } + } + + private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler { + + var pauseScheduler = false + var pausedBlock: Runnable? = null + private set + + var delayedBlock: Runnable? = null + private set + + var canceledBlock: Runnable? = null + private set + + override fun post(block: Runnable) { + if (pauseScheduler) { + pausedBlock = block + return + } + block.run() + } + + override fun postDelayed(delayMillis: Long, block: Runnable) { + delayedBlock = block + } + + override fun cancel(block: Runnable) { + canceledBlock = delayedBlock + delayedBlock = null + } + } + + private class TestBubbleBarParentViewHeightUpdateNotifier : + BubbleBarParentViewHeightUpdateNotifier { + + var timesInvoked: Int = 0 + + override fun updateTopBoundary() { + timesInvoked++ + } + } + + private class FakeBubbleStashController : BubbleStashController { + + var handleAnimator: PhysicsAnimator? = null + var taskbarTouchRegionUpdated = false + private set + + var animationInterrupted = false + private set + + private var _isStashed = true + + override var launcherState = BubbleLauncherState.HOME + override val isStashed: Boolean + get() = _isStashed + + override var bubbleBarVerticalCenterForHome = 0 + override var isSysuiLocked = false + override val isTransientTaskBar = true + override val hasHandleView = true + override val bubbleBarTranslationYForTaskbar = BAR_TRANSLATION_Y_FOR_TASKBAR + override val bubbleBarTranslationYForHotseat = BAR_TRANSLATION_Y_FOR_HOTSEAT + override var inAppDisplayOverrideProgress = 0f + + override fun init( + taskbarInsetsController: TaskbarInsetsController, + bubbleBarViewController: BubbleBarViewController, + bubbleStashedHandleViewController: BubbleStashedHandleViewController?, + controllersAfterInitAction: BubbleStashController.ControllersAfterInitAction, + ) {} + + override fun showBubbleBarImmediate() { + _isStashed = false + } + + override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) { + _isStashed = false + } + + override fun stashBubbleBarImmediate() { + _isStashed = true + } + + override fun getTouchableHeight() = 100 + + override fun isBubbleBarVisible() = true + + override fun onNewBubbleAnimationInterrupted( + isStashed: Boolean, + bubbleBarTranslationY: Float, + ) { + _isStashed = isStashed + animationInterrupted = true + } + + override fun isEventOverBubbleBarViews(ev: MotionEvent) = false + + override fun setBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) {} + + override fun stashBubbleBar() { + _isStashed = true + } + + override fun showBubbleBar(expandBubbles: Boolean, bubbleBarGesture: Boolean) { + _isStashed = false + } + + override fun getDiffBetweenHandleAndBarCenters() = DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + + override fun getStashedHandleTranslationForNewBubbleAnimation() = HANDLE_TRANSLATION + + override fun getStashedHandlePhysicsAnimator(): PhysicsAnimator? { + return handleAnimator + } + + override fun updateTaskbarTouchRegion() { + taskbarTouchRegionUpdated = true + } + + override fun setHandleTranslationY(translationY: Float) {} + + override fun getHandleTranslationY() = 0f + + override fun getHandleBounds(bounds: Rect) {} + } +} + +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/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java index 44c23ba9a2c..c215ff2227a 100644 --- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java +++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java @@ -24,13 +24,22 @@ import androidx.test.uiautomator.Until; import com.android.launcher3.tapl.LaunchedAppState; +import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.util.TestUtil; +import com.android.launcher3.util.Wait; +import com.android.quickstep.fallback.RecentsState; +import com.android.quickstep.fallback.window.RecentsWindowManager; import com.android.quickstep.views.RecentsView; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + /** * Base class for all instrumentation tests that deal with Quickstep. */ @@ -53,10 +62,48 @@ protected void onLauncherActivityClose(QuickstepLauncher launcher) { } } + // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call + // expecting the results of that gesture because the wait can hide flakeness. + protected void waitForRecentsWindowState(String message, Supplier state) { + waitForRecentsWindowCondition(message, recentsWindow -> + recentsWindow.getStateManager().getCurrentStableState() == state.get()); + } + + // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide + // flakiness. + protected void waitForRecentsWindowCondition(String + message, Function condition) { + waitForRecentsWindowCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT); + } + + protected T getFromRecentsWindow(Function f) { + if (!TestHelpers.isInLauncherProcess()) return null; + return getOnUiThread(() -> { + RecentsWindowManager recentsWindowManager = + RecentsWindowManager.getRecentsWindowTracker().getCreatedContext(); + return recentsWindowManager != null ? f.apply(recentsWindowManager) : null; + }); + } + + // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide + // flakiness. + protected void waitForRecentsWindowCondition( + String message, Function condition, long timeout) { + verifyKeyguardInvisible(); + if (!TestHelpers.isInLauncherProcess()) return; + Wait.atMost(message, () -> getFromRecentsWindow(condition), mLauncher, timeout); + } + + protected boolean isInRecentsWindowState(Supplier state) { + if (!TestHelpers.isInLauncherProcess()) return true; + return getFromRecentsWindow( + recentsWindow -> recentsWindow.getStateManager().getState() == state.get()); + } + protected void assertTestActivityIsRunning(int activityNumber, String message) { assertTrue(message, mDevice.wait( Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity" + activityNumber)), - DEFAULT_UI_TIMEOUT)); + TestUtil.DEFAULT_UI_TIMEOUT)); } protected LaunchedAppState getAndAssertLaunchedApp() { diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java index fc757b44c9d..0ccc76b24ea 100644 --- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java +++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java @@ -55,7 +55,9 @@ public void setUp() throws Exception { "com.android.launcher3.testcomponent.BaseTestingActivity"); mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder); AbstractLauncherUiTest.initialize(this); - startAppFast(CALCULATOR_APP_PACKAGE); + if (startCalendarAppDuringSetup()) { + startAppFast(CALCULATOR_APP_PACKAGE); + } mLauncher.enableBlockTimeout(true); mLauncher.showTaskbarIfHidden(); } @@ -72,8 +74,20 @@ protected static boolean isTaskbarInTransientMode(Context context) { return DisplayController.isTransientTaskbar(context); } + protected boolean startCalendarAppDuringSetup() { + return true; + } + + protected boolean expectTaskbarIconsMatchHotseat() { + return true; + } + protected Taskbar getTaskbar() { Taskbar taskbar = mLauncher.getLaunchedAppState().getTaskbar(); + if (!expectTaskbarIconsMatchHotseat()) { + return taskbar; + } + List taskbarIconNames = taskbar.getIconNames(); List hotseatIconNames = mLauncher.getHotseatIconNames(); diff --git a/quickstep/tests/src/com/android/quickstep/AspectRatioSystemShortcutTests.kt b/quickstep/tests/src/com/android/quickstep/AspectRatioSystemShortcutTests.kt new file mode 100644 index 00000000000..10e85e6a1b1 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/AspectRatioSystemShortcutTests.kt @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep + +import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.provider.Settings +import android.view.Display.DEFAULT_DISPLAY +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.AbstractFloatingView +import com.android.launcher3.AbstractFloatingViewHelper +import com.android.launcher3.Flags.enableRefactorTaskThumbnail +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.R +import com.android.launcher3.logging.StatsLogManager +import com.android.launcher3.logging.StatsLogManager.LauncherEvent +import com.android.launcher3.logging.StatsLogManager.StatsLogger +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.model.data.TaskViewItemInfo +import com.android.launcher3.util.RunnableList +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.launcher3.util.TransformingTouchDelegate +import com.android.launcher3.util.WindowBounds +import com.android.quickstep.orientation.LandscapePagedViewHandler +import com.android.quickstep.recents.data.RecentsDeviceProfileRepository +import com.android.quickstep.recents.data.RecentsRotationStateRepository +import com.android.quickstep.recents.di.RecentsDependencies +import com.android.quickstep.task.thumbnail.TaskThumbnailView +import com.android.quickstep.util.RecentsOrientedState +import com.android.quickstep.views.LauncherRecentsView +import com.android.quickstep.views.RecentsViewContainer +import com.android.quickstep.views.TaskContainer +import com.android.quickstep.views.TaskThumbnailViewDeprecated +import com.android.quickstep.views.TaskView +import com.android.quickstep.views.TaskViewIcon +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.Task.TaskKey +import com.android.window.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito +import org.mockito.Mockito.eq +import org.mockito.Mockito.isNull +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Test for [AspectRatioSystemShortcut] */ +class AspectRatioSystemShortcutTests { + + @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) + + /** Spy on a concrete Context so we can reference real View, Layout, and Display properties. */ + private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext) + + /** + * RecentsViewContainer and its super-interface ActivityContext contain methods to convert + * themselves to a Context at runtime, and static methods to convert a Context back to + * themselves by traversing ContextWrapper layers. + * + * Thus there is an undocumented assumption that a RecentsViewContainer always extends Context. + * We need to mock all of the RecentsViewContainer methods but leave the Context-under-test + * intact. + * + * The simplest way is to extend ContextWrapper and delegate the RecentsViewContainer interface + * to a mock. + */ + class RecentsViewContainerContextWrapper(base: Context) : + ContextWrapper(base), RecentsViewContainer by mock() { + + private val statsLogManager: StatsLogManager = mock() + + override fun getStatsLogManager(): StatsLogManager = statsLogManager + + override fun startActivitySafely(v: View, intent: Intent, item: ItemInfo?): RunnableList? = + null + } + + /** + * This is implicitly required in many parts of Launcher that + * require a Context. See RecentsViewContainerContextWrapper. + */ + private val launcher: RecentsViewContainerContextWrapper = + spy(RecentsViewContainerContextWrapper(context)) + + private val recentsView: LauncherRecentsView = mock() + private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock() + private val taskOverlayFactory: TaskOverlayFactory = + mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS) + private val factory: TaskShortcutFactory = + AspectRatioSystemShortcut.createFactory(abstractFloatingViewHelper) + private val statsLogger = mock() + private val orientedState: RecentsOrientedState = + mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS) + private val taskView: TaskView = + LayoutInflater.from(context).cloneInContext(launcher).inflate(R.layout.task, null) as + TaskView + + @Before + fun setUp() { + whenever(launcher.getOverviewPanel()).thenReturn(recentsView) + + val statsLogManager = launcher.getStatsLogManager() + whenever(statsLogManager.logger()).thenReturn(statsLogger) + whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger) + + whenever(orientedState.orientationHandler).thenReturn(LandscapePagedViewHandler()) + taskView.setLayoutParams(ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)) + + if (enableRefactorTaskThumbnail()) { + val recentsDependencies = RecentsDependencies.maybeInitialize(launcher) + val scopeId = recentsDependencies.createRecentsViewScope(launcher) + recentsDependencies.provide( + RecentsRotationStateRepository::class.java, + scopeId, + { mock() } + ) + recentsDependencies.provide( + RecentsDeviceProfileRepository::class.java, + scopeId, + { mock() } + ) + } + } + + @After + fun tearDown() { + if (enableRefactorTaskThumbnail()) { + RecentsDependencies.destroy(launcher) + } + } + + /** + * When the corresponding feature flag is off, there will not be an option to open aspect ratio + * settings. + */ + @DisableFlags(com.android.window.flags.Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT) + @Test + fun createShortcut_flaggedOff_notCreated() { + val task = createTask() + val taskContainer = createTaskContainer(task) + + setScreenSizeDp(widthDp = 1200, heightDp = 800) + taskView.bind(task, orientedState, taskOverlayFactory) + + assertThat(factory.getShortcuts(launcher, taskContainer)).isNull() + } + + /** + * When the screen doesn't meet or exceed sw600dp (eg. phone, watch), there will not be an + * option to open aspect ratio settings. + */ + @EnableFlags(com.android.window.flags.Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT) + @Test + fun createShortcut_sw599dp_notCreated() { + val task = createTask() + val taskContainer = createTaskContainer(task) + + setScreenSizeDp(widthDp = 599, heightDp = 599) + taskView.bind(task, orientedState, taskOverlayFactory) + + assertThat(factory.getShortcuts(launcher, taskContainer)).isNull() + } + + /** + * When the screen does meet or exceed sw600dp (eg. tablet, inner foldable screen, home cinema) + * there will be an option to open aspect ratio settings. + */ + @EnableFlags(com.android.window.flags.Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT) + @Test + fun createShortcut_sw800dp_created_andOpensSettings() { + val task = createTask() + val taskContainer = spy(createTaskContainer(task)) + val taskViewItemInfo = mock() + doReturn(taskViewItemInfo).whenever(taskContainer).itemInfo + + setScreenSizeDp(widthDp = 1200, heightDp = 800) + taskView.bind(task, orientedState, taskOverlayFactory) + + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).hasSize(1) + + // On clicking the shortcut: + val shortcut = shortcuts!!.first() as AspectRatioSystemShortcut + shortcut.onClick(taskView) + + // 1) Panel should be closed + val allTypesExceptRebindSafe = + AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv() + verify(abstractFloatingViewHelper).closeOpenViews(launcher, true, allTypesExceptRebindSafe) + + // 2) Compat mode settings activity should be launched + val intentCaptor = argumentCaptor() + verify(launcher) + .startActivitySafely(any(), intentCaptor.capture(), eq(taskViewItemInfo)) + val intent = intentCaptor.firstValue!! + assertThat(intent.action).isEqualTo(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS) + + // 3) Shortcut tap event should be reported + verify(statsLogger).withItemInfo(taskViewItemInfo) + verify(statsLogger).log(LauncherEvent.LAUNCHER_ASPECT_RATIO_SETTINGS_SYSTEM_SHORTCUT_TAP) + } + + /** + * Overrides the screen size reported in the DeviceProfile, keeping the same pixel density as + * the underlying device and adjusting the pixel width/height to match what is required. + */ + private fun setScreenSizeDp(widthDp: Int, heightDp: Int) { + val density = context.resources.configuration.densityDpi + val widthPx = widthDp * density / 160 + val heightPx = heightDp * density / 160 + + val screenBounds = WindowBounds(widthPx, heightPx, widthPx, heightPx, Surface.ROTATION_0) + val deviceProfile = + InvariantDeviceProfile.INSTANCE[context].getDeviceProfile(context) + .toBuilder(context) + .setWindowBounds(screenBounds) + .build() + whenever(launcher.getDeviceProfile()).thenReturn(deviceProfile) + } + + /** Create a (very) fake task for testing. */ + private fun createTask() = + Task( + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + ComponentName("", ""), + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + ComponentName("", ""), + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ false, + ) + ) + + /** Create TaskContainer out of a given Task and fill in the rest with mocks. */ + private fun createTaskContainer(task: Task) = + TaskContainer( + taskView, + task, + if (enableRefactorTaskThumbnail()) mock() + else mock(), + mock(), + mock(), + SplitConfigurationOptions.STAGE_POSITION_UNDEFINED, + digitalWellBeingToast = null, + showWindowsView = null, + taskOverlayFactory, + ) +} diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt index 50b5df13f2e..8a2393d539f 100644 --- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt +++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt @@ -16,35 +16,50 @@ package com.android.quickstep +import android.Manifest.permission.SYSTEM_ALERT_WINDOW import android.content.ComponentName +import android.content.Context import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule -import com.android.dx.mockito.inline.extended.ExtendedMockito +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.platform.app.InstrumentationRegistry import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.internal.R import com.android.launcher3.AbstractFloatingView import com.android.launcher3.AbstractFloatingViewHelper +import com.android.launcher3.Flags.enableRefactorTaskThumbnail import com.android.launcher3.logging.StatsLogManager import com.android.launcher3.logging.StatsLogManager.LauncherEvent -import com.android.launcher3.model.data.WorkspaceItemInfo -import com.android.launcher3.uioverrides.QuickstepLauncher +import com.android.launcher3.model.data.TaskViewItemInfo import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.TransformingTouchDelegate import com.android.quickstep.TaskOverlayFactory.TaskOverlay +import com.android.quickstep.task.thumbnail.TaskThumbnailView import com.android.quickstep.views.LauncherRecentsView +import com.android.quickstep.views.RecentsViewContainer +import com.android.quickstep.views.TaskContainer import com.android.quickstep.views.TaskThumbnailViewDeprecated import com.android.quickstep.views.TaskView import com.android.quickstep.views.TaskViewIcon +import com.android.quickstep.views.TaskViewType 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.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource 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.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq @@ -54,25 +69,22 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -/** Test for DesktopSystemShortcut */ +/** Test for [DesktopSystemShortcut] */ +// TODO(b/403558856): Improve test coverage for DesktopModeCompatPolicy integration. class DesktopSystemShortcutTest { @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) - private val launcher: QuickstepLauncher = mock() + private val launcher: RecentsViewContainer = mock() private val statsLogManager: StatsLogManager = mock() private val statsLogger: StatsLogManager.StatsLogger = mock() private val recentsView: LauncherRecentsView = mock() - 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 overlayFactory: TaskOverlayFactory = mock() private val factory: TaskShortcutFactory = DesktopSystemShortcut.createFactory(abstractFloatingViewHelper) - private val overlayFactory: TaskOverlayFactory = mock() - private val overlay: TaskOverlay<*> = mock() + private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext) + private val taskView: TaskView = createTaskViewMock() private lateinit var mockitoSession: StaticMockitoSession @@ -81,11 +93,11 @@ class DesktopSystemShortcutTest { mockitoSession = mockitoSession() .strictness(Strictness.LENIENT) - .spyStatic(DesktopModeStatus::class.java) + .mockStatic(DesktopModeStatus::class.java) .startMocking() - ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() } - ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - whenever(overlayFactory.createOverlay(any())).thenReturn(overlay) + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + whenever(overlayFactory.createOverlay(any())).thenReturn(mock>()) + whenever(launcher.asContext()).thenReturn(context) } @After @@ -95,46 +107,148 @@ class DesktopSystemShortcutTest { @Test fun createDesktopTaskShortcutFactory_desktopModeDisabled() { - setFlagsRule.disableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + `when`(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) - val task = - Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { - isDockable = true - } - val taskContainer = createTaskContainer(task) + val taskContainer = createTaskContainer(createTask()) 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()) + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) + fun createDesktopTaskShortcutFactory_transparentTask() { + val baseComponent = ComponentName("", /* class */ "") + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + baseComponent, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + baseComponent, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ true, + ) + val taskContainer = createTaskContainer(Task(taskKey)) + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNull() + } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, + ) + fun createDesktopTaskShortcutFactoryPermissionEnabledAllowed_transparentTask() { + val packageManager: PackageManager = mock() + setUpTransparentPermission(packageManager, isAllowed = true) + val baseComponent = ComponentName("", /* class */ "") + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + baseComponent, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + baseComponent, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ true, + ) + val taskContainer = createTaskContainer(Task(taskKey)) val shortcuts = factory.getShortcuts(launcher, taskContainer) assertThat(shortcuts).isNull() } @Test - 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() } + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, + ) + fun createDesktopTaskShortcutFactoryPermissionEnabledNotAllowed_transparentTask() { + val packageManager: PackageManager = mock() + setUpTransparentPermission(packageManager, isAllowed = false) + val baseComponent = ComponentName("", /* class */ "") + val homeActivities = ComponentName("defaultHomePackage", /* class */ "") + whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities) + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + baseComponent, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + baseComponent, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ true, + ) + val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true }) + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNotEmpty() + } - val taskContainer = spy(createTaskContainer(createTask())) - doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun createDesktopTaskShortcutFactory_systemUiTask() { + val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi) + val baseComponent = ComponentName(sysUiPackageName, /* class */ "") + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + baseComponent, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + baseComponent, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ false, + ) + val taskContainer = createTaskContainer(Task(taskKey)) + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNull() + } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun createDesktopTaskShortcutFactory_defaultHomeTask() { + val packageManager: PackageManager = mock() + whenever(context.packageManager).thenReturn(packageManager) + val homeActivities = ComponentName("defaultHomePackage", /* class */ "") + whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities) + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + homeActivities, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + homeActivities, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ false, + ) + val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true }) val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).isNotNull() + assertThat(shortcuts).isNull() } @Test fun createDesktopTaskShortcutFactory_undockable() { - setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - val unDockableTask = createTask().apply { isDockable = false } val taskContainer = createTaskContainer(unDockableTask) @@ -144,8 +258,6 @@ class DesktopSystemShortcutTest { @Test fun desktopSystemShortcutClicked() { - setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - val task = createTask() val taskContainer = spy(createTaskContainer(task)) @@ -153,17 +265,19 @@ class DesktopSystemShortcutTest { whenever(launcher.statsLogManager).thenReturn(statsLogManager) whenever(statsLogManager.logger()).thenReturn(statsLogger) whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger) + whenever(taskView.context).thenReturn(context) whenever(recentsView.moveTaskToDesktop(any(), any(), any())).thenAnswer { val successCallback = it.getArgument(2) successCallback.run() } - doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo + val taskViewItemInfo = mock() + doReturn(taskViewItemInfo).whenever(taskContainer).itemInfo val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).hasSize(1) - assertThat(shortcuts!!.first()).isInstanceOf(DesktopSystemShortcut::class.java) + assertThat(shortcuts).isNotNull() + assertThat(shortcuts!!.single()).isInstanceOf(DesktopSystemShortcut::class.java) - val desktopShortcut = shortcuts.first() as DesktopSystemShortcut + val desktopShortcut = shortcuts.single() as DesktopSystemShortcut desktopShortcut.onClick(taskView) @@ -174,29 +288,64 @@ class DesktopSystemShortcutTest { .moveTaskToDesktop( eq(taskContainer), eq(DesktopModeTransitionSource.APP_FROM_OVERVIEW), - any() + any(), ) - verify(statsLogger).withItemInfo(workspaceItemInfo) + verify(statsLogger).withItemInfo(taskViewItemInfo) 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 createTask() = + Task( + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + ComponentName("", ""), + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + ComponentName("", ""), + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ false, + ) + ) + .apply { isDockable = true } - private fun createTaskContainer(task: Task): TaskView.TaskContainer { - return taskView.TaskContainer( + private fun createTaskContainer(task: Task) = + TaskContainer( + taskView, task, - thumbnailView = null, - thumbnailViewDeprecated, - iconView, - transformingTouchDelegate, + if (enableRefactorTaskThumbnail()) mock() + else mock(), + mock(), + mock(), SplitConfigurationOptions.STAGE_POSITION_UNDEFINED, digitalWellBeingToast = null, showWindowsView = null, - overlayFactory + overlayFactory, ) + + private fun setUpTransparentPermission(packageManager: PackageManager, isAllowed: Boolean) { + val packageInfo: PackageInfo = mock() + if (isAllowed) { + packageInfo.requestedPermissions = arrayOf(SYSTEM_ALERT_WINDOW) + } + whenever(context.packageManager).thenReturn(packageManager) + whenever( + packageManager.getPackageInfoAsUser( + anyString(), + eq(PackageManager.GET_PERMISSIONS), + anyInt(), + ) + ) + .thenReturn(packageInfo) + } + + private fun createTaskViewMock(): TaskView { + val taskView: TaskView = mock() + whenever(taskView.type).thenReturn(TaskViewType.SINGLE) + whenever(taskView.context).thenReturn(context) + return taskView } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java similarity index 100% rename from quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java rename to quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt new file mode 100644 index 00000000000..2db94f6f1af --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt @@ -0,0 +1,258 @@ +/* + * 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.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.platform.app.InstrumentationRegistry +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession +import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.internal.R +import com.android.launcher3.AbstractFloatingView +import com.android.launcher3.AbstractFloatingViewHelper +import com.android.launcher3.Flags.enableRefactorTaskThumbnail +import com.android.launcher3.logging.StatsLogManager +import com.android.launcher3.logging.StatsLogManager.LauncherEvent +import com.android.launcher3.model.data.TaskViewItemInfo +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.launcher3.util.TransformingTouchDelegate +import com.android.quickstep.TaskOverlayFactory.TaskOverlay +import com.android.quickstep.task.thumbnail.TaskThumbnailView +import com.android.quickstep.views.LauncherRecentsView +import com.android.quickstep.views.RecentsViewContainer +import com.android.quickstep.views.TaskContainer +import com.android.quickstep.views.TaskThumbnailViewDeprecated +import com.android.quickstep.views.TaskView +import com.android.quickstep.views.TaskViewIcon +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.Task.TaskKey +import com.android.window.flags.Flags +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito.`when` +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +/** Test for [ExternalDisplaySystemShortcut] */ +class ExternalDisplaySystemShortcutTest { + + @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) + + private val launcher: RecentsViewContainer = mock() + private val statsLogManager: StatsLogManager = mock() + private val statsLogger: StatsLogManager.StatsLogger = mock() + private val recentsView: LauncherRecentsView = mock() + private val taskView: TaskView = mock() + private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock() + private val overlayFactory: TaskOverlayFactory = mock() + private val factory: TaskShortcutFactory = + ExternalDisplaySystemShortcut.createFactory(abstractFloatingViewHelper) + private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext) + + private lateinit var mockitoSession: StaticMockitoSession + + @Before + fun setUp() { + mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(DesktopModeStatus::class.java) + .startMocking() + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + whenever(overlayFactory.createOverlay(any())).thenReturn(mock>()) + whenever(launcher.asContext()).thenReturn(context) + } + + @After + fun tearDown() { + mockitoSession.finishMocking() + } + + @Test + @EnableFlags(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT) + fun createExternalDisplayTaskShortcut_desktopModeDisabled() { + `when`(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) + + val taskContainer = createTaskContainer(createTask()) + + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNull() + } + + @Test + @EnableFlags( + Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + ) + @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) + fun createExternalDisplayTaskShortcut_transparentTask() { + val baseComponent = ComponentName("", /* class */ "") + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + baseComponent, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + baseComponent, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ true, + ) + val taskContainer = createTaskContainer(Task(taskKey)) + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNull() + } + + @Test + @EnableFlags( + Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + ) + fun createExternalDisplayTaskShortcut_systemUiTask() { + val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi) + val baseComponent = ComponentName(sysUiPackageName, /* class */ "") + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + baseComponent, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + baseComponent, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ false, + ) + val taskContainer = createTaskContainer(Task(taskKey)) + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNull() + } + + @Test + @EnableFlags( + Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + ) + fun createExternalDisplayTaskShortcut_defaultHomeTask() { + val packageManager: PackageManager = mock() + val homeActivities = ComponentName("defaultHomePackage", /* class */ "") + whenever(context.packageManager).thenReturn(packageManager) + whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities) + val taskKey = + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + homeActivities, + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + homeActivities, + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ false, + ) + val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true }) + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT) + fun externalDisplaySystemShortcutClicked() { + val task = createTask() + val taskContainer = spy(createTaskContainer(task)) + + whenever(launcher.getOverviewPanel()).thenReturn(recentsView) + whenever(launcher.statsLogManager).thenReturn(statsLogManager) + whenever(statsLogManager.logger()).thenReturn(statsLogger) + whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger) + whenever(recentsView.moveTaskToExternalDisplay(any(), any())).thenAnswer { + val successCallback = it.getArgument(1) + successCallback.run() + } + val taskViewItemInfo = mock() + doReturn(taskViewItemInfo).whenever(taskContainer).itemInfo + + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).hasSize(1) + assertThat(shortcuts!!.first()).isInstanceOf(ExternalDisplaySystemShortcut::class.java) + + val externalDisplayShortcut = shortcuts.first() as ExternalDisplaySystemShortcut + + externalDisplayShortcut.onClick(taskView) + + val allTypesExceptRebindSafe = + AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv() + verify(abstractFloatingViewHelper).closeOpenViews(launcher, true, allTypesExceptRebindSafe) + verify(recentsView).moveTaskToExternalDisplay(eq(taskContainer), any()) + verify(statsLogger).withItemInfo(taskViewItemInfo) + verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP) + } + + private fun createTask() = + Task( + TaskKey( + /* id */ 1, + /* windowingMode */ 0, + Intent(), + ComponentName("", ""), + /* userId */ 0, + /* lastActiveTime */ 2000, + DEFAULT_DISPLAY, + ComponentName("", ""), + /* numActivities */ 1, + /* isTopActivityNoDisplay */ false, + /* isActivityStackTransparent */ false, + ) + ) + + private fun createTaskContainer(task: Task) = + TaskContainer( + taskView, + task, + if (enableRefactorTaskThumbnail()) mock() + else mock(), + mock(), + mock(), + 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 28589291fdf..a4c9ef2d5ca 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -19,12 +19,11 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static com.android.launcher3.Flags.enableFallbackOverviewInWindow; import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS; import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage; import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess; -import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_ACTIVITY_TIMEOUT; import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_BROADCAST_TIMEOUT_SECS; -import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT; import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp; import static com.android.launcher3.ui.AbstractLauncherUiTest.startAppFast; import static com.android.launcher3.ui.AbstractLauncherUiTest.startTestActivity; @@ -56,6 +55,7 @@ import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.ui.AbstractLauncherUiTest; +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; @@ -64,7 +64,10 @@ import com.android.launcher3.util.rule.TestIsolationRule; import com.android.launcher3.util.rule.TestStabilityRule; import com.android.launcher3.util.rule.ViewCaptureRule; +import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener; +import com.android.quickstep.fallback.window.RecentsWindowManager; import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.RecentsViewContainer; import org.junit.After; import org.junit.Before; @@ -142,7 +145,7 @@ public void evaluate() throws Throwable { }; final ViewCaptureRule viewCaptureRule = new ViewCaptureRule( - RecentsActivity.ACTIVITY_TRACKER::getCreatedActivity); + RecentsActivity.ACTIVITY_TRACKER::getCreatedContext); mOrderSensitiveRules = RuleChain .outerRule(new SamplerRule()) .around(new TestStabilityRule()) @@ -192,29 +195,31 @@ public void goToOverviewFromHome() { @Test public void goToOverviewFromApp() { startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); - waitForRecentsActivityStop(); + waitForRecentsClosed(); mLauncher.getLaunchedAppState().switchToOverview(); } - protected void executeOnRecents(Consumer f) { + protected void executeOnRecents(Consumer f) { getFromRecents(r -> { f.accept(r); return true; }); } - protected T getFromRecents(Function f) { + protected T getFromRecents(Function f) { if (!TestHelpers.isInLauncherProcess()) return null; Object[] result = new Object[1]; Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> { - RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity(); - if (activity == null) { + RecentsViewContainer recentsViewContainer = enableFallbackOverviewInWindow() + ? RecentsWindowManager.getRecentsWindowTracker().getCreatedContext() + : RecentsActivity.ACTIVITY_TRACKER.getCreatedContext(); + if (recentsViewContainer == null) { return false; } - result[0] = f.apply(activity); + result[0] = f.apply(recentsViewContainer); return true; - }).get(), DEFAULT_UI_TIMEOUT, mLauncher); + }).get(), mLauncher); return (T) result[0]; } @@ -225,14 +230,19 @@ private BaseOverview pressHomeAndGoToOverview() { private void pressHomeAndWaitForOverviewClose() { mDevice.pressHome(); - waitForRecentsActivityStop(); + waitForRecentsClosed(); } - private void waitForRecentsActivityStop() { + private void waitForRecentsClosed() { try { - final boolean recentsActivityIsNull = MAIN_EXECUTOR.submit( - () -> RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity() == null).get(); - if (recentsActivityIsNull) { + final boolean isRecentsContainerNUll = MAIN_EXECUTOR.submit(() -> { + RecentsViewContainer recentsViewContainer = enableFallbackOverviewInWindow() + ? RecentsWindowManager.getRecentsWindowTracker().getCreatedContext() + : RecentsActivity.ACTIVITY_TRACKER.getCreatedContext(); + + return recentsViewContainer == null; + }).get(); + if (isRecentsContainerNUll) { // Null activity counts as a "stopped" one. return; } @@ -242,9 +252,9 @@ private void waitForRecentsActivityStop() { throw new RuntimeException(e); } - Wait.atMost("Recents activity didn't stop", + Wait.atMost("Recents view container didn't close", () -> getFromRecents(recents -> !recents.isStarted()), - DEFAULT_UI_TIMEOUT, mLauncher); + mLauncher); } @Test @@ -252,9 +262,10 @@ public void testOverview() throws IOException { startAppFast(getAppPackageName()); startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startTestActivity(2); - waitForRecentsActivityStop(); + waitForRecentsClosed(); Wait.atMost("Expected three apps in the task list", - () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher); + () -> mLauncher.getRecentTasks().size() >= 3, + mLauncher); checkTestLauncher(); BaseOverview overview = mLauncher.getLaunchedAppState().switchToOverview(); @@ -282,7 +293,7 @@ public void testOverview() throws IOException { assertNotNull("OverviewTask.open returned null", task.open()); assertTrue("Test activity didn't open from Overview", TestHelpers.wait(Until.hasObject( By.pkg(getAppPackageName()).text("TestActivity2")), - DEFAULT_UI_TIMEOUT)); + TestUtil.DEFAULT_UI_TIMEOUT)); // Test dismissing a task. @@ -312,37 +323,39 @@ private void checkTestLauncher() throws IOException { ); } - private int getCurrentOverviewPage(RecentsActivity recents) { - return recents.getOverviewPanel().getCurrentPage(); + private int getCurrentOverviewPage(RecentsViewContainer recentsViewContainer) { + return recentsViewContainer.getOverviewPanel().getCurrentPage(); } - private int getTaskCount(RecentsActivity recents) { - return recents.getOverviewPanel().getTaskViewCount(); + private int getTaskCount(RecentsViewContainer recentsViewContainer) { + return recentsViewContainer.getOverviewPanel().getTaskViewCount(); } - private class OverviewUpdateHandler { + private class OverviewUpdateHandler implements OverviewChangeListener { - final RecentsAnimationDeviceState mRads; final OverviewComponentObserver mObserver; final CountDownLatch mChangeCounter; OverviewUpdateHandler() { Context ctx = getInstrumentation().getTargetContext(); - mRads = new RecentsAnimationDeviceState(ctx); - mObserver = new OverviewComponentObserver(ctx, mRads); + mObserver = OverviewComponentObserver.INSTANCE.get(ctx); mChangeCounter = new CountDownLatch(1); if (mObserver.getHomeIntent().getComponent() .getPackageName().equals(mOtherLauncherActivity.packageName)) { // Home already same mChangeCounter.countDown(); } else { - mObserver.setOverviewChangeListener(b -> mChangeCounter.countDown()); + mObserver.addOverviewChangeListener(this); } } + @Override + public void onOverviewTargetChange(boolean isHomeAndOverviewSame) { + mChangeCounter.countDown(); + } + void destroy() { - mObserver.onDestroy(); - mRads.destroy(); + mObserver.removeOverviewChangeListener(this); } } } diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java new file mode 100644 index 00000000000..655560c0a66 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java @@ -0,0 +1,635 @@ +/* + * 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.quickstep.InputConsumerUtils.newBaseConsumer; +import static com.android.quickstep.InputConsumerUtils.newConsumer; + +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; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.kotlin.StubberKt.doCallRealMethod; + +import android.annotation.NonNull; +import android.os.Looper; +import android.view.Choreographer; +import android.view.Display; +import android.view.MotionEvent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.anim.AnimatedFloat; +import com.android.launcher3.dagger.LauncherAppComponent; +import com.android.launcher3.dagger.LauncherAppModule; +import com.android.launcher3.dagger.LauncherAppSingleton; +import com.android.launcher3.taskbar.TaskbarActivityContext; +import com.android.launcher3.taskbar.TaskbarManager; +import com.android.launcher3.taskbar.bubbles.BubbleBarController; +import com.android.launcher3.taskbar.bubbles.BubbleBarPinController; +import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController; +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController; +import com.android.launcher3.taskbar.bubbles.BubbleControllers; +import com.android.launcher3.taskbar.bubbles.BubbleCreator; +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.BubbleStashedHandleViewController; +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; +import com.android.launcher3.util.LockedUserState; +import com.android.launcher3.util.SandboxApplication; +import com.android.launcher3.views.BaseDragLayer; +import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; +import com.android.quickstep.inputconsumers.BubbleBarInputConsumer; +import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; +import com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer; +import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer; +import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; +import com.android.quickstep.inputconsumers.OverviewInputConsumer; +import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer; +import com.android.quickstep.inputconsumers.ProgressDelegateInputConsumer; +import com.android.quickstep.inputconsumers.ResetGestureInputConsumer; +import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; +import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer; +import com.android.quickstep.inputconsumers.TrackpadStatusBarInputConsumer; +import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.NavBarPosition; +import com.android.quickstep.views.RecentsViewContainer; +import com.android.systemui.shared.system.InputChannelCompat; +import com.android.systemui.shared.system.InputMonitorCompat; + +import dagger.BindsInstance; +import dagger.Component; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Optional; +import java.util.function.Function; + +import javax.inject.Provider; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class InputConsumerUtilsTest { + + @Rule public final SandboxApplication mContext = new SandboxApplication(); + + private final int mDisplayId = Display.DEFAULT_DISPLAY; + @NonNull private final InputMonitorCompat mInputMonitorCompat = + new InputMonitorCompat("", mDisplayId); + + private TaskAnimationManager mTaskAnimationManager; + private InputChannelCompat.InputEventReceiver mInputEventReceiver; + private boolean mUserUnlocked = true; + @NonNull private Function mSwipeUpProxyProvider = (state) -> null; + + @NonNull @Mock private TaskbarActivityContext mTaskbarActivityContext; + @NonNull @Mock private OverviewComponentObserver mOverviewComponentObserver; + @NonNull @Mock private RecentsAnimationDeviceState mDeviceState; + @NonNull @Mock private AbsSwipeUpHandler.Factory mSwipeUpHandlerFactory; + @NonNull @Mock private TaskbarManager mTaskbarManager; + @NonNull @Mock private OverviewCommandHelper mOverviewCommandHelper; + @NonNull @Mock private GestureState mPreviousGestureState; + @NonNull @Mock private GestureState mCurrentGestureState; + @NonNull @Mock private LockedUserState mLockedUserState; + @NonNull @Mock private TopTaskTracker.CachedTaskInfo mRunningTask; + @NonNull @Mock private BaseContainerInterface mContainerInterface; + @NonNull @Mock private BaseDragLayer mBaseDragLayer; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Before + public void setupTaskAnimationManager() { + mTaskAnimationManager = new TaskAnimationManager(mContext, mDeviceState, mDisplayId); + } + + @Before + public void setupDaggerGraphOverrides() { + mContext.initDaggerComponent(DaggerInputConsumerUtilsTest_TestComponent + .builder() + .bindLockedState(mLockedUserState) + .bindRotationHelper(mock(RotationTouchHelper.class)) + .bindRecentsState(mDeviceState)); + } + + @Before + public void setUpInputEventReceiver() { + runOnMainSync(() -> + mInputEventReceiver = mInputMonitorCompat.getInputReceiver( + Looper.getMainLooper(), + Choreographer.getInstance(), + event -> {})); + } + + @Before + public void setUpTaskbarActivityContext() { + NavHandle navHandle = mock(NavHandle.class); + + when(navHandle.canNavHandleBeLongPressed()).thenReturn(true); + + when(mTaskbarActivityContext.getDeviceProfile()).thenReturn(new DeviceProfile()); + when(mTaskbarActivityContext.getNavHandle()).thenReturn(navHandle); + } + + @Before + public void setUpTaskbarManager() { + when(mTaskbarManager.getCurrentActivityContext()).thenReturn(mTaskbarActivityContext); + } + + @Before + public void setupLockedUserState() { + when(mLockedUserState.isUserUnlocked()).thenReturn(true); + } + + @Before + public void setupGestureStates() { + when(mCurrentGestureState.getRunningTask()).thenReturn(mRunningTask); + doReturn(mContainerInterface).when(mCurrentGestureState).getContainerInterface(); + } + + @Before + public void setUpContainerInterface() { + RecentsViewContainer recentsViewContainer = mock(RecentsViewContainer.class); + + when(recentsViewContainer.getDragLayer()).thenReturn(mBaseDragLayer); + when(recentsViewContainer.getRootView()).thenReturn(mBaseDragLayer); + when(recentsViewContainer.asContext()).thenReturn(mContext); + + doReturn(recentsViewContainer).when(mContainerInterface).getCreatedContainer(); + } + + @Before + public void setupBaseDragLayer() { + when(mBaseDragLayer.hasWindowFocus()).thenReturn(true); + } + + @Before + public void setupDeviceState() { + when(mDeviceState.canStartTrackpadGesture()).thenReturn(true); + when(mDeviceState.canStartSystemGesture()).thenReturn(true); + when(mDeviceState.isFullyGesturalNavMode()).thenReturn(true); + when(mDeviceState.getNavBarPosition()).thenReturn(mock(NavBarPosition.class)); + } + + @After + public void cleanUp() { + mInputMonitorCompat.dispose(); + mInputEventReceiver.dispose(); + } + + @Test + public void testNewBaseConsumer_onKeyguard_returnsDeviceLockedInputConsumer() { + when(mDeviceState.isKeyguardShowingOccluded()).thenReturn(true); + + assertCorrectInputConsumer( + this::createBaseInputConsumer, + DeviceLockedInputConsumer.class, + InputConsumer.TYPE_DEVICE_LOCKED); + } + + @Test + public void testNewBaseConsumer_onLiveTileModeWithNoContainer_returnsDefaultInputConsumer() { + when(mContainerInterface.isInLiveTileMode()).thenReturn(true); + when(mContainerInterface.getCreatedContainer()).thenReturn(null); + + assertEqualsDefaultInputConsumer(this::createBaseInputConsumer); + } + + @Test + public void testNewBaseConsumer_onLiveTileMode_returnsOverviewInputConsumer() { + when(mContainerInterface.isInLiveTileMode()).thenReturn(true); + + assertCorrectInputConsumer( + this::createBaseInputConsumer, + OverviewInputConsumer.class, + InputConsumer.TYPE_OVERVIEW); + } + + @Test + public void testNewBaseConsumer_withNoRunningTask_returnsDefaultInputConsumer() { + when(mCurrentGestureState.getRunningTask()).thenReturn(null); + + assertEqualsDefaultInputConsumer(this::createBaseInputConsumer); + } + + @Test + public void testNewBaseConsumer_prevGestureAnimatingToLauncher_returnsOverviewInputConsumer() { + when(mPreviousGestureState.isRunningAnimationToLauncher()).thenReturn(true); + + assertCorrectInputConsumer( + this::createBaseInputConsumer, + OverviewInputConsumer.class, + InputConsumer.TYPE_OVERVIEW); + } + + @Test + public void testNewBaseConsumer_predictiveBackToHomeInProgress_returnsOverviewInputConsumer() { + when(mDeviceState.isPredictiveBackToHomeInProgress()).thenReturn(true); + + assertCorrectInputConsumer( + this::createBaseInputConsumer, + OverviewInputConsumer.class, + InputConsumer.TYPE_OVERVIEW); + } + + @Test + public void testNewBaseConsumer_resumedThroughShellTransition_returnsOverviewInputConsumer() { + when(mContainerInterface.isResumed()).thenReturn(true); + + assertCorrectInputConsumer( + this::createBaseInputConsumer, + OverviewInputConsumer.class, + InputConsumer.TYPE_OVERVIEW); + } + + @Test + public void testNewBaseConsumer_shellNoWindowFocus_returnsOverviewWithoutFocusInputConsumer() { + when(mContainerInterface.isResumed()).thenReturn(true); + when(mBaseDragLayer.hasWindowFocus()).thenReturn(false); + + assertCorrectInputConsumer( + this::createBaseInputConsumer, + OverviewWithoutFocusInputConsumer.class, + InputConsumer.TYPE_OVERVIEW_WITHOUT_FOCUS); + } + + @Test + public void testNewBaseConsumer_forceOverviewInputConsumer_returnsOverviewInputConsumer() { + when(mContainerInterface.isResumed()).thenReturn(true); + when(mRunningTask.isRootChooseActivity()).thenReturn(true); + + assertCorrectInputConsumer( + this::createBaseInputConsumer, + OverviewInputConsumer.class, + InputConsumer.TYPE_OVERVIEW); + } + + @Test + public void testNewBaseConsumer_launcherChildActivityResumed_returnsDefaultInputConsumer() { + when(mRunningTask.isHomeTask()).thenReturn(true); + when(mOverviewComponentObserver.isHomeAndOverviewSameActivity()).thenReturn(true); + + assertEqualsDefaultInputConsumer(this::createBaseInputConsumer); + } + + @Test + public void testNewBaseConsumer_onGestureBlockedTask_returnsDefaultInputConsumer() { + when(mDeviceState.isGestureBlockedTask(any())).thenReturn(true); + + assertEqualsDefaultInputConsumer(this::createBaseInputConsumer); + } + + @Test + public void testNewBaseConsumer_noGestureBlockedTask_returnsOtherActivityInputConsumer() { + doCallRealMethod().when(mDeviceState).setGestureBlockingTaskId(anyInt()); + mDeviceState.setGestureBlockingTaskId(-1); + when(mDeviceState.isGestureBlockedTask(any())).thenCallRealMethod(); + + assertCorrectInputConsumer(this::createBaseInputConsumer, OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY); + } + + @Test + public void testNewBaseConsumer_containsOtherActivityInputConsumer() { + assertCorrectInputConsumer( + this::createBaseInputConsumer, + OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY); + } + + @Test + public void testNewConsumer_containsOtherActivityInputConsumer() { + assertCorrectInputConsumer( + this::createInputConsumer, + NavHandleLongPressInputConsumer.class, + OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY | InputConsumer.TYPE_NAV_HANDLE_LONG_PRESS); + } + + @Test + public void testNewConsumer_eventCanTriggerAssistantAction_containsAssistantInputConsumer() { + when(mDeviceState.canTriggerAssistantAction(any())).thenReturn(true); + + assertCorrectInputConsumer( + this::createInputConsumer, + NavHandleLongPressInputConsumer.class, + OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY + | InputConsumer.TYPE_NAV_HANDLE_LONG_PRESS + | InputConsumer.TYPE_ASSISTANT); + } + + @Test + public void testNewConsumer_taskbarIsPresent_containsTaskbarUnstashInputConsumer() { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.isTaskbarPresent = true; + when(mTaskbarActivityContext.getDeviceProfile()).thenReturn(deviceProfile); + + assertCorrectInputConsumer( + this::createInputConsumer, + NavHandleLongPressInputConsumer.class, + OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY + | InputConsumer.TYPE_TASKBAR_STASH + | InputConsumer.TYPE_NAV_HANDLE_LONG_PRESS + | InputConsumer.TYPE_CURSOR_HOVER); + } + + @Test + public void testNewConsumer_whileSystemUiDialogShowing_returnsSysUiOverlayInputConsumer() { + when(mDeviceState.isSystemUiDialogShowing()).thenReturn(true); + + assertCorrectInputConsumer( + this::createInputConsumer, + SysUiOverlayInputConsumer.class, + InputConsumer.TYPE_SYSUI_OVERLAY); + } + + @Test + public void testNewConsumer_onTrackpadGesture_returnsTrackpadStatusBarInputConsumer() { + when(mCurrentGestureState.isTrackpadGesture()).thenReturn(true); + + assertCorrectInputConsumer( + this::createInputConsumer, + TrackpadStatusBarInputConsumer.class, + OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY + | InputConsumer.TYPE_NAV_HANDLE_LONG_PRESS + | InputConsumer.TYPE_STATUS_BAR); + } + + @Test + public void testNewConsumer_whileScreenPinningActive_returnsScreenPinnedInputConsumer() { + when(mDeviceState.isScreenPinningActive()).thenReturn(true); + + assertCorrectInputConsumer( + this::createInputConsumer, + ScreenPinnedInputConsumer.class, + InputConsumer.TYPE_SCREEN_PINNED); + } + + @Test + public void testNewConsumer_canTriggerOneHandedAction_returnsOneHandedModeInputConsumer() { + when(mDeviceState.canTriggerOneHandedAction(any())).thenReturn(true); + + assertCorrectInputConsumer( + this::createInputConsumer, + OneHandedModeInputConsumer.class, + OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY + | InputConsumer.TYPE_NAV_HANDLE_LONG_PRESS + | InputConsumer.TYPE_ONE_HANDED); + } + + @Test + public void testNewConsumer_accessibilityMenuAvailable_returnsAccessibilityInputConsumer() { + when(mDeviceState.isAccessibilityMenuAvailable()).thenReturn(true); + + assertCorrectInputConsumer( + this::createInputConsumer, + AccessibilityInputConsumer.class, + OtherActivityInputConsumer.class, + InputConsumer.TYPE_OTHER_ACTIVITY + | InputConsumer.TYPE_NAV_HANDLE_LONG_PRESS + | InputConsumer.TYPE_ACCESSIBILITY); + } + + @Test + public void testNewConsumer_onStashedBubbleBar_returnsBubbleBarInputConsumer() { + BubbleControllers bubbleControllers = createBubbleControllers(/* isStashed= */ true); + + when(mTaskbarActivityContext.isBubbleBarEnabled()).thenReturn(true); + when(mTaskbarActivityContext.getBubbleControllers()).thenReturn(bubbleControllers); + + assertCorrectInputConsumer( + this::createInputConsumer, + BubbleBarInputConsumer.class, + InputConsumer.TYPE_BUBBLE_BAR); + } + + @Test + public void testNewConsumer_onVisibleBubbleBar_returnsBubbleBarInputConsumer() { + BubbleControllers bubbleControllers = createBubbleControllers(/* isStashed= */ false); + + when(mTaskbarActivityContext.isBubbleBarEnabled()).thenReturn(true); + when(mTaskbarActivityContext.getBubbleControllers()).thenReturn(bubbleControllers); + + assertCorrectInputConsumer( + this::createInputConsumer, + BubbleBarInputConsumer.class, + InputConsumer.TYPE_BUBBLE_BAR); + } + + @Test + public void testNewConsumer_withSwipeUpProxyProvider_returnsProgressDelegateInputConsumer() { + mSwipeUpProxyProvider = (state) -> new AnimatedFloat(); + + assertCorrectInputConsumer( + this::createInputConsumer, + ProgressDelegateInputConsumer.class, + InputConsumer.TYPE_PROGRESS_DELEGATE); + } + + @Test + public void testNewConsumer_onLockedState_returnsDeviceLockedInputConsumer() { + when(mLockedUserState.isUserUnlocked()).thenReturn(false); + + assertCorrectInputConsumer( + this::createInputConsumer, + DeviceLockedInputConsumer.class, + InputConsumer.TYPE_DEVICE_LOCKED); + } + + @Test + public void testNewConsumer_cannotStartSysGestureOnLockedState_returnsDefaultInputConsumer() { + when(mLockedUserState.isUserUnlocked()).thenReturn(false); + when(mDeviceState.canStartSystemGesture()).thenReturn(false); + + assertEqualsDefaultInputConsumer(this::createInputConsumer); + } + + @Test + public void testNewConsumer_cannotStartTrackGestureOnLockedState_returnsDefaultInputConsumer() { + when(mLockedUserState.isUserUnlocked()).thenReturn(false); + when(mCurrentGestureState.isTrackpadGesture()).thenReturn(true); + when(mDeviceState.canStartTrackpadGesture()).thenReturn(false); + + assertEqualsDefaultInputConsumer(this::createInputConsumer); + } + + private InputConsumer createInputConsumer() { + MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + InputConsumer inputConsumer = newConsumer( + mContext, + mUserUnlocked, + mOverviewComponentObserver, + mDeviceState, + mPreviousGestureState, + mCurrentGestureState, + mTaskAnimationManager, + mInputMonitorCompat, + mSwipeUpHandlerFactory, + otherActivityInputConsumer -> {}, + mInputEventReceiver, + mTaskbarManager, + mSwipeUpProxyProvider, + mOverviewCommandHelper, + event); + + event.recycle(); + + return inputConsumer; + } + + private InputConsumer createBaseInputConsumer() { + MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + InputConsumer inputConsumer = newBaseConsumer( + mContext, + mUserUnlocked, + mTaskbarManager, + mOverviewComponentObserver, + mDeviceState, + mPreviousGestureState, + mCurrentGestureState, + mTaskAnimationManager, + mInputMonitorCompat, + mSwipeUpHandlerFactory, + otherActivityInputConsumer -> {}, + mInputEventReceiver, + event, + ActiveGestureLog.CompoundString.NO_OP); + + event.recycle(); + + return inputConsumer; + } + + private void assertEqualsDefaultInputConsumer( + @NonNull Provider inputConsumerProvider) { + assertCorrectInputConsumer( + inputConsumerProvider, + ResetGestureInputConsumer.class, + InputConsumer.TYPE_RESET_GESTURE); + + mUserUnlocked = false; + + assertCorrectInputConsumer( + inputConsumerProvider, + InputConsumer.class, + InputConsumer.TYPE_NO_OP); + } + + private void assertCorrectInputConsumer( + @NonNull Provider inputConsumerProvider, + @NonNull Class expectedOutputConsumer, + int expectedType) { + assertCorrectInputConsumer( + inputConsumerProvider, + expectedOutputConsumer, + expectedOutputConsumer, + expectedType); + } + + private void assertCorrectInputConsumer( + @NonNull Provider inputConsumerProvider, + @NonNull Class expectedOutputConsumer, + @NonNull Class expectedActiveConsumer, + int expectedType) { + when(mCurrentGestureState.getDisplayId()).thenReturn(mDisplayId); + + runOnMainSync(() -> { + InputConsumer inputConsumer = inputConsumerProvider.get(); + + assertThat(inputConsumer).isInstanceOf(expectedOutputConsumer); + assertThat(inputConsumer.getActiveConsumerInHierarchy()) + .isInstanceOf(expectedActiveConsumer); + assertThat(inputConsumer.getType()).isEqualTo(expectedType); + assertThat(inputConsumer.getDisplayId()).isEqualTo(mDisplayId); + }); + int expectedDisplayId = mDisplayId + 1; + + when(mCurrentGestureState.getDisplayId()).thenReturn(expectedDisplayId); + + runOnMainSync(() -> assertThat(inputConsumerProvider.get().getDisplayId()) + .isEqualTo(expectedDisplayId)); + } + + private static void runOnMainSync(@NonNull Runnable runnable) { + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } + + private static BubbleControllers createBubbleControllers(boolean isStashed) { + BubbleBarController bubbleBarController = mock(BubbleBarController.class); + BubbleBarViewController bubbleBarViewController = mock(BubbleBarViewController.class); + BubbleStashController bubbleStashController = mock(BubbleStashController.class); + BubbleStashedHandleViewController bubbleStashedHandleViewController = + mock(BubbleStashedHandleViewController.class); + BubbleDragController bubbleDragController = mock(BubbleDragController.class); + BubbleDismissController bubbleDismissController = mock(BubbleDismissController.class); + BubbleBarPinController bubbleBarPinController = mock(BubbleBarPinController.class); + BubblePinController bubblePinController = mock(BubblePinController.class); + BubbleBarSwipeController bubbleBarSwipeController = mock(BubbleBarSwipeController.class); + BubbleCreator bubbleCreator = mock(BubbleCreator.class); + BubbleControllers bubbleControllers = new BubbleControllers( + bubbleBarController, + bubbleBarViewController, + bubbleStashController, + Optional.of(bubbleStashedHandleViewController), + bubbleDragController, + bubbleDismissController, + bubbleBarPinController, + bubblePinController, + Optional.of(bubbleBarSwipeController), + bubbleCreator); + + when(bubbleBarViewController.hasBubbles()).thenReturn(true); + when(bubbleStashController.isStashed()).thenReturn(isStashed); + when(bubbleStashedHandleViewController.isEventOverHandle(any())).thenReturn(true); + when(bubbleBarViewController.isBubbleBarVisible()).thenReturn(!isStashed); + when(bubbleBarViewController.isEventOverBubbleBar(any())).thenReturn(true); + + return bubbleControllers; + } + + @LauncherAppSingleton + @Component(modules = {LauncherAppModule.class}) + interface TestComponent extends LauncherAppComponent { + @Component.Builder + interface Builder extends LauncherAppComponent.Builder { + @BindsInstance Builder bindLockedState(LockedUserState state); + @BindsInstance Builder bindRotationHelper(RotationTouchHelper helper); + @BindsInstance Builder bindRecentsState(RecentsAnimationDeviceState state); + + @Override + TestComponent build(); + } + } +} diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index 4459ed69447..c713c3dc9a1 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -16,7 +16,7 @@ package com.android.quickstep; -import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL; import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON; @@ -26,6 +26,7 @@ import android.content.Context; import android.content.pm.PackageManager; +import android.os.Process; import android.util.Log; import androidx.test.uiautomator.UiDevice; @@ -57,8 +58,6 @@ public class NavigationModeSwitchRule implements TestRule { static final String TAG = "QuickStepOnOffRule"; - public static final int WAIT_TIME_MS = 10000; - public enum Mode { THREE_BUTTON, ZERO_BUTTON, ALL } @@ -155,7 +154,9 @@ public static boolean setActiveOverlay(LauncherInstrumentation launcher, String Log.d(TAG, "setActiveOverlay: " + overlayPackage + "..."); UiDevice.getInstance(getInstrumentation()).executeShellCommand( - "cmd overlay enable-exclusive --category " + overlayPackage); + String.format("cmd overlay enable-exclusive --user %d --category %s", + Process.myUserHandle().getIdentifier(), + overlayPackage)); if (currentSysUiNavigationMode() != expectedMode) { final CountDownLatch latch = new CountDownLatch(1); @@ -179,12 +180,13 @@ public static boolean setActiveOverlay(LauncherInstrumentation launcher, String } Wait.atMost("Couldn't switch to " + overlayPackage, - () -> launcher.getNavigationModel() == expectedMode, WAIT_TIME_MS, launcher); + () -> launcher.getNavigationModel() == expectedMode, + launcher); Wait.atMost(() -> "Switching nav mode: " + launcher.getNavigationModeMismatchError(false), () -> launcher.getNavigationModeMismatchError(false) == null, - WAIT_TIME_MS, launcher); + launcher); AbstractLauncherUiTest.checkDetectedLeaks(launcher, false); return true; } diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java index a738e768336..154d86d9770 100644 --- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java +++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java @@ -21,6 +21,7 @@ import static com.android.launcher3.util.NavigationMode.NO_BUTTON; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -41,6 +42,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.launcher3.testing.shared.ResourceUtils; +import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.RotationUtils; import com.android.launcher3.util.WindowBounds; @@ -88,7 +91,7 @@ public void disabledMultipleRegions_shouldOverrideFirstRegion() { float landscapeRegionY = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); tapAndAssertTrue(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertFalse(100, landscapeRegionY, @@ -100,7 +103,8 @@ public void disabledMultipleRegions_shouldOverrideFirstRegion() { // Override region mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), + "test"); tapAndAssertFalse(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertTrue(100, landscapeRegionY, @@ -111,7 +115,7 @@ public void disabledMultipleRegions_shouldOverrideFirstRegion() { event -> mTouchTransformer.touchInAssistantRegion(event)); // Override region again - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); tapAndAssertTrue(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertFalse(100, landscapeRegionY, @@ -130,7 +134,8 @@ public void enableMultipleRegions_shouldOverrideFirstRegion() { generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), + "test"); tapAndAssertFalse(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertTrue(100, landscapeRegionY, @@ -142,7 +147,7 @@ public void enableMultipleRegions_shouldOverrideFirstRegion() { // We have to add 0 rotation second so that gets set as the current rotation, otherwise // matrix transform will fail (tests only work in Portrait at the moment) mTouchTransformer.enableMultipleRegions(true, mInfo); - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); tapAndAssertTrue(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -163,8 +168,9 @@ public void enableMultipleRegions_assistantTriggersInMostRecent() { mTouchTransformer.enableMultipleRegions(true, mInfo); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); - mTouchTransformer.createOrAddTouchRegion(mInfo); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), + "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); tapAndAssertTrue(0, portraitRegionY, event -> mTouchTransformer.touchInAssistantRegion(event)); tapAndAssertFalse(0, landscapeRegionY, @@ -179,9 +185,10 @@ public void enableMultipleRegions_assistantTriggersInCurrentOrientationAfterDisa generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; mTouchTransformer.enableMultipleRegions(true, mInfo); - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), + "test"); mTouchTransformer.enableMultipleRegions(false, mInfo); tapAndAssertTrue(0, portraitRegionY, event -> mTouchTransformer.touchInAssistantRegion(event)); @@ -211,14 +218,14 @@ public void assistantTriggersInCurrentScreenAfterScreenSizeChange() { @Test public void applyTransform_taskNotFrozen_notInRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); tapAndAssertFalse(100, 100, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); } @Test public void applyTransform_taskFrozen_noRotate_outOfRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); mTouchTransformer.enableMultipleRegions(true, mInfo); tapAndAssertFalse(100, 100, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -226,7 +233,7 @@ public void applyTransform_taskFrozen_noRotate_outOfRegion() { @Test public void applyTransform_taskFrozen_noRotate_inRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); mTouchTransformer.enableMultipleRegions(true, mInfo); float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1; tapAndAssertTrue(100, y, @@ -235,7 +242,7 @@ public void applyTransform_taskFrozen_noRotate_inRegion() { @Test public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1; tapAndAssertTrue(100, y, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -244,7 +251,8 @@ public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() { @Test public void applyTransform_taskNotFrozen_90Rotate_inRegion() { mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), + "test"); float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; tapAndAssertTrue(100, y, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -252,10 +260,11 @@ public void applyTransform_taskNotFrozen_90Rotate_inRegion() { @Test public void applyTransform_taskNotFrozen_90Rotate_withTwoRegions() { - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); mTouchTransformer.enableMultipleRegions(true, mInfo); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), + "test"); // Landscape point float y1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1); @@ -276,10 +285,11 @@ public void applyTransform_taskNotFrozen_90Rotate_withTwoRegions() { @Test public void applyTransform_90Rotate_inRotatedRegion() { // Create regions for both 0 Rotation and 90 Rotation - mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); mTouchTransformer.enableMultipleRegions(true, mInfo); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), + "test"); // Portrait point in landscape orientation axis float x1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0); // bottom of screen, from landscape perspective right side of screen @@ -287,6 +297,35 @@ public void applyTransform_90Rotate_inRotatedRegion() { assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY())); } + @Test + public void testSimpleOrientationTouchTransformer() { + final DisplayController displayController = mock(DisplayController.class); + doReturn(mInfo).when(displayController).getInfoForDisplay(anyInt()); + final SimpleOrientationTouchTransformer transformer = + new SimpleOrientationTouchTransformer(getApplicationContext(), displayController, + mock(DaggerSingletonTracker.class)); + final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10); + transformer.transform(move1, Surface.ROTATION_90); + // The position is transformed to 90 degree. + assertEquals(10, move1.getX(), 0f /* delta */); + assertEquals(NORMAL_SCREEN_SIZE.getWidth() - 100, move1.getY(), 0f /* delta */); + + // If the touching state is specified, the position is still transformed to 90 degree even + // if the given rotation is changed. + final MotionEvent move2 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10); + transformer.updateTouchingOrientation(Surface.ROTATION_90); + transformer.transform(move2, Surface.ROTATION_0); + assertEquals(move1.getX(), move2.getX(), 0f /* delta */); + assertEquals(move1.getY(), move2.getY(), 0f /* delta */); + + // If the touching state is cleared, it restores to use the given rotation. + final MotionEvent move3 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10); + transformer.clearTouchingOrientation(); + transformer.transform(move3, Surface.ROTATION_0); + assertEquals(100, move3.getX(), 0f /* delta */); + assertEquals(10, move3.getY(), 0f /* delta */); + } + private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) { Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight()); RotationUtils.rotateSize(displaySize, rotation); diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java deleted file mode 100644 index 03244eb0bf8..00000000000 --- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java +++ /dev/null @@ -1,107 +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 junit.framework.TestCase.assertNull; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.app.KeyguardManager; - -import androidx.test.filters.SmallTest; - -import com.android.launcher3.util.LooperExecutor; -import com.android.quickstep.util.GroupTask; -import com.android.wm.shell.util.GroupedRecentTaskInfo; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@SmallTest -public class RecentTasksListTest { - - @Mock - private SystemUiProxy mockSystemUiProxy; - @Mock - private TopTaskTracker mTopTaskTracker; - - // Class under test - private RecentTasksList mRecentTasksList; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class); - KeyguardManager mockKeyguardManager = mock(KeyguardManager.class); - mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager, - mockSystemUiProxy, mTopTaskTracker); - } - - @Test - public void onRecentTasksChanged_doesNotFetchTasks() { - mRecentTasksList.onRecentTasksChanged(); - verify(mockSystemUiProxy, times(0)) - .getRecentTasks(anyInt(), anyInt()); - } - - @Test - public void loadTasksInBackground_onlyKeys_noValidTaskDescription() { - GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks( - new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null); - when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) - .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); - - List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, - true); - - assertEquals(1, taskList.size()); - assertNull(taskList.get(0).task1.taskDescription.getLabel()); - assertNull(taskList.get(0).task2.taskDescription.getLabel()); - } - - @Test - public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() { - String taskDescription = "Wheeee!"; - ActivityManager.RecentTaskInfo task1 = new ActivityManager.RecentTaskInfo(); - task1.taskDescription = new ActivityManager.TaskDescription(taskDescription); - ActivityManager.RecentTaskInfo task2 = new ActivityManager.RecentTaskInfo(); - task2.taskDescription = new ActivityManager.TaskDescription(); - GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(task1, task2, - null); - when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) - .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); - - List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, - false); - - assertEquals(1, taskList.size()); - assertEquals(taskDescription, taskList.get(0).task1.taskDescription.getLabel()); - assertNull(taskList.get(0).task2.taskDescription.getLabel()); - } -} diff --git a/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt new file mode 100644 index 00000000000..418d66c6161 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep + +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession +import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.launcher3.FakeInvariantDeviceProfileTest +import com.android.quickstep.recents.data.RecentsDeviceProfile +import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl +import com.android.quickstep.views.RecentsViewContainer +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +class RecentsDeviceProfileRepositoryImplTest : FakeInvariantDeviceProfileTest() { + private val recentsViewContainer: RecentsViewContainer = mock() + + private lateinit var mockitoSession: StaticMockitoSession + + @Before + override fun setUp() { + super.setUp() + mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(DesktopModeStatus::class.java) + .startMocking() + whenever(recentsViewContainer.asContext()).thenReturn(context) + } + + @After + fun tearDown() { + mockitoSession.finishMocking() + } + + @Test + fun deviceProfileMappedCorrectlyForPhone() { + val deviceProfileRepo = RecentsDeviceProfileRepositoryImpl(recentsViewContainer) + initializeVarsForPhone() + val phoneDeviceProfile = newDP() + whenever(recentsViewContainer.deviceProfile).thenReturn(phoneDeviceProfile) + + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) + assertThat(deviceProfileRepo.getRecentsDeviceProfile()) + .isEqualTo(RecentsDeviceProfile(isLargeScreen = false, canEnterDesktopMode = false)) + } + + @Test + fun deviceProfileMappedCorrectlyForTablet() { + val deviceProfileRepo = RecentsDeviceProfileRepositoryImpl(recentsViewContainer) + initializeVarsForTablet() + val tabletDeviceProfile = newDP() + whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile) + + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + assertThat(deviceProfileRepo.getRecentsDeviceProfile()) + .isEqualTo(RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true)) + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java index b7fd8be3118..2c275f45eed 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java +++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java @@ -15,20 +15,18 @@ */ 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 org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.content.Intent; import android.platform.test.annotations.PlatinumTest; -import com.android.launcher3.tapl.OverviewTask.OverviewSplitTask; +import com.android.launcher3.tapl.Overview; +import com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer; 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 com.android.quickstep.util.SplitScreenTestUtils; import org.junit.Test; @@ -69,41 +67,18 @@ private void startTestApps() { } @Test - @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/288939273 public void testSplitTaskTapBothIconMenus() { - createAndLaunchASplitPair(); + Overview overview = SplitScreenTestUtils.createAndLaunchASplitPairInOverview(mLauncher); - OverviewTaskMenu taskMenu = - mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu(); + OverviewTaskMenu taskMenu = overview.getCurrentTask().tapMenu(); assertTrue("App info item not appearing in expanded task menu.", taskMenu.hasMenuItem("App info")); taskMenu.touchOutsideTaskMenuToDismiss(); - OverviewTaskMenu splitMenu = - mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu( - OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT); + OverviewTaskMenu splitMenu = overview.getCurrentTask().tapMenu( + OverviewTaskContainer.SPLIT_BOTTOM_OR_RIGHT); assertTrue("App info item not appearing in expanded split task's menu.", splitMenu.hasMenuItem("App info")); splitMenu.touchOutsideTaskMenuToDismiss(); } - - private void createAndLaunchASplitPair() { - clearAllRecentTasks(); - - startTestActivity(2); - startTestActivity(3); - - if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) { - mLauncher.goHome().switchToOverview().getOverviewActions() - .clickSplit() - .getTestActivityTask(2) - .open(); - } else { - mLauncher.goHome().switchToOverview().getCurrentTask() - .tapMenu() - .tapSplitMenuItem() - .getCurrentTask() - .open(); - } - } -} +} \ No newline at end of file diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java index 23a29f78406..88be752005a 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java +++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java @@ -21,7 +21,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; +import android.os.Process; import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -55,8 +57,6 @@ public class TaplPrivateSpaceTest extends AbstractQuickStepTest { @Override public void setUp() throws Exception { super.setUp(); - initialize(this); - createAndStartPrivateProfileUser(); mDevice.pressHome(); @@ -75,8 +75,9 @@ public void setUp() throws Exception { } private void createAndStartPrivateProfileUser() { - String createUserOutput = executeShellCommand("pm create-user --profileOf 0 --user-type " - + "android.os.usertype.profile.PRIVATE " + PRIVATE_PROFILE_NAME); + int myUserId = Process.myUserHandle().getIdentifier(); + String createUserOutput = executeShellCommand("pm create-user --profileOf " + myUserId + + " --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]); @@ -117,7 +118,6 @@ public void testPrivateSpaceContainerIsPresent() { } @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. @@ -142,7 +142,6 @@ public void testUserInstalledAppIsShownAboveDivider() throws IOException { } @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. @@ -166,8 +165,8 @@ public void testPrivateSpaceAppLongPressUninstallMenu() throws IOException { } @Test - @ScreenRecordRule.ScreenRecord // b/334946529 public void testPrivateSpaceLockingBehaviour() throws IOException { + assumeFalse(mLauncher.isTablet()); // b/367258373 // Scroll to the bottom of All Apps executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader()); HomeAllApps homeAllApps = mLauncher.getAllApps(); diff --git a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java index 1886ce671a8..2fb08dd9a63 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java @@ -16,6 +16,8 @@ package com.android.quickstep; +import android.util.Log; + import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -29,8 +31,14 @@ @RunWith(AndroidJUnit4.class) public class TaplStartLauncherViaGestureTests extends AbstractQuickStepTest { + public static final String TAG = "TaplStartLauncherViaGestureTests"; + static final int STRESS_REPEAT_COUNT = 10; + private enum TestCase { + TO_HOME, TO_OVERVIEW, + } + @Override @Before public void setUp() throws Exception { @@ -41,28 +49,60 @@ public void setUp() throws Exception { } @Test - @NavigationModeSwitch + @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON) public void testStressPressHome() { - for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { - // Destroy Launcher activity. - closeLauncherActivity(); + runTest(TestCase.TO_HOME); + } - // The test action. - mLauncher.goHome(); - } + @Test + @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.ZERO_BUTTON) + public void testStressSwipeHome() { + runTest(TestCase.TO_HOME); + } + + @Test + @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON) + public void testStressPressOverview() { + runTest(TestCase.TO_OVERVIEW); } @Test - @NavigationModeSwitch + @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.ZERO_BUTTON) public void testStressSwipeToOverview() { + runTest(TestCase.TO_OVERVIEW); + } + + private void runTest(TestCase testCase) { + long testStartTime = System.currentTimeMillis(); for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { + long loopStartTime = System.currentTimeMillis(); // Destroy Launcher activity. closeLauncherActivity(); // The test action. - mLauncher.getLaunchedAppState().switchToOverview(); + switch (testCase) { + case TO_OVERVIEW: + mLauncher.getLaunchedAppState().switchToOverview(); + break; + case TO_HOME: + mLauncher.goHome(); + break; + default: + throw new IllegalStateException("Cannot run test case: " + testCase); + } + Log.d(TAG, "Loop " + (i + 1) + " runtime=" + + (System.currentTimeMillis() - loopStartTime) + "ms"); + } + Log.d(TAG, "Test runtime=" + (System.currentTimeMillis() - testStartTime) + "ms"); + switch (testCase) { + case TO_OVERVIEW: + closeLauncherActivity(); + mLauncher.goHome(); + break; + case TO_HOME: + default: + // No-Op + break; } - closeLauncherActivity(); - mLauncher.goHome(); } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java index 43ebb1752ae..3c4f1d9c6ca 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java @@ -49,6 +49,7 @@ private enum TestCase { DISMISS(0), LAUNCH_LAST_APP(0), LAUNCH_SELECTED_APP(1), + DISMISS_WHEN_GOING_HOME(1), LAUNCH_OVERVIEW(KeyboardQuickSwitchController.MAX_TASKS - 1); private final int mNumAdditionalRunningTasks; @@ -156,6 +157,11 @@ public void testLaunchSingleRecentTask() { mLauncher.goHome().showQuickSwitchView().launchFocusedAppTask(CALCULATOR_APP_PACKAGE); } + @Test + public void testDismissedWhenGoingHome() { + runTest(TestSurface.LAUNCHED_APP, TestCase.DISMISS_WHEN_GOING_HOME); + } + private void runTest(@NonNull TestSurface testSurface, @NonNull TestCase testCase) { for (int i = 0; i < testCase.mNumAdditionalRunningTasks; i++) { startTestActivity(3 + i); @@ -197,6 +203,9 @@ private void runTest(@NonNull TestSurface testSurface, @NonNull TestCase testCas } kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE); break; + case DISMISS_WHEN_GOING_HOME: + kqs.dismissByGoingHome(); + break; case LAUNCH_OVERVIEW: kqs.moveFocusBackward(); if (!testSurface.mInitialFocusAtZero) { diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java new file mode 100644 index 00000000000..b2617dd8034 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.test.InstrumentationRegistry.getTargetContext; + +import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME; +import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT; +import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; +import android.util.Log; +import android.view.WindowManagerGlobal; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.tapl.HomeAllApps; +import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; +import com.android.launcher3.util.rule.SetPropRule; +import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; +import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch; +import com.android.window.flags.Flags; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; + +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.RunWith; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplTestsLockedTaskbar extends AbstractTaplTestsTaskbar { + private static final String TAG = "TaplTestsLockedTaskbar"; + + @Rule + public SetPropRule mSetPropRule = + new SetPropRule(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP, "true"); + + // Default-to-desktop feature requires the display to be freeform mode. + @Rule + public ExternalResource mFreeformDisplayRule = new ExternalResource() { + private int mOriginalWindowingMode = WINDOWING_MODE_UNDEFINED; + + @Override + protected void before() { + mOriginalWindowingMode = setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + } + + @Override + protected void after() { + if (mOriginalWindowingMode != WINDOWING_MODE_UNDEFINED) { + setDisplayWindowingMode(mOriginalWindowingMode); + } + } + }; + + @Override + public void setUp() throws Exception { + Assume.assumeTrue(mLauncher.isTablet()); + Assume.assumeTrue(Flags.enterDesktopByDefaultOnFreeformDisplays()); + Assume.assumeTrue(DesktopModeStatus.canEnterDesktopMode(getTargetContext())); + super.setUp(); + } + + @Override + protected boolean startCalendarAppDuringSetup() { + return false; + } + + @Override + protected boolean expectTaskbarIconsMatchHotseat() { + return false; + } + + @Test + @PortraitLandscape + @NavigationModeSwitch + @TaskbarModeSwitch(mode = PERSISTENT) + public void testTaskbarVisibility() { + // The taskbar should be visible on home. + mDevice.pressHome(); + waitForResumed("Launcher internal state is still Background"); + mLauncher.getLaunchedAppState().assertTaskbarVisible(); + + // The taskbar should be visible when a freeform task is active. + startAppFast(CALCULATOR_APP_PACKAGE); + mLauncher.getLaunchedAppState().assertTaskbarVisible(); + } + + @Test + @PortraitLandscape + @NavigationModeSwitch + @TaskbarModeSwitch(mode = PERSISTENT) + public void testDragFromAllAppsToWorspace() { + mDevice.pressHome(); + waitForResumed("Launcher internal state is still Background"); + + final HomeAllApps allApps = getTaskbar().openAllAppsOnHome(); + allApps.freeze(); + try { + allApps.getAppIcon(TEST_APP_NAME).dragToWorkspace(false, false); + assertThat(mLauncher.getWorkspace().getWorkspaceAppIcon(TEST_APP_NAME)).isNotNull(); + } finally { + allApps.unfreeze(); + } + } + + private int setDisplayWindowingMode(int windowingMode) { + try { + int originalWindowingMode = + WindowManagerGlobal.getWindowManagerService().getWindowingMode(DEFAULT_DISPLAY); + WindowManagerGlobal.getWindowManagerService().setWindowingMode( + DEFAULT_DISPLAY, windowingMode); + return originalWindowingMode; + } catch (RemoteException e) { + Log.e(TAG, "error setting windowing mode", e); + throw new RuntimeException(e); + } + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt new file mode 100644 index 00000000000..75947abd1b9 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.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 + +import android.platform.test.rule.AllowedDevices +import android.platform.test.rule.DeviceProduct +import android.platform.test.rule.IgnoreLimit +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.launcher3.BuildConfig +import com.android.launcher3.tapl.LaunchedAppState +import com.android.launcher3.tapl.OverviewTask +import com.android.launcher3.ui.AbstractLauncherUiTest +import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape +import com.android.launcher3.uioverrides.QuickstepLauncher +import com.android.launcher3.util.TestUtil +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Before +import org.junit.Test + +/** Test Desktop windowing in Overview. */ +@AllowedDevices(allowed = [DeviceProduct.CF_TABLET, DeviceProduct.TANGORPRO]) +@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD) +class TaplTestsOverviewDesktop : AbstractLauncherUiTest() { + @Before + fun setup() { + val overview = mLauncher.goHome().switchToOverview() + if (overview.hasTasks()) { + overview.dismissAllTasks() + } + startTestAppsWithCheck() + mLauncher.goHome() + } + + @Test + @PortraitLandscape + fun enterDesktopViaOverviewMenu() { + mLauncher.workspace.switchToOverview() + moveTaskToDesktop(TEST_ACTIVITY_2) // Move last launched TEST_ACTIVITY_2 into Desktop + + // Scroll back to TEST_ACTIVITY_1, then move it into Desktop + mLauncher + .goHome() + .switchToOverview() + .apply { flingForward() } + .also { moveTaskToDesktop(TEST_ACTIVITY_1) } + TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) } + + // Launch static DesktopTaskView without live tile in Overview + val desktopTask = + mLauncher.goHome().switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open() + TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) } + + // Launch live-tile DesktopTaskView + desktopTask.switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open() + TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) } + + // Launch static DesktopTaskView with live tile in Overview + mLauncher.goHome() + startTestActivity(TEST_ACTIVITY_EXTRA) + mLauncher.launchedAppState + .switchToOverview() + .apply { flingBackward() } + .getTestActivityTask(TEST_ACTIVITIES) + .open() + TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) } + } + + @Test + @PortraitLandscape + fun dismissFocusedTasks_thenDesktopIsCentered() { + // Create DesktopTaskView + mLauncher.goHome().switchToOverview() + moveTaskToDesktop(TEST_ACTIVITY_2) + + // Create a new task activity to be the focused task + mLauncher.goHome() + startTestActivity(TEST_ACTIVITY_EXTRA) + + val overview = mLauncher.goHome().switchToOverview() + + // Dismiss focused task + val focusedTask1 = overview.currentTask + assertTaskContentDescription(focusedTask1, TEST_ACTIVITY_EXTRA) + focusedTask1.dismiss() + + // Dismiss new focused task + val focusedTask2 = overview.currentTask + assertTaskContentDescription(focusedTask2, TEST_ACTIVITY_1) + focusedTask2.dismiss() + + // Dismiss DesktopTaskView + val desktopTask = overview.currentTask + assertWithMessage("The current task is not a Desktop.").that(desktopTask.isDesktop).isTrue() + desktopTask.dismiss() + + assertWithMessage("Still have tasks after dismissing all the tasks") + .that(mLauncher.workspace.switchToOverview().hasTasks()) + .isFalse() + } + + @Test + @PortraitLandscape + fun dismissTasks_whenDesktopTask_IsInTheCenter() { + // Create extra activity to be DesktopTaskView + startTestActivity(TEST_ACTIVITY_EXTRA) + mLauncher.goHome().switchToOverview() + + val desktop = moveTaskToDesktop(TEST_ACTIVITY_EXTRA) + var overview = desktop.switchToOverview() + + // Open first fullscreen task and go back to Overview to validate whether it has adjacent + // tasks in its both sides (grid task on left and desktop tasks at its right side) + val firstFullscreenTaskOpened = overview.getTestActivityTask(TEST_ACTIVITY_2).open() + + // Fling to desktop task and dismiss the first fullscreen task to check repositioning of + // grid tasks. + overview = firstFullscreenTaskOpened.switchToOverview().apply { flingBackward() } + val desktopTask = overview.currentTask + assertWithMessage("The current task is not a Desktop.").that(desktopTask.isDesktop).isTrue() + + // Get first fullscreen task (previously opened task) then dismiss this task + val firstFullscreenTaskInOverview = overview.getTestActivityTask(TEST_ACTIVITY_2) + assertTaskContentDescription(firstFullscreenTaskInOverview, TEST_ACTIVITY_2) + firstFullscreenTaskInOverview.dismiss() + + // Dismiss DesktopTask to validate whether the new task will take its position + desktopTask.dismiss() + + // Dismiss last fullscreen task + val lastFocusedTask = overview.currentTask + assertTaskContentDescription(lastFocusedTask, TEST_ACTIVITY_1) + lastFocusedTask.dismiss() + + assertWithMessage("Still have tasks after dismissing all the tasks") + .that(mLauncher.workspace.switchToOverview().hasTasks()) + .isFalse() + } + + private fun assertTaskContentDescription(task: OverviewTask, activityIndex: Int) { + assertWithMessage("The current task content description is not TestActivity$activityIndex.") + .that(task.containsContentDescription("TestActivity$activityIndex")) + .isTrue() + } + + private fun moveTaskToDesktop(activityIndex: Int): LaunchedAppState { + return mLauncher.overview + .getTestActivityTask(activityIndex) + .tapMenu() + .tapDesktopMenuItem() + .also { assertTestAppLaunched(activityIndex) } + } + + private fun startTestAppsWithCheck() { + TEST_ACTIVITIES.forEach { + startTestActivity(it) + executeOnLauncher { launcher -> + assertWithMessage( + "Launcher activity is the top activity; expecting TestActivity$it" + ) + .that(isInLaunchedApp(launcher)) + .isTrue() + } + } + } + + private fun assertTestAppLaunched(index: Int) { + assertWithMessage("TestActivity$index not opened in Desktop") + .that( + mDevice.wait( + Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity$index")), + TestUtil.DEFAULT_UI_TIMEOUT, + ) + ) + .isTrue() + } + + companion object { + const val TEST_ACTIVITY_1 = 2 + const val TEST_ACTIVITY_2 = 3 + const val TEST_ACTIVITY_EXTRA = 4 + val TEST_ACTIVITIES = listOf(TEST_ACTIVITY_1, TEST_ACTIVITY_2) + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java index df73e0913e3..a16811efaf8 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java @@ -15,16 +15,12 @@ */ package com.android.quickstep; -import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT; - import android.graphics.Rect; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; -import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; -import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch; import org.junit.Assert; import org.junit.Test; @@ -35,8 +31,6 @@ public class TaplTestsPersistentTaskbar extends AbstractTaplTestsTaskbar { @Test - @TaskbarModeSwitch(mode = PERSISTENT) - @PortraitLandscape @NavigationModeSwitch public void testTaskbarFillsWidth() { // Width check is performed inside TAPL whenever getTaskbar() is called. diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index 7877e8ac22b..b207d4ac8f4 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -16,12 +16,14 @@ package com.android.quickstep; +import static com.android.launcher3.Flags.enableLauncherOverviewInWindow; import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; 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.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; @@ -30,13 +32,13 @@ import android.content.Intent; import android.content.res.Configuration; +import androidx.annotation.NonNull; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; -import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.tapl.BaseOverview; import com.android.launcher3.tapl.LaunchedAppState; @@ -47,11 +49,13 @@ import com.android.launcher3.tapl.SelectModeButtons; import com.android.launcher3.tapl.Workspace; 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.ScreenRecord; +import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.TestStabilityRule; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch; +import com.android.quickstep.fallback.RecentsState; import com.android.quickstep.views.RecentsView; import org.junit.After; @@ -60,6 +64,10 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Comparator; +import java.util.function.Consumer; +import java.util.function.Function; + @LargeTest @RunWith(AndroidJUnit4.class) public class TaplTestsQuickstep extends AbstractQuickStepTest { @@ -69,21 +77,32 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { private static final String READ_DEVICE_CONFIG_PERMISSION = "android.permission.READ_DEVICE_CONFIG"; + private enum ExpectedState { + + HOME(LauncherState.NORMAL, RecentsState.HOME), + OVERVIEW(LauncherState.OVERVIEW, RecentsState.DEFAULT); + + private final LauncherState mLauncherState; + private final RecentsState mRecentsState; + + ExpectedState(LauncherState launcherState, RecentsState recentsState) { + this.mLauncherState = launcherState; + this.mRecentsState = recentsState; + } + } + @Before public void setUp() throws Exception { super.setUp(); - executeOnLauncher(launcher -> { - RecentsView recentsView = launcher.getOverviewPanel(); - recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true); - }); + runOnRecentsView(recentsView -> + recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true)); } @After public void tearDown() { - executeOnLauncherInTearDown(launcher -> { - RecentsView recentsView = launcher.getOverviewPanel(); - recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false); - }); + runOnRecentsView(recentsView -> + recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false), + /* forTearDown= */ true); } public static void startTestApps() throws Exception { @@ -92,14 +111,6 @@ public static void startTestApps() throws Exception { startTestActivity(2); } - private void startTestAppsWithCheck() throws Exception { - startTestApps(); - executeOnLauncher(launcher -> assertTrue( - "Launcher activity is the top activity; expecting another activity to be the top " - + "one", - isInLaunchedApp(launcher))); - } - @Test @NavigationModeSwitch @PortraitLandscape @@ -116,28 +127,26 @@ public void testOverview() throws Exception { startTestAppsWithCheck(); // mLauncher.pressHome() also tests an important case of pressing home while in background. Overview overview = mLauncher.goHome().switchToOverview(); - assertTrue("Launcher internal state didn't switch to Overview", - isInState(() -> LauncherState.OVERVIEW)); - executeOnLauncher( - launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3)); + assertIsInState( + "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW); + runOnRecentsView(recentsView -> assertTrue("Don't have at least 3 tasks", + recentsView.getTaskViewCount() >= 3)); // Test flinging forward and backward. - executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0", - 0, getCurrentOverviewPage(launcher))); + runOnRecentsView(recentsView -> assertEquals("Current task in Overview is not 0", + 0, recentsView.getCurrentPage())); overview.flingForward(); - assertTrue("Launcher internal state is not Overview", - isInState(() -> LauncherState.OVERVIEW)); - final Integer currentTaskAfterFlingForward = getFromLauncher( - launcher -> getCurrentOverviewPage(launcher)); - executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", + assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); + final Integer currentTaskAfterFlingForward = + getFromRecentsView(RecentsView::getCurrentPage); + runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0", currentTaskAfterFlingForward > 0)); overview.flingBackward(); - assertTrue("Launcher internal state is not Overview", - isInState(() -> LauncherState.OVERVIEW)); - executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing", - getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward)); + assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); + runOnRecentsView(recentsView -> assertTrue("Flinging back in Overview did nothing", + recentsView.getCurrentPage() < currentTaskAfterFlingForward)); // Test opening a task. OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask(); @@ -145,31 +154,26 @@ public void testOverview() throws Exception { assertNotNull("OverviewTask.open returned null", task.open()); assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject( By.pkg(getAppPackageName()).text("TestActivity2")), - DEFAULT_UI_TIMEOUT)); - executeOnLauncher(launcher -> assertTrue( - "Launcher activity is the top activity; expecting another activity to be the top " - + "one", - isInLaunchedApp(launcher))); + TestUtil.DEFAULT_UI_TIMEOUT)); + expectLaunchedAppState(); // Test dismissing a task. overview = mLauncher.goHome().switchToOverview(); - assertTrue("Launcher internal state didn't switch to Overview", - isInState(() -> LauncherState.OVERVIEW)); - final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher)); + assertIsInState("Launcher internal state didn't switch to Overview", + ExpectedState.OVERVIEW); + final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); task = overview.getCurrentTask(); assertNotNull("overview.getCurrentTask() returned null (2)", task); task.dismiss(); - executeOnLauncher( - launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview", - numTasks - 1, getTaskCount(launcher))); + runOnRecentsView(recentsView -> assertEquals( + "Dismissing a task didn't remove 1 task from Overview", + numTasks - 1, recentsView.getTaskViewCount())); // Test dismissing all tasks. mLauncher.goHome().switchToOverview().dismissAllTasks(); - assertTrue("Launcher internal state is not Home", - isInState(() -> LauncherState.NORMAL)); - executeOnLauncher( - launcher -> assertEquals("Still have tasks after dismissing all", - 0, getTaskCount(launcher))); + assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); + runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all", + 0, recentsView.getTaskViewCount())); } /** @@ -191,12 +195,10 @@ public void testOverviewActions() throws Exception { public void testDismissOverviewWithEscKey() throws Exception { startTestAppsWithCheck(); final Overview overview = mLauncher.goHome().switchToOverview(); - assertTrue("Launcher internal state is not Overview", - isInState(() -> LauncherState.OVERVIEW)); + assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); overview.dismissByEscKey(); - assertTrue("Launcher internal state is not Home", - isInState(() -> LauncherState.NORMAL)); + assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); } @Test @@ -217,11 +219,9 @@ public void testDismissModalTaskAndOverviewWithEscKey() throws Exception { selectModeButtons.dismissByEscKey(); - assertTrue("Launcher internal state is not Overview", - isInState(() -> LauncherState.OVERVIEW)); + assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); overview.dismissByEscKey(); - assertTrue("Launcher internal state is not Home", - isInState(() -> LauncherState.NORMAL)); + assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); } @Test @@ -229,11 +229,11 @@ public void testOpenOverviewWithActionPlusTabKeys() throws Exception { startTestAppsWithCheck(); startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app. Workspace home = mLauncher.goHome(); - assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL)); + assertIsInState("Launcher state is not Home", ExpectedState.HOME); Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut(); - assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW)); + assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW); overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused. } @@ -242,33 +242,14 @@ public void testOpenOverviewWithRecentsKey() throws Exception { startTestAppsWithCheck(); startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app. Workspace home = mLauncher.goHome(); - assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL)); + assertIsInState("Launcher state is not Home", ExpectedState.HOME); Overview overview = home.openOverviewFromRecentsKeyboardShortcut(); - assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW)); + assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW); overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused. } - private int getCurrentOverviewPage(Launcher launcher) { - return launcher.getOverviewPanel().getCurrentPage(); - } - - private int getTaskCount(Launcher launcher) { - return launcher.getOverviewPanel().getTaskViewCount(); - } - - private int getTopRowTaskCountForTablet(Launcher launcher) { - return launcher.getOverviewPanel().getTopRowTaskCountForTablet(); - } - - private int getBottomRowTaskCountForTablet(Launcher launcher) { - return launcher.getOverviewPanel().getBottomRowTaskCountForTablet(); - } - - // Staging; will be promoted to presubmit if stable - @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) - @Test @NavigationModeSwitch @PortraitLandscape @@ -276,8 +257,8 @@ public void testSwitchToOverview() throws Exception { startTestAppsWithCheck(); assertNotNull("Workspace.switchToOverview() returned null", mLauncher.goHome().switchToOverview()); - assertTrue("Launcher internal state didn't switch to Overview", - isInState(() -> LauncherState.OVERVIEW)); + assertIsInState( + "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW); } @Test @@ -293,9 +274,6 @@ public void testSwitchToOverviewWithStashedTaskbar() throws Exception { } } - // Staging; will be promoted to presubmit if stable - @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) - @Test @NavigationModeSwitch @PortraitLandscape @@ -305,27 +283,13 @@ public void testBackground() throws Exception { assertNotNull("Background.switchToOverview() returned null", launchedAppState.switchToOverview()); - assertTrue("Launcher internal state didn't switch to Overview", - isInState(() -> LauncherState.OVERVIEW)); - } - - private void quickSwitchToPreviousAppAndAssert(boolean toRight) { - final LaunchedAppState launchedAppState = getAndAssertLaunchedApp(); - if (toRight) { - launchedAppState.quickSwitchToPreviousApp(); - } else { - launchedAppState.quickSwitchToPreviousAppSwipeLeft(); - } - - // While enable shell transition, Launcher can be resumed due to transient launch. - waitForLauncherCondition("Launcher shouldn't stay in resume forever", - this::isInLaunchedApp, 3000 /* timeout */); + assertIsInState( + "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW); } @Test @NavigationModeSwitch @PortraitLandscape - @ScreenRecord // b/313464374 @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406 public void testQuickSwitchFromApp() throws Exception { startTestActivity(2); @@ -393,11 +357,6 @@ public void testQuickSwitchToPreviousAppForTablet() throws Exception { } } - private boolean isHardwareKeyboard() { - return Configuration.KEYBOARD_QWERTY - == mTargetContext.getResources().getConfiguration().keyboard; - } - @Test @NavigationModeSwitch @PortraitLandscape @@ -418,11 +377,11 @@ public void testPressBack() throws Exception { // Debug if we need to goHome to prevent wrong previous state b/315525621 mLauncher.goHome(); mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace(); - waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); + waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME); startAppFast(CALCULATOR_APP_PACKAGE); mLauncher.getLaunchedAppState().pressBackToWorkspace(); - waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); + waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME); } @Test @@ -437,16 +396,14 @@ public void testOverviewForTablet() throws Exception { } Overview overview = mLauncher.goHome().switchToOverview(); - executeOnLauncher( - launcher -> assertTrue("Don't have at least 13 tasks", - getTaskCount(launcher) >= 13)); + runOnRecentsView(recentsView -> assertTrue("Don't have at least 13 tasks", + recentsView.getTaskViewCount() >= 13)); // Test scroll the first task off screen overview.scrollCurrentTaskOffScreen(); - assertTrue("Launcher internal state is not Overview", - isInState(() -> LauncherState.OVERVIEW)); - executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", - getCurrentOverviewPage(launcher) > 0)); + assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); + runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0", + recentsView.getCurrentPage() > 0)); // Test opening the task. overview.getCurrentTask().open(); @@ -454,45 +411,43 @@ public void testOverviewForTablet() throws Exception { mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text( mLauncher.isGridOnlyOverviewEnabled() ? "TestActivity12" : "TestActivity13")), - DEFAULT_UI_TIMEOUT)); + TestUtil.DEFAULT_UI_TIMEOUT)); // Scroll the task offscreen as it is now first overview = mLauncher.goHome().switchToOverview(); overview.scrollCurrentTaskOffScreen(); - assertTrue("Launcher internal state is not Overview", - isInState(() -> LauncherState.OVERVIEW)); - executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", - getCurrentOverviewPage(launcher) > 0)); + assertIsInState( + "Launcher internal state is not Overview", ExpectedState.OVERVIEW); + runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0", + recentsView.getCurrentPage() > 0)); // Test dismissing the later task. - final Integer numTasks = getFromLauncher(this::getTaskCount); + final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); + overview.getCurrentTask().dismiss(); + runOnRecentsView(recentsView -> assertEquals( + "Dismissing a task didn't remove 1 task from Overview", + numTasks - 1, recentsView.getTaskViewCount())); + runOnRecentsView(recentsView -> assertTrue("Grid did not rebalance after dismissal", + (Math.abs(recentsView.getTopRowTaskCountForTablet() + - recentsView.getBottomRowTaskCountForTablet()) <= 1))); + + // Test dismissing more tasks. + assertIsInState( + "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW); overview.getCurrentTask().dismiss(); - executeOnLauncher( - launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview", - numTasks - 1, getTaskCount(launcher))); - executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal", - (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet( - launcher)) <= 1))); - - // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss -// // Test dismissing more tasks. -// assertTrue("Launcher internal state didn't remain in Overview", -// isInState(() -> LauncherState.OVERVIEW)); -// overview.getCurrentTask().dismiss(); -// assertTrue("Launcher internal state didn't remain in Overview", -// isInState(() -> LauncherState.OVERVIEW)); -// overview.getCurrentTask().dismiss(); -// executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals", -// (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet( -// launcher)) <= 1))); + assertIsInState( + "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW); + overview.getCurrentTask().dismiss(); + runOnRecentsView(recentsView -> assertTrue( + "Grid did not rebalance after multiple dismissals", + (Math.abs(recentsView.getTopRowTaskCountForTablet() + - recentsView.getBottomRowTaskCountForTablet()) <= 1))); // Test dismissing all tasks. mLauncher.goHome().switchToOverview().dismissAllTasks(); - assertTrue("Launcher internal state is not Home", - isInState(() -> LauncherState.NORMAL)); - executeOnLauncher( - launcher -> assertEquals("Still have tasks after dismissing all", - 0, getTaskCount(launcher))); + assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); + runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all", + 0, recentsView.getTaskViewCount())); } @Test @@ -501,22 +456,18 @@ public void testOverviewDeadzones() throws Exception { startTestAppsWithCheck(); Overview overview = mLauncher.goHome().switchToOverview(); - assertTrue("Launcher internal state should be Overview", - isInState(() -> LauncherState.OVERVIEW)); - executeOnLauncher( - launcher -> assertTrue("Should have at least 3 tasks", - getTaskCount(launcher) >= 3)); + assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); + runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks", + recentsView.getTaskViewCount() >= 3)); // It should not dismiss overview when tapping between tasks overview.touchBetweenTasks(); overview = mLauncher.getOverview(); - assertTrue("Launcher internal state should be Overview", - isInState(() -> LauncherState.OVERVIEW)); + assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); // Dismiss when tapping to the right of the focused task overview.touchOutsideFirstTask(); - assertTrue("Launcher internal state should be Home", - isInState(() -> LauncherState.NORMAL)); + assertIsInState("Launcher internal state should be Home", ExpectedState.HOME); } @Test @@ -528,34 +479,28 @@ public void testTaskbarDeadzonesForTablet() throws Exception { startTestAppsWithCheck(); Overview overview = mLauncher.goHome().switchToOverview(); - assertTrue("Launcher internal state should be Overview", - isInState(() -> LauncherState.OVERVIEW)); - executeOnLauncher( - launcher -> assertTrue("Should have at least 3 tasks", - getTaskCount(launcher) >= 3)); + assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); + runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks", + recentsView.getTaskViewCount() >= 3)); if (mLauncher.isTransientTaskbar()) { // On transient taskbar, it should dismiss when tapping outside taskbar bounds. overview.touchTaskbarBottomCorner(/* tapRight= */ false); - assertTrue("Launcher internal state should be Normal", - isInState(() -> LauncherState.NORMAL)); + assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME); overview = mLauncher.getWorkspace().switchToOverview(); // On transient taskbar, it should dismiss when tapping outside taskbar bounds. overview.touchTaskbarBottomCorner(/* tapRight= */ true); - assertTrue("Launcher internal state should be Normal", - isInState(() -> LauncherState.NORMAL)); + assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME); } else { // On persistent taskbar, it should not dismiss when tapping the taskbar overview.touchTaskbarBottomCorner(/* tapRight= */ false); - assertTrue("Launcher internal state should be Overview", - isInState(() -> LauncherState.OVERVIEW)); + assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); // On persistent taskbar, it should not dismiss when tapping the taskbar overview.touchTaskbarBottomCorner(/* tapRight= */ true); - assertTrue("Launcher internal state should be Overview", - isInState(() -> LauncherState.OVERVIEW)); + assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); } } @@ -568,7 +513,7 @@ public void testDisableRotationCheckForPhone() throws Exception { mLauncher.getDevice().setOrientationLeft(); startTestActivity(7); Wait.atMost("Device should not be in natural orientation", - () -> !mDevice.isNaturalOrientation(), DEFAULT_UI_TIMEOUT, mLauncher); + () -> !mDevice.isNaturalOrientation(), mLauncher); mLauncher.goHome(); } finally { mLauncher.setExpectedRotationCheckEnabled(true); @@ -581,20 +526,164 @@ public void testDisableRotationCheckForPhone() throws Exception { 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)); + currentTask.containsContentDescription("ExcludeFromRecents")); // 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)); + currentTask.containsContentDescription("ExcludeFromRecents")); } else { // Presumably the test started with 0 tasks and remains that way after going home. } } + + @Test + @PortraitLandscape + @ScreenRecordRule.ScreenRecord // TODO(b/396447643): Remove screen record. + public void testDismissCancel() throws Exception { + startTestAppsWithCheck(); + Overview overview = mLauncher.goHome().switchToOverview(); + assertIsInState("Launcher internal state didn't switch to Overview", + ExpectedState.OVERVIEW); + final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); + OverviewTask task = overview.getCurrentTask(); + assertNotNull("overview.getCurrentTask() returned null (2)", task); + + task.dismissCancel(); + + runOnRecentsView(recentsView -> assertEquals( + "Canceling dismissing a task removed a task from Overview", + numTasks == null ? 0 : numTasks, recentsView.getTaskViewCount())); + } + + @Test + @PortraitLandscape + public void testDismissBottomRow() throws Exception { + assumeTrue(mLauncher.isTablet()); + mLauncher.goHome().switchToOverview().dismissAllTasks(); + startTestAppsWithCheck(); + Overview overview = mLauncher.goHome().switchToOverview(); + assertIsInState("Launcher internal state didn't switch to Overview", + ExpectedState.OVERVIEW); + final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); + OverviewTask bottomTask = overview.getCurrentTasksForTablet().stream().max( + Comparator.comparingInt(OverviewTask::getTaskCenterY)).get(); + assertNotNull("bottomTask null", bottomTask); + + bottomTask.dismiss(); + + runOnRecentsView(recentsView -> assertEquals( + "Dismissing a bottomTask didn't remove 1 bottomTask from Overview", + numTasks - 1, recentsView.getTaskViewCount())); + } + + @Test + @PortraitLandscape + public void testDismissLastGridRow() throws Exception { + assumeTrue(mLauncher.isTablet()); + mLauncher.goHome().switchToOverview().dismissAllTasks(); + startTestAppsWithCheck(); + startTestActivity(3); + startTestActivity(4); + runOnRecentsView( + recentsView -> assertNotEquals("Grid overview should have unequal row counts", + recentsView.getTopRowTaskCountForTablet(), + recentsView.getBottomRowTaskCountForTablet())); + Overview overview = mLauncher.goHome().switchToOverview(); + assertIsInState("Launcher internal state didn't switch to Overview", + ExpectedState.OVERVIEW); + overview.flingForwardUntilClearAllVisible(); + assertTrue("Clear All not visible.", overview.isClearAllVisible()); + final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); + OverviewTask lastGridTask = overview.getCurrentTasksForTablet().stream().min( + Comparator.comparingInt(OverviewTask::getTaskCenterX)).get(); + assertNotNull("lastGridTask null.", lastGridTask); + + lastGridTask.dismiss(); + + runOnRecentsView(recentsView -> assertEquals( + "Dismissing a lastGridTask didn't remove 1 lastGridTask from Overview", + numTasks - 1, recentsView.getTaskViewCount())); + runOnRecentsView(recentsView -> assertEquals("Grid overview should have equal row counts.", + recentsView.getTopRowTaskCountForTablet(), + recentsView.getBottomRowTaskCountForTablet())); + assertTrue("Clear All not visible.", overview.isClearAllVisible()); + } + + private void startTestAppsWithCheck() throws Exception { + startTestApps(); + expectLaunchedAppState(); + } + + private void quickSwitchToPreviousAppAndAssert(boolean toRight) { + final LaunchedAppState launchedAppState = getAndAssertLaunchedApp(); + if (toRight) { + launchedAppState.quickSwitchToPreviousApp(); + } else { + launchedAppState.quickSwitchToPreviousAppSwipeLeft(); + } + + // While enable shell transition, Launcher can be resumed due to transient launch. + waitForLauncherCondition("Launcher shouldn't stay in resume forever", + this::isInLaunchedApp, 3000 /* timeout */); + } + + private boolean isHardwareKeyboard() { + return Configuration.KEYBOARD_QWERTY + == mTargetContext.getResources().getConfiguration().keyboard; + } + + private void assertIsInState( + @NonNull String failureMessage, @NonNull ExpectedState expectedState) { + assertTrue(failureMessage, enableLauncherOverviewInWindow() + ? isInRecentsWindowState(() -> expectedState.mRecentsState) + : isInState(() -> expectedState.mLauncherState)); + } + + private void waitForState( + @NonNull String failureMessage, @NonNull ExpectedState expectedState) { + if (enableLauncherOverviewInWindow()) { + waitForRecentsWindowState(failureMessage, () -> expectedState.mRecentsState); + } else { + waitForState(failureMessage, () -> expectedState.mLauncherState); + } + } + + private void expectLaunchedAppState() { + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInLaunchedApp(launcher))); + } + + private T getFromRecentsView(Function f) { + return getFromRecentsView(f, false); + } + + private T getFromRecentsView(Function f, boolean forTearDown) { + if (enableLauncherOverviewInWindow()) { + return getFromRecentsWindow(recentsWindowManager -> + (forTearDown && recentsWindowManager == null) + ? null : f.apply(recentsWindowManager.getOverviewPanel())); + } else { + return getFromLauncher(launcher -> (forTearDown && launcher == null) + ? null : f.apply(launcher.getOverviewPanel())); + } + } + + private void runOnRecentsView(Consumer f) { + runOnRecentsView(f, false); + } + + private void runOnRecentsView(Consumer f, boolean forTearDown) { + getFromRecentsView(recentsView -> { + if (forTearDown && recentsView == null) { + return null; + } + f.accept(recentsView); + return null; + }, forTearDown); + } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java index 8adf79318ba..37ac4a0ed29 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java @@ -16,10 +16,6 @@ package com.android.quickstep; -import static com.android.launcher3.config.FeatureFlags.enableSplitContextually; -import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; -import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -33,8 +29,7 @@ import com.android.launcher3.tapl.Overview; import com.android.launcher3.tapl.Taskbar; import com.android.launcher3.tapl.TaskbarAppIcon; -import com.android.launcher3.util.rule.TestStabilityRule; -import com.android.wm.shell.Flags; +import com.android.quickstep.util.SplitScreenTestUtils; import org.junit.After; import org.junit.Before; @@ -72,7 +67,6 @@ public void tearDown() { } @Test - @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 assumeTrue(mLauncher.isTablet()); @@ -92,28 +86,16 @@ public void testSplitAppFromHomeWithItself() throws Exception { .getSplitScreenMenuItem() .click(); - if (enableSplitContextually()) { - // We're staying in all apps, use same instance - mLauncher.getAllApps() - .getAppIcon(CALCULATOR_APP_NAME) - .launchIntoSplitScreen(); - } else { - // We're in overview, use taskbar instance - mLauncher.getLaunchedAppState() - .getTaskbar() - .getAppIcon(CALCULATOR_APP_NAME) - .launchIntoSplitScreen(); - } + // We're staying in all apps, use same instance + mLauncher.getAllApps() + .getAppIcon(CALCULATOR_APP_NAME) + .launchIntoSplitScreen(); } @Test public void testSaveAppPairMenuItemOrActionExistsOnSplitPair() { - assumeTrue("App pairs feature is currently not enabled, no test needed", - Flags.enableAppPairs()); + Overview overview = SplitScreenTestUtils.createAndLaunchASplitPairInOverview(mLauncher); - createAndLaunchASplitPair(); - - Overview overview = mLauncher.goHome().switchToOverview(); if (mLauncher.isGridOnlyOverviewEnabled() || !mLauncher.isTablet()) { assertTrue("Save app pair menu item is missing", overview.getCurrentTask() @@ -124,9 +106,6 @@ public void testSaveAppPairMenuItemOrActionExistsOnSplitPair() { @Test public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception { - assumeTrue("App pairs feature is currently not enabled, no test needed", - Flags.enableAppPairs()); - startAppFast(CALCULATOR_APP_PACKAGE); assertFalse("Save app pair menu item is erroneously appearing on single task", @@ -157,24 +136,4 @@ public void testSplitSingleTaskFromTaskbar() { TaskbarAppIcon firstApp = taskbar.getAppIcon(firstAppName); firstApp.launchIntoSplitScreen(); } - - private void createAndLaunchASplitPair() { - clearAllRecentTasks(); - - startTestActivity(2); - startTestActivity(3); - - if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) { - mLauncher.goHome().switchToOverview().getOverviewActions() - .clickSplit() - .getTestActivityTask(2) - .open(); - } else { - mLauncher.goHome().switchToOverview().getCurrentTask() - .tapMenu() - .tapSplitMenuItem() - .getCurrentTask() - .open(); - } - } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java index 2c23f867cbb..710ad6f6dc5 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java @@ -16,8 +16,6 @@ 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; @@ -32,8 +30,6 @@ 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; @@ -95,8 +91,6 @@ public void pressBack() throws Exception { @Test @PortraitLandscape @NavigationModeSwitch - @ScreenRecordRule.ScreenRecord // b/336606166 - @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/336606166 public void switchToOverview() throws Exception { assumeTrue(mLauncher.isTablet()); diff --git a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java index 512557bf3a0..dc1da693426 100644 --- a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java +++ b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java @@ -87,18 +87,6 @@ public void notShowBorderOnBorderDisabled() { true); } - @Test - 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)); - verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */ - true); - mTaskView.onFocusChanged(true, 0, new Rect()); - verify(mFocusAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */ - true); - } - @Test public void showBorderOnBorderEnabled() { presetBorderStatus(/* enabled= */ false); diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt new file mode 100644 index 00000000000..daa77d2a314 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.desktop + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.app.ActivityManager +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.util.DisplayMetrics +import android.view.SurfaceControl +import android.view.WindowManager +import android.window.TransitionInfo +import android.window.TransitionInfo.Change +import androidx.core.util.Supplier +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread +import com.android.app.animation.Interpolators +import com.android.internal.jank.Cuj +import com.android.launcher3.desktop.DesktopAppLaunchAnimatorHelper +import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.window.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class DesktopAppLaunchAnimatorHelperTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + + private val context = mock() + private val resources = mock() + private val transaction = mock() + private val transactionSupplier = mock>() + + private lateinit var helper: DesktopAppLaunchAnimatorHelper + + @Before + fun setUp() { + helper = + DesktopAppLaunchAnimatorHelper( + context = context, + launchType = AppLaunchType.LAUNCH, + cujType = Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_INTENT, + transactionSupplier = transactionSupplier, + ) + whenever(transactionSupplier.get()).thenReturn(transaction) + whenever(transaction.setCrop(any(), any())).thenReturn(transaction) + whenever(transaction.setCornerRadius(any(), any())).thenReturn(transaction) + whenever(transaction.setScale(any(), any(), any())).thenReturn(transaction) + whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction) + whenever(transaction.setAlpha(any(), any())).thenReturn(transaction) + whenever(transaction.setFrameTimeline(any())).thenReturn(transaction) + + whenever(context.resources).thenReturn(resources) + whenever(resources.displayMetrics).thenReturn(DisplayMetrics()) + whenever(context.mainThreadHandler).thenReturn(MAIN_EXECUTOR.handler) + } + + @Test + fun launchTransition_returnsLaunchAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE)) + + val actual = helper.createAnimators(transitionInfo, finishCallback = {}) + + assertThat(actual).hasSize(1) + assertLaunchAnimator(actual[0]) + } + + @Test + fun launchTransition_callsAnimationEndListener() = runOnUiThread { + val finishCallback = mock>() + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE)) + + val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback) + + animators.forEach { animator -> + animator.start() + animator.end() + verify(finishCallback).invoke(animator) + } + } + + @Test + fun noLaunchTransition_returnsEmptyAnimatorsList() = runOnUiThread { + val pipChange = + TransitionInfo.Change(mock(), mock()).apply { + mode = WindowManager.TRANSIT_PIP + taskInfo = TASK_INFO_FREEFORM + } + val transitionInfo = createTransitionInfo(listOf(pipChange)) + + val actual = helper.createAnimators(transitionInfo, finishCallback = {}) + + assertThat(actual).hasSize(0) + } + + @Test + fun minimizeTransition_returnsLaunchAndMinimizeAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, MINIMIZE_CHANGE)) + + val actual = helper.createAnimators(transitionInfo, finishCallback = {}) + + assertThat(actual).hasSize(2) + assertLaunchAnimator(actual[0]) + assertMinimizeAnimator(actual[1]) + } + + @Test + fun minimizeTransition_callsAnimationEndListener() = runOnUiThread { + val finishCallback = mock>() + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, MINIMIZE_CHANGE)) + + val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback) + + animators.forEach { animator -> + animator.start() + animator.end() + verify(finishCallback).invoke(animator) + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) + fun trampolineTransition_flagEnabled_returnsLaunchAndCloseAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE)) + + val actual = helper.createAnimators(transitionInfo, finishCallback = {}) + + assertThat(actual).hasSize(2) + assertTrampolineLaunchAnimator(actual[0]) + assertCloseAnimator(actual[1]) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) + fun trampolineTransition_flagEnabled_callsAnimationEndListener() = runOnUiThread { + val finishCallback = mock>() + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE)) + + val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback) + + animators.forEach { animator -> + animator.start() + animator.end() + verify(finishCallback).invoke(animator) + } + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) + fun trampolineTransition_flagDisabled_returnsLaunchAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE)) + + val actual = helper.createAnimators(transitionInfo, finishCallback = {}) + + assertThat(actual).hasSize(1) + assertLaunchAnimator(actual[0]) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) + fun trampolineTransition_flagEnabled_hitDesktopWindowLimit_returnsLaunchMinimizeCloseAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo( + listOf(OPEN_CHANGE, MINIMIZE_CHANGE, CLOSE_CHANGE)) + + val actual = helper.createAnimators(transitionInfo, finishCallback = {}) + + assertThat(actual).hasSize(3) + assertTrampolineLaunchAnimator(actual[0]) + assertMinimizeAnimator(actual[1]) + assertCloseAnimator(actual[2]) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) + fun trampolineTransition_flagDisabled_hitDesktopWindowLimit_returnsLaunchMinimizeAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo( + listOf(OPEN_CHANGE, MINIMIZE_CHANGE, CLOSE_CHANGE)) + + val actual = helper.createAnimators(transitionInfo, finishCallback = {}) + + assertThat(actual).hasSize(2) + assertLaunchAnimator(actual[0]) + assertMinimizeAnimator(actual[1]) + } + + private fun assertLaunchAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(AnimatorSet::class.java) + assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(2) + assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.childAnimations[0].interpolator) + .isEqualTo(AppLaunchType.LAUNCH.boundsAnimationParams.interpolator) + assertThat(animator.childAnimations[0].duration) + .isEqualTo(AppLaunchType.LAUNCH.boundsAnimationParams.durationMs) + assertThat(animator.childAnimations[1]).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.childAnimations[1].interpolator).isEqualTo(Interpolators.LINEAR) + assertThat(animator.childAnimations[1].duration) + .isEqualTo(AppLaunchType.LAUNCH.alphaDurationMs) + } + + private fun assertTrampolineLaunchAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(AnimatorSet::class.java) + assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(1) + assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.childAnimations[0].interpolator).isEqualTo(Interpolators.LINEAR) + assertThat(animator.childAnimations[0].duration) + .isEqualTo(AppLaunchType.LAUNCH.alphaDurationMs) + } + + private fun assertMinimizeAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(AnimatorSet::class.java) + assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(2) + assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.childAnimations[0].interpolator) + .isInstanceOf(Interpolators.STANDARD_ACCELERATE::class.java) + assertThat(animator.childAnimations[0].duration).isEqualTo(200) + assertThat(animator.childAnimations[1]).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.childAnimations[1].interpolator) + .isInstanceOf(Interpolators.LINEAR::class.java) + assertThat(animator.childAnimations[1].duration).isEqualTo(100) + } + + private fun assertCloseAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.interpolator).isInstanceOf(Interpolators.LINEAR::class.java) + assertThat(animator.duration).isEqualTo(100) + } + + private fun createTransitionInfo(changes: List): TransitionInfo { + val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) + changes.forEach { transitionInfo.addChange(it) } + return transitionInfo + } + + private companion object { + val TASK_INFO_FREEFORM = + ActivityManager.RunningTaskInfo().apply { + baseIntent = + Intent().apply { + component = ComponentName("com.example.app", "com.example.app.MainActivity") + } + configuration.windowConfiguration.windowingMode = + WindowConfiguration.WINDOWING_MODE_FREEFORM + } + + val OPEN_CHANGE = + TransitionInfo.Change(mock(), mock()).apply { + mode = WindowManager.TRANSIT_OPEN + taskInfo = TASK_INFO_FREEFORM + } + + val CLOSE_CHANGE = + TransitionInfo.Change(mock(), mock()).apply { + mode = WindowManager.TRANSIT_CLOSE + taskInfo = TASK_INFO_FREEFORM + } + + val MINIMIZE_CHANGE = + TransitionInfo.Change(mock(), mock()).apply { + mode = WindowManager.TRANSIT_TO_BACK + taskInfo = TASK_INFO_FREEFORM + } + } +} diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt b/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt new file mode 100644 index 00000000000..99c74bec156 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.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.quickstep.util + +import androidx.test.uiautomator.By +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.launcher3.tapl.Overview +import com.android.launcher3.tapl.OverviewTask +import com.android.launcher3.ui.AbstractLauncherUiTest + +object SplitScreenTestUtils { + + /** Creates 2 tasks and makes a split mode pair. Also asserts the accessibility labels. */ + @JvmStatic + fun createAndLaunchASplitPairInOverview(launcher: LauncherInstrumentation): Overview { + clearAllRecentTasks(launcher) + + AbstractLauncherUiTest.startTestActivity(2) + AbstractLauncherUiTest.startTestActivity(3) + + val overView = launcher.goHome().switchToOverview() + if (launcher.isTablet && !launcher.isGridOnlyOverviewEnabled) { + overView.overviewActions.clickSplit().getTestActivityTask(2).open() + } else { + overView.currentTask.tapMenu().tapSplitMenuItem().currentTask.open() + } + + val overviewWithSplitPair = launcher.goHome().switchToOverview() + val currentTask = overviewWithSplitPair.currentTask + currentTask.containsContentDescription( + By.pkg(AbstractLauncherUiTest.getAppPackageName()).text("TestActivity3").toString(), + OverviewTask.OverviewTaskContainer.SPLIT_TOP_OR_LEFT, + ) + currentTask.containsContentDescription( + By.pkg(AbstractLauncherUiTest.getAppPackageName()).text("TestActivity2").toString(), + OverviewTask.OverviewTaskContainer.SPLIT_BOTTOM_OR_RIGHT, + ) + return overviewWithSplitPair + } + + private fun clearAllRecentTasks(launcher: LauncherInstrumentation) { + if (launcher.recentTasks.isNotEmpty()) { + launcher.goHome().switchToOverview().dismissAllTasks() + } + } +} diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml deleted file mode 100644 index 3d7ad2bd601..00000000000 --- a/res/anim-v33/shared_x_axis_activity_close_enter.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml deleted file mode 100644 index fb63602d4e0..00000000000 --- a/res/anim-v33/shared_x_axis_activity_close_exit.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml deleted file mode 100644 index cba74ba0ec9..00000000000 --- a/res/anim-v33/shared_x_axis_activity_open_enter.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml deleted file mode 100644 index 22e878d7f10..00000000000 --- a/res/anim-v33/shared_x_axis_activity_open_exit.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - \ 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 deleted file mode 100644 index e54f95380e1..00000000000 --- a/res/color-night-v31/material_color_surface_container_highest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ 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 deleted file mode 100644 index 40f0d4c9def..00000000000 --- a/res/color-night-v31/material_color_surface_container_low.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ 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 24f559b494f..4396f6d0130 100644 --- a/res/color-night-v31/material_color_surface_container_lowest.xml +++ b/res/color-night-v31/material_color_surface_container_lowest.xml @@ -1,6 +1,5 @@ - - + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_inverse.xml b/res/color-night-v31/material_color_surface_inverse.xml deleted file mode 100644 index ac63072a9e5..00000000000 --- a/res/color-night-v31/material_color_surface_inverse.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/popup_shade_first.xml b/res/color-night-v31/popup_shade_first.xml index 6909f815877..e62ed9c8a9e 100644 --- a/res/color-night-v31/popup_shade_first.xml +++ b/res/color-night-v31/popup_shade_first.xml @@ -12,7 +12,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/color-v31/material_color_surface_container_high.xml b/res/color-v31/material_color_surface_container_high.xml deleted file mode 100644 index a996d51eba9..00000000000 --- a/res/color-v31/material_color_surface_container_high.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ 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 deleted file mode 100644 index e7a535af539..00000000000 --- a/res/color-v31/material_color_surface_container_highest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ 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 deleted file mode 100644 index b8fe01e4846..00000000000 --- a/res/color-v31/material_color_surface_container_low.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ 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 25e86668626..f726aea0813 100644 --- a/res/color-v31/material_color_surface_container_lowest.xml +++ b/res/color-v31/material_color_surface_container_lowest.xml @@ -1,6 +1,5 @@ - - + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_dim.xml b/res/color-v31/material_color_surface_dim.xml deleted file mode 100644 index e2d226fa896..00000000000 --- a/res/color-v31/material_color_surface_dim.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_variant.xml b/res/color-v31/material_color_surface_variant.xml deleted file mode 100644 index e2d226fa896..00000000000 --- a/res/color-v31/material_color_surface_variant.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/popup_shade_first.xml b/res/color-v31/popup_shade_first.xml index 4b50cba3c23..9a71caeb51f 100644 --- a/res/color-v31/popup_shade_first.xml +++ b/res/color-v31/popup_shade_first.xml @@ -13,7 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/color/overview_button.xml b/res/color/overview_button.xml index 3cca9c91b70..aa6c618ef7a 100644 --- a/res/color/overview_button.xml +++ b/res/color/overview_button.xml @@ -1,12 +1,11 @@ - + \ No newline at end of file diff --git a/res/color/popup_shade_first.xml b/res/color/popup_shade_first.xml index 4b50cba3c23..9a71caeb51f 100644 --- a/res/color/popup_shade_first.xml +++ b/res/color/popup_shade_first.xml @@ -13,7 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml b/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml deleted file mode 100644 index 47f2a5d73a6..00000000000 --- a/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml index e279fa051a8..39af989a1d5 100644 --- a/res/drawable/add_item_dialog_background.xml +++ b/res/drawable/add_item_dialog_background.xml @@ -1,7 +1,7 @@ - + diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml index 7b27273f28e..d200b9f9615 100644 --- a/res/drawable/all_apps_tabs_background.xml +++ b/res/drawable/all_apps_tabs_background.xml @@ -13,36 +13,25 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + + + - + - - \ No newline at end of file + \ No newline at end of file diff --git a/res/drawable/all_apps_tabs_background_selected.xml b/res/drawable/all_apps_tabs_background_selected.xml new file mode 100644 index 00000000000..f7873dab337 --- /dev/null +++ b/res/drawable/all_apps_tabs_background_selected.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/all_apps_tabs_background_selected_focused.xml b/res/drawable/all_apps_tabs_background_selected_focused.xml new file mode 100644 index 00000000000..28402627ff6 --- /dev/null +++ b/res/drawable/all_apps_tabs_background_selected_focused.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/all_apps_tabs_background_unselected.xml b/res/drawable/all_apps_tabs_background_unselected.xml new file mode 100644 index 00000000000..4004021dfd6 --- /dev/null +++ b/res/drawable/all_apps_tabs_background_unselected.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/all_apps_tabs_background_unselected_focused.xml b/res/drawable/all_apps_tabs_background_unselected_focused.xml new file mode 100644 index 00000000000..3564a0763e5 --- /dev/null +++ b/res/drawable/all_apps_tabs_background_unselected_focused.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/bg_letter_list_text.xml b/res/drawable/bg_letter_list_text.xml new file mode 100644 index 00000000000..073730c7057 --- /dev/null +++ b/res/drawable/bg_letter_list_text.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/bg_ps_header.xml b/res/drawable/bg_ps_header.xml index da314452ad4..87a21e485f1 100644 --- a/res/drawable/bg_ps_header.xml +++ b/res/drawable/bg_ps_header.xml @@ -20,7 +20,7 @@ - + diff --git a/res/drawable/bg_ps_lock_button.xml b/res/drawable/bg_ps_lock_button.xml index aef1e816efa..7a20e49b22d 100644 --- a/res/drawable/bg_ps_lock_button.xml +++ b/res/drawable/bg_ps_lock_button.xml @@ -21,12 +21,12 @@ android:viewportHeight="36"> + android:fillColor="@color/materialColorPrimaryFixedDim"/> + android:fillColor="@color/materialColorOnPrimaryFixed"/> \ No newline at end of file diff --git a/res/drawable/bg_ps_transition_image.xml b/res/drawable/bg_ps_transition_image.xml index dfad3cf5b3c..694303c5174 100644 --- a/res/drawable/bg_ps_transition_image.xml +++ b/res/drawable/bg_ps_transition_image.xml @@ -23,13 +23,13 @@ + android:fillColor="@color/materialColorOnPrimaryFixed"/> + android:fillColor="@color/materialColorPrimaryFixedDim"/> + android:fillColor="@color/materialColorOnPrimaryFixed"/> \ No newline at end of file diff --git a/res/drawable/bg_ps_unlock_button.xml b/res/drawable/bg_ps_unlock_button.xml index d5eedd293e0..563c3f6d682 100644 --- a/res/drawable/bg_ps_unlock_button.xml +++ b/res/drawable/bg_ps_unlock_button.xml @@ -21,9 +21,9 @@ android:viewportHeight="36"> + android:fillColor="@color/materialColorPrimaryFixedDim"/> \ No newline at end of file diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml index ca9448964bf..b0bd33bb513 100644 --- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml +++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml @@ -15,8 +15,7 @@ --> - + diff --git a/res/drawable/bg_widgets_header_states_two_pane.xml b/res/drawable/bg_widgets_header_states_two_pane.xml index 5f4b8c66b49..1ec41a9a0a0 100644 --- a/res/drawable/bg_widgets_header_states_two_pane.xml +++ b/res/drawable/bg_widgets_header_states_two_pane.xml @@ -14,18 +14,16 @@ limitations under the License. --> - - - - - - + + + + - - - - - - + + + + + + diff --git a/res/drawable/bg_widgets_header_two_pane.xml b/res/drawable/bg_widgets_header_two_pane.xml index ca3feef1f06..e2370028989 100644 --- a/res/drawable/bg_widgets_header_two_pane.xml +++ b/res/drawable/bg_widgets_header_two_pane.xml @@ -14,13 +14,10 @@ limitations under the License. --> - - + android:insetTop="@dimen/widget_list_entry_spacing"> + - - + + \ No newline at end of file diff --git a/res/drawable/bg_widgets_header_two_pane_expanded_focused.xml b/res/drawable/bg_widgets_header_two_pane_expanded_focused.xml new file mode 100644 index 00000000000..0ee3d147153 --- /dev/null +++ b/res/drawable/bg_widgets_header_two_pane_expanded_focused.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/bg_widgets_header_two_pane_expanded_unfocused.xml b/res/drawable/bg_widgets_header_two_pane_expanded_unfocused.xml new file mode 100644 index 00000000000..9028ebe2708 --- /dev/null +++ b/res/drawable/bg_widgets_header_two_pane_expanded_unfocused.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/bg_widgets_header_two_pane_unexpanded_focused.xml b/res/drawable/bg_widgets_header_two_pane_unexpanded_focused.xml new file mode 100644 index 00000000000..12dc907bd3d --- /dev/null +++ b/res/drawable/bg_widgets_header_two_pane_unexpanded_focused.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/bg_widgets_header_two_pane_unexpanded_unfocused.xml b/res/drawable/bg_widgets_header_two_pane_unexpanded_unfocused.xml new file mode 100644 index 00000000000..ba26f9fc3e7 --- /dev/null +++ b/res/drawable/bg_widgets_header_two_pane_unexpanded_unfocused.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/button_top_rounded_bordered_ripple.xml b/res/drawable/button_top_rounded_bordered_ripple.xml index f5b68866cb9..723668fb393 100644 --- a/res/drawable/button_top_rounded_bordered_ripple.xml +++ b/res/drawable/button_top_rounded_bordered_ripple.xml @@ -25,7 +25,7 @@ android:topRightRadius="12dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp" /> - + diff --git a/res/drawable/cloud_download_24px.xml b/res/drawable/cloud_download_24px.xml new file mode 100644 index 00000000000..6f7c95aac22 --- /dev/null +++ b/res/drawable/cloud_download_24px.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/drawable/cloud_download_semibold_24px.xml b/res/drawable/cloud_download_semibold_24px.xml new file mode 100644 index 00000000000..ef15f9f7356 --- /dev/null +++ b/res/drawable/cloud_download_semibold_24px.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/drawable/desktop_mode_ic_taskbar_menu_new_window.xml b/res/drawable/desktop_mode_ic_taskbar_menu_new_window.xml new file mode 100644 index 00000000000..b96a596e947 --- /dev/null +++ b/res/drawable/desktop_mode_ic_taskbar_menu_new_window.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_aspect_ratio.xml b/res/drawable/ic_aspect_ratio.xml new file mode 100644 index 00000000000..aafaac4618a --- /dev/null +++ b/res/drawable/ic_aspect_ratio.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/drawable/ic_bubble_button.xml b/res/drawable/ic_bubble_button.xml new file mode 100644 index 00000000000..1ed212e09ae --- /dev/null +++ b/res/drawable/ic_bubble_button.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_chevron_end.xml b/res/drawable/ic_chevron_end.xml new file mode 100644 index 00000000000..9ca4f3a3b4e --- /dev/null +++ b/res/drawable/ic_chevron_end.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/res/drawable/ic_chevron_start.xml b/res/drawable/ic_chevron_start.xml new file mode 100644 index 00000000000..913da026dad --- /dev/null +++ b/res/drawable/ic_chevron_start.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/res/drawable/ic_close_work_edu.xml b/res/drawable/ic_close_work_edu.xml new file mode 100644 index 00000000000..24f61dd543e --- /dev/null +++ b/res/drawable/ic_close_work_edu.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_corp_off.xml b/res/drawable/ic_corp_off.xml index 117258e3bd5..e58e1729cf2 100644 --- a/res/drawable/ic_corp_off.xml +++ b/res/drawable/ic_corp_off.xml @@ -16,9 +16,9 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24" - android:tint="?android:attr/textColorHint"> + android:viewportHeight="24"> - \ No newline at end of file + android:pathData="M16,6H20C21.11,6 22,6.89 22,8V18.99C22,19.021 21.994,19.05 21.989,19.077C21.984,19.102 21.98,19.126 21.98,19.15L20,17.17V8H10.83L8,5.17V4C8,2.89 8.89,2 10,2H14C15.11,2 16,2.89 16,4V6ZM10,6H14V4H10V6ZM19,19L8,8L6,6L2.81,2.81L1.39,4.22L3.3,6.13C2.54,6.41 2.01,7.14 2.01,8L2,19C2,20.11 2.89,21 4,21H18.17L19.78,22.61L21.19,21.2L20.82,20.83L19,19ZM4,8V19H16.17L5.17,8H4Z" + android:fillColor="@color/materialColorOnPrimary" + android:fillType="evenOdd"/> + diff --git a/res/drawable/ic_desktop_add.xml b/res/drawable/ic_desktop_add.xml new file mode 100644 index 00000000000..d31b04b858c --- /dev/null +++ b/res/drawable/ic_desktop_add.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/res/drawable/ic_desktop_with_bg.xml b/res/drawable/ic_desktop_with_bg.xml new file mode 100644 index 00000000000..f54285c4447 --- /dev/null +++ b/res/drawable/ic_desktop_with_bg.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/res/drawable/ic_info_no_shadow.xml b/res/drawable/ic_info_no_shadow.xml index 29a81bd27c1..31cf51200ac 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="?attr/materialColorOnSurface"> + android:tint="@color/materialColorOnSurface"> + android:tint="@color/materialColorOnSurface"> + android:tint="@color/materialColorOnSurface"> diff --git a/res/drawable/ic_more_horiz_24.xml b/res/drawable/ic_more_horiz_24.xml new file mode 100644 index 00000000000..d46827cede9 --- /dev/null +++ b/res/drawable/ic_more_horiz_24.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/drawable/ic_more_vert_dots.xml b/res/drawable/ic_more_vert_dots.xml new file mode 100644 index 00000000000..c4659f821fc --- /dev/null +++ b/res/drawable/ic_more_vert_dots.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_private_profile_divider_badge.xml b/res/drawable/ic_private_profile_divider_badge.xml new file mode 100644 index 00000000000..92292f6bd70 --- /dev/null +++ b/res/drawable/ic_private_profile_divider_badge.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/res/drawable/ic_private_profile_letter_list_fast_scroller_badge.xml b/res/drawable/ic_private_profile_letter_list_fast_scroller_badge.xml new file mode 100644 index 00000000000..8d125988c16 --- /dev/null +++ b/res/drawable/ic_private_profile_letter_list_fast_scroller_badge.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/res/drawable/ic_private_space_with_background.xml b/res/drawable/ic_private_space_with_background.xml index fe85168d3ba..d66549d90d3 100644 --- a/res/drawable/ic_private_space_with_background.xml +++ b/res/drawable/ic_private_space_with_background.xml @@ -13,20 +13,19 @@ limitations under the License. --> + android:fillColor="@color/materialColorSurfaceContainerLowest" /> + android:fillColor="@color/materialColorOnSurface" /> + android:fillColor="@color/materialColorOnSurface" /> diff --git a/res/drawable/ic_ps_settings.xml b/res/drawable/ic_ps_settings.xml index 47edeb85efe..5453f357764 100644 --- a/res/drawable/ic_ps_settings.xml +++ b/res/drawable/ic_ps_settings.xml @@ -24,9 +24,9 @@ android:pathData="M10,10h20v20h-20z"/> + android:fillColor="@color/materialColorOnSurfaceVariant"/> + android:fillColor="@color/materialColorOnSurfaceVariant"/> \ No newline at end of file diff --git a/res/drawable/ic_schedule.xml b/res/drawable/ic_schedule.xml new file mode 100644 index 00000000000..d57b0a78064 --- /dev/null +++ b/res/drawable/ic_schedule.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_split_horizontal.xml b/res/drawable/ic_split_horizontal.xml index 2efd2b9bc20..26efedc4d98 100644 --- a/res/drawable/ic_split_horizontal.xml +++ b/res/drawable/ic_split_horizontal.xml @@ -1,9 +1,25 @@ + - + android:width="20dp" + android:height="20dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + diff --git a/res/drawable/ic_split_vertical.xml b/res/drawable/ic_split_vertical.xml index 9bc97851ab8..787953a52fb 100644 --- a/res/drawable/ic_split_vertical.xml +++ b/res/drawable/ic_split_vertical.xml @@ -1,9 +1,25 @@ + - + android:width="20dp" + android:height="20dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + diff --git a/res/drawable/ic_taskbar_all_apps_button.xml b/res/drawable/ic_taskbar_all_apps_button.xml deleted file mode 100644 index 82fbbea6177..00000000000 --- a/res/drawable/ic_taskbar_all_apps_button.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - diff --git a/res/drawable/ic_taskbar_all_apps_search_button_expressive_theme.xml b/res/drawable/ic_taskbar_all_apps_search_button_expressive_theme.xml new file mode 100644 index 00000000000..ed4a821c79a --- /dev/null +++ b/res/drawable/ic_taskbar_all_apps_search_button_expressive_theme.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_transient_taskbar_all_apps_button.xml b/res/drawable/ic_transient_taskbar_all_apps_button.xml deleted file mode 100644 index 6e740aed4fa..00000000000 --- a/res/drawable/ic_transient_taskbar_all_apps_button.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - diff --git a/res/drawable/ic_uninstall_no_shadow.xml b/res/drawable/ic_uninstall_no_shadow.xml index 6200054f1ba..829e5907801 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="?attr/materialColorOnSurface" > + android:tint="@color/materialColorOnSurface" > diff --git a/res/drawable/ic_unpin.xml b/res/drawable/ic_unpin.xml new file mode 100644 index 00000000000..557b4f9d764 --- /dev/null +++ b/res/drawable/ic_unpin.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/res/drawable/icon_menu_arrow_background.xml b/res/drawable/icon_menu_arrow_background.xml index 8af3c00fad1..1de111ab29e 100644 --- a/res/drawable/icon_menu_arrow_background.xml +++ b/res/drawable/icon_menu_arrow_background.xml @@ -15,14 +15,13 @@ limitations under the License. --> + android:centerColor="@color/materialColorSurfaceBright" + android:endColor="@color/materialColorSurfaceBright" /> \ No newline at end of file diff --git a/res/drawable/inset_rounded_action_button.xml b/res/drawable/inset_rounded_action_button.xml new file mode 100644 index 00000000000..8ae40c07bbb --- /dev/null +++ b/res/drawable/inset_rounded_action_button.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/res/drawable/popup_background.xml b/res/drawable/popup_background.xml index 6eedecb6caf..686456f7ab3 100644 --- a/res/drawable/popup_background.xml +++ b/res/drawable/popup_background.xml @@ -15,6 +15,6 @@ --> - + \ 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 1ea12b3328e..f92dca73d56 100644 --- a/res/drawable/private_space_app_divider.xml +++ b/res/drawable/private_space_app_divider.xml @@ -16,6 +16,6 @@ - + \ 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 cfec2b126d6..1e7fe43527c 100644 --- a/res/drawable/private_space_install_app_icon.xml +++ b/res/drawable/private_space_install_app_icon.xml @@ -13,19 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - - - - - + + + + diff --git a/res/drawable/private_space_install_app_icon_foreground.xml b/res/drawable/private_space_install_app_icon_foreground.xml new file mode 100644 index 00000000000..d55abe73041 --- /dev/null +++ b/res/drawable/private_space_install_app_icon_foreground.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ps_lock_background.xml b/res/drawable/ps_lock_background.xml index 0be83dbd92a..bc66595ac35 100644 --- a/res/drawable/ps_lock_background.xml +++ b/res/drawable/ps_lock_background.xml @@ -21,7 +21,7 @@ - + diff --git a/res/drawable/ps_settings_background.xml b/res/drawable/ps_settings_background.xml index b0c6b5b0d11..7746012d401 100644 --- a/res/drawable/ps_settings_background.xml +++ b/res/drawable/ps_settings_background.xml @@ -18,6 +18,6 @@ android:inset="4dp"> - + \ No newline at end of file diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml index 9deab6eb570..6ee6d65d1f6 100644 --- a/res/drawable/rounded_action_button.xml +++ b/res/drawable/rounded_action_button.xml @@ -7,7 +7,7 @@ ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ - ~ Unless required by applicable law or agreed to in writing, software + ~ Unless required by applicable law or agreed to in writing, soft]ware ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and @@ -16,15 +16,11 @@ - + - + android:color="@color/materialColorSurfaceContainerLow" /> diff --git a/res/drawable/widget_picker_tabs_background.xml b/res/drawable/widget_picker_tabs_background.xml index a874dd8b901..f6607b7ad11 100644 --- a/res/drawable/widget_picker_tabs_background.xml +++ b/res/drawable/widget_picker_tabs_background.xml @@ -13,36 +13,39 @@ See the License for the specific language governing permissions and limitations under the License. --> - + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/widgets_list_expand_button_background.xml b/res/drawable/widgets_list_expand_button_background.xml new file mode 100644 index 00000000000..068b26db433 --- /dev/null +++ b/res/drawable/widgets_list_expand_button_background.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/work_card.xml b/res/drawable/work_card.xml index 1437c6f7d70..0e37d4f7b9e 100644 --- a/res/drawable/work_card.xml +++ b/res/drawable/work_card.xml @@ -17,6 +17,7 @@ - + + diff --git a/res/drawable/work_mode_fab_background.xml b/res/drawable/work_mode_fab_background.xml index 6be33e86ce0..ad795eb4784 100644 --- a/res/drawable/work_mode_fab_background.xml +++ b/res/drawable/work_mode_fab_background.xml @@ -18,7 +18,10 @@ - + + diff --git a/res/drawable/work_scheduler_background.xml b/res/drawable/work_scheduler_background.xml new file mode 100644 index 00000000000..50c81837d8c --- /dev/null +++ b/res/drawable/work_scheduler_background.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml index d113a38ac87..2bb2eb3097e 100644 --- a/res/layout/add_item_confirmation_activity.xml +++ b/res/layout/add_item_confirmation_activity.xml @@ -71,7 +71,8 @@ android:id="@+id/widget_preview_scroll_view" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginVertical="16dp" + android:layout_margin="16dp" + android:background="@drawable/widgets_surface_background" android:layout_weight="1"> + \ No newline at end of file diff --git a/res/layout/all_apps_personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml index e04b207a3e8..1435f8203d5 100644 --- a/res/layout/all_apps_personal_work_tabs.xml +++ b/res/layout/all_apps_personal_work_tabs.xml @@ -21,8 +21,6 @@ android:layout_width="match_parent" android:layout_height="@dimen/all_apps_header_pill_height" android:layout_gravity="center_horizontal" - android:paddingTop="@dimen/all_apps_tabs_vertical_padding" - android:paddingBottom="@dimen/all_apps_tabs_vertical_padding" android:layout_marginTop="@dimen/all_apps_tabs_margin_top" android:orientation="horizontal" style="@style/TextHeadline" @@ -36,6 +34,7 @@ android:layout_weight="1" android:background="@drawable/all_apps_tabs_background" android:text="@string/all_apps_personal_tab" + android:contentDescription="@string/all_apps_personal_tab_content_description" android:textColor="@color/all_apps_tab_text" android:textSize="14sp" style="?android:attr/borderlessButtonStyle" /> @@ -48,6 +47,7 @@ android:layout_weight="1" android:background="@drawable/all_apps_tabs_background" android:text="@string/all_apps_work_tab" + android:contentDescription="@string/all_apps_work_tab_content_description" android:textColor="@color/all_apps_tab_text" android:textSize="14sp" style="?android:attr/borderlessButtonStyle" /> diff --git a/res/layout/bubble_bar_overflow_button.xml b/res/layout/bubble_bar_overflow_button.xml new file mode 100644 index 00000000000..cb54990c860 --- /dev/null +++ b/res/layout/bubble_bar_overflow_button.xml @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/res/layout/fast_scroller_letter_list_text_view.xml b/res/layout/fast_scroller_letter_list_text_view.xml new file mode 100644 index 00000000000..493b6fcb7e7 --- /dev/null +++ b/res/layout/fast_scroller_letter_list_text_view.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml index fe06f455c28..0b3385238bc 100644 --- a/res/layout/launcher.xml +++ b/res/layout/launcher.xml @@ -29,6 +29,7 @@ android:importantForAccessibility="no"> - @@ -97,7 +100,8 @@ android:gravity="center_vertical" android:layout_marginStart="@dimen/ps_header_layout_margin" android:text="@string/ps_container_title" + android:maxLines="1" android:theme="@style/PrivateSpaceHeaderTextStyle" android:importantForAccessibility="no"/> - \ 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 43a8aac8b23..002e7b7eec7 100644 --- a/res/layout/user_folder_icon_normalized.xml +++ b/res/layout/user_folder_icon_normalized.xml @@ -40,12 +40,11 @@ - + android:layout_height="match_parent"> - - \ 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 a292d67cc25..a7c4fbe7e0f 100644 --- a/res/layout/widgets_full_sheet_paged_view.xml +++ b/res/layout/widgets_full_sheet_paged_view.xml @@ -81,6 +81,7 @@ android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:background="@drawable/widgets_surface_background" + android:clipToOutline="true" android:orientation="vertical" android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin" android:visibility="gone"> @@ -103,7 +104,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding" - android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/widget_picker_tabs_background" android:text="@string/widgets_full_sheet_personal_tab" @@ -116,7 +116,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding" - android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/widget_picker_tabs_background" android:text="@string/widgets_full_sheet_work_tab" diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml index 5427732c4d4..1ce1c55ec4b 100644 --- a/res/layout/widgets_full_sheet_recyclerview.xml +++ b/res/layout/widgets_full_sheet_recyclerview.xml @@ -64,6 +64,7 @@ android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:background="@drawable/widgets_surface_background" + android:clipToOutline="true" android:orientation="vertical" android:visibility="gone"> diff --git a/res/layout/widgets_list_expand_button.xml b/res/layout/widgets_list_expand_button.xml new file mode 100644 index 00000000000..ff2d777a0f2 --- /dev/null +++ b/res/layout/widgets_list_expand_button.xml @@ -0,0 +1,34 @@ + + +