diff --git a/ACTIVITY_MANAGER_OWNERS b/ACTIVITY_MANAGER_OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..47782d1406c4d1132182754d4616b4515ea1395e --- /dev/null +++ b/ACTIVITY_MANAGER_OWNERS @@ -0,0 +1,4 @@ +mwachens@google.com +sudheersai@google.com +varunshah@google.com +yamasani@google.com diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 0ca97898e93637d78d05c5ea17a3cfe84692df6e..dd919cacc534e830c4ec2466edac68a27ddc2df6 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -93,6 +93,7 @@ aconfig_declarations_group { "com.android.media.flags.performance-aconfig-java", "com.android.media.flags.projection-aconfig-java", "com.android.net.thread.platform.flags-aconfig-java", + "com.android.ranging.flags.ranging-aconfig-java", "com.android.server.contextualsearch.flags-java", "com.android.server.flags.services-aconfig-java", "com.android.text.flags-aconfig-java", @@ -1549,6 +1550,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Ranging +java_aconfig_library { + name: "com.android.ranging.flags.ranging-aconfig-java", + aconfig_declarations: "ranging_aconfig_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // System Server aconfig_declarations { name: "android.systemserver.flags-aconfig", diff --git a/BATTERY_STATS_OWNERS b/BATTERY_STATS_OWNERS index 7728975fcec176ed22c6bfd7d96bf878d0ac005c..575bded5ad647d6575f7b76dcb0321cb5c90f0a2 100644 --- a/BATTERY_STATS_OWNERS +++ b/BATTERY_STATS_OWNERS @@ -1,4 +1,3 @@ # OWNERS of BatteryStats related files -bookatz@google.com dplotnikov@google.com mwachens@google.com diff --git a/OOM_ADJUSTER_OWNERS b/OOM_ADJUSTER_OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..7727f9f014c0e4dc4a53aa8265ee43ff52fd9984 --- /dev/null +++ b/OOM_ADJUSTER_OWNERS @@ -0,0 +1,3 @@ +mwachens@google.com +dplotnikov@google.com +tyk@google.com diff --git a/TEST_MAPPING b/TEST_MAPPING index dfacbc425181d929281de4d37d4d2c078096f9bd..e469f167d32f6afcc5eb1bc5259f71987ac896ef 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,44 +1,17 @@ { "presubmit-large": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksServicesTests_Presubmit" } ], "presubmit-pm": [ { - "name": "PackageManagerServiceServerTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "PackageManagerServiceServerTests_Presubmit" } ], "presubmit": [ { - "name": "ManagedProvisioningTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ManagedProvisioningTests" }, { "file_patterns": [ @@ -46,86 +19,28 @@ "SystemServer\\.java", "services/tests/apexsystemservices/.*" ], - "name": "ApexSystemServicesTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "ApexSystemServicesTestCases" }, { - "name": "FrameworksUiServicesTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksUiServicesTests" }, { - "name": "FrameworksInputMethodSystemServerTests", - "options": [ - {"include-filter": "com.android.server.inputmethod"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "FrameworksInputMethodSystemServerTests_server_inputmethod" }, { - "name": "ExtServicesUnitTests-tplus", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ExtServicesUnitTests-tplus" }, { - "name": "ExtServicesUnitTests-sminus", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ExtServicesUnitTests-sminus" }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_Presubmit" }, { - "name": "FrameworkPermissionTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworkPermissionTests_Presubmit" }, { - "name": "FrameworksInProcessTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksInProcessTests" }, { "name": "vts_treble_vintf_framework_test" @@ -166,78 +81,25 @@ // infra during the hardening phase. // TODO: this tag to be removed once the above is no longer an issue. { - "name": "FrameworksUiServicesTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksUiServicesTests" }, { - "name": "ExtServicesUnitTests-tplus", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ExtServicesUnitTests-tplus" }, { - "name": "ExtServicesUnitTests-sminus", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ExtServicesUnitTests-sminus" }, { - "name": "TestablesTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TestablesTests" }, { - "name": "FrameworksCoreTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_Presubmit" }, { - "name": "FrameworksServicesTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksServicesTests_presubmit" }, { - "name": "PackageManagerServiceServerTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "PackageManagerServiceServerTests_Presubmit" } ] } diff --git a/apex/jobscheduler/ALARM_OWNERS b/apex/jobscheduler/ALARM_OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..5c3bff7b42f43ab075393966bae830c2c6592882 --- /dev/null +++ b/apex/jobscheduler/ALARM_OWNERS @@ -0,0 +1,2 @@ +suprabh@google.com +tetianameronyk@google.com \ No newline at end of file diff --git a/apex/jobscheduler/DEVICE_IDLE_OWNERS b/apex/jobscheduler/DEVICE_IDLE_OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..62db91db98f85dc96ef4f616eaf8b6ab5caa7957 --- /dev/null +++ b/apex/jobscheduler/DEVICE_IDLE_OWNERS @@ -0,0 +1,2 @@ +suprabh@google.com +guanxin@google.com \ No newline at end of file diff --git a/apex/jobscheduler/JOB_OWNERS b/apex/jobscheduler/JOB_OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..05bf25c50d604aa292d0c62ed7405ec236c0e94e --- /dev/null +++ b/apex/jobscheduler/JOB_OWNERS @@ -0,0 +1,3 @@ +suprabh@google.com +varunshah@google.com +guanxin@google.com \ No newline at end of file diff --git a/apex/jobscheduler/OWNERS b/apex/jobscheduler/OWNERS index 22b648975e5f71814c955abda76b1550d28b458f..ffa58022c4f7e1fbdda78dda88709e33fbafdc16 100644 --- a/apex/jobscheduler/OWNERS +++ b/apex/jobscheduler/OWNERS @@ -1,8 +1,25 @@ -ctate@android.com -ctate@google.com -dplotnikov@google.com -jji@google.com -omakoto@google.com -suprabh@google.com -varunshah@google.com -yamasani@google.com +# For Job Scheduler and App Standby changes, JOB_OWNERS +per-file JOB_OWNERS = file:/apex/jobscheduler/JOB_OWNERS +per-file framework/aconfig/job.aconfig = file:/apex/jobscheduler/JOB_OWNERS +per-file service/aconfig/job.aconfig = file:/apex/jobscheduler/JOB_OWNERS +per-file service/aconfig/app_idle.aconfig = file:/apex/jobscheduler/JOB_OWNERS +per-file *Job* = file:/apex/jobscheduler/JOB_OWNERS +per-file *Standby* = file:/apex/jobscheduler/JOB_OWNERS +per-file framework/java/android/app/job/* = file:/apex/jobscheduler/JOB_OWNERS +per-file framework/java/com/android/server/job/* = file:/apex/jobscheduler/JOB_OWNERS +per-file service/java/com/android/server/job/* = file:/apex/jobscheduler/JOB_OWNERS + +# For Alarm Manager changes, ALARM_OWNERS +per-file ALARM_OWNERS = file:/apex/jobscheduler/ALARM_OWNERS +per-file service/aconfig/alarm.aconfig = file:/apex/jobscheduler/ALARM_OWNERS +per-file *Alarm* = file:/apex/jobscheduler/ALARM_OWNERS +per-file service/java/com/android/server/alarm/* = file:/apex/jobscheduler/ALARM_OWNERS + +# For Device Idle changes, DEVICE_IDLE_OWNERS +per-file DEVICE_IDLE_OWNERS = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file service/aconfig/device_idle.aconfig = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file *Idle* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file service/java/com/android/server/deviceidle/* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file framework/java/com/android/server/deviceidle/* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS + +suprabh@google.com #{LAST_RESORT_SUGGESTION} \ No newline at end of file diff --git a/apex/jobscheduler/framework/java/android/app/job/OWNERS b/apex/jobscheduler/framework/java/android/app/job/OWNERS deleted file mode 100644 index 0b1e559dda15f2cdf54c2798466c6c0da63f2118..0000000000000000000000000000000000000000 --- a/apex/jobscheduler/framework/java/android/app/job/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# Bug component: 330738 - -yamasani@google.com -omakoto@google.com -ctate@android.com -ctate@google.com diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING index b58cb881fadeafc7c239f6c48fc5f82129faa3f9..e3e72f43ef6572f7c7610890039a35a176bb4ddf 100644 --- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING @@ -11,10 +11,7 @@ ], "postsubmit": [ { - "name": "FrameworksMockingServicesTests", - "options": [ - {"include-filter": "com.android.server"} - ] + "name": "FrameworksMockingServicesTests_android_server" } ] } diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING index afa509c6ea9327a50708644b280013dc0a30c07c..ed8851c9304254dc3495474ecedc43ce96a6378b 100644 --- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING @@ -6,10 +6,7 @@ ], "postsubmit": [ { - "name": "FrameworksMockingServicesTests", - "options": [ - {"include-filter": "com.android.server"} - ] + "name": "FrameworksMockingServicesTests_android_server" } ] } diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING index a0bf78f281278bc449eec5b53463d0d027ec84c1..d198bfdd03ee5fa99c497e1c73dec62932252494 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING @@ -1,11 +1,7 @@ { "presubmit": [ { - "name": "CtsJobSchedulerTestCases", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"} - ] + "name": "CtsJobSchedulerTestCases_com_android_server_job" }, { "name": "FrameworksMockingServicesTests_com_android_server_job_Presubmit" @@ -19,26 +15,16 @@ "name": "CtsJobSchedulerTestCases" }, { - "name": "FrameworksMockingServicesTests", - "options": [ - {"include-filter": "com.android.server.job"} - ] + "name": "FrameworksMockingServicesTests_com_android_server_job" }, { "name": "FrameworksServicesTests_com_android_server_job" }, { - "name": "CtsHostsideNetworkPolicyTests", - "options": [ - {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"}, - {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"} - ] + "name": "CtsHostsideNetworkPolicyTests_com_android_server_job" }, { - "name": "CtsStatsdAtomHostTestCases", - "options": [ - {"include-filter": "android.cts.statsdatom.jobscheduler"} - ] + "name": "CtsStatsdAtomHostTestCases_statsdatom_jobscheduler" } ] } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index f56c14da8f23b8f908908538276d05af9bd08548..1a2013daf2cdfa92529f81a71237b817a44ca749 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -1,13 +1,7 @@ { "presubmit": [ { - "name": "CtsUsageStatsTestCases", - "options": [ - {"include-filter": "android.app.usage.cts.UsageStatsTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.MediumTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"} - ] + "name": "CtsUsageStatsTestCases_cts_usagestatstest" }, { "name": "CtsBRSTestCases" diff --git a/api/Android.bp b/api/Android.bp index 533f9f66434b9bb21e1fafbd346614ba6411d52e..3f2316f005bda22b0116d04b4be878d53b43d357 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -102,6 +102,11 @@ combined_apis { "framework-crashrecovery", ], default: [], + }) + select(release_flag("RELEASE_RANGING_STACK"), { + true: [ + "framework-ranging", + ], + default: [], }), system_server_classpath: [ "service-art", diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index b3a674fbd70e2d86ec3d44f1bbf62e801cbb7f28..d1aa23c8ea5fce9f30fef8a544cd019426b8a6c1 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -465,6 +465,32 @@ java_library { ], } +java_library { + name: "android-non-updatable.stubs.system_server", + defaults: ["android-non-updatable_defaults"], + static_libs: [ + "android-non-updatable.stubs.system_server.from-source", + ], + product_variables: { + build_from_text_stub: { + static_libs: [ + "android-non-updatable.stubs.system_server.from-text", + ], + exclude_static_libs: [ + "android-non-updatable.stubs.system_server.from-source", + ], + }, + }, +} + +java_library { + name: "android-non-updatable.stubs.exportable.system_server", + defaults: ["android-non-updatable_defaults"], + static_libs: [ + "android-non-updatable.stubs.exportable.system_server.from-source", + ], +} + java_library { name: "android-non-updatable.stubs.from-source", defaults: [ @@ -561,6 +587,30 @@ java_library { }, } +java_library { + name: "android-non-updatable.stubs.system_server.from-source", + defaults: [ + "android-non-updatable_defaults", + "android-non-updatable_from_source_defaults", + ], + srcs: [":services-non-updatable-stubs"], + libs: non_updatable_api_deps_on_modules, +} + +java_library { + name: "android-non-updatable.stubs.exportable.system_server.from-source", + defaults: [ + "android-non-updatable_defaults", + "android-non-updatable_from_source_defaults", + "android-non-updatable_exportable_from_source_defaults", + ], + srcs: [":services-non-updatable-stubs{.exportable}"], + libs: non_updatable_api_deps_on_modules, + dist: { + dir: "apistubs/android/system-server", + }, +} + java_defaults { name: "android-non-updatable_from_text_defaults", defaults: ["android-non-updatable-stubs-libs-defaults"], @@ -662,6 +712,25 @@ java_api_library { libs: ["all-modules-system-stubs"], } +java_api_library { + name: "android-non-updatable.stubs.system_server.from-text", + api_surface: "system_server", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + "services-non-updatable-stubs.api.contribution", + ], + defaults: [ + "module-classpath-java-defaults", + "android-non-updatable_everything_from_text_defaults", + ], + + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.system-server.latest", +} + java_defaults { name: "android_stubs_dists_default", dist: { @@ -813,9 +882,9 @@ java_library { defaults: [ "android.jar_defaults", ], - srcs: [":services-non-updatable-stubs"], installable: false, static_libs: [ + "android-non-updatable.stubs.system_server", "android_module_lib_stubs_current", ], visibility: ["//frameworks/base/services"], @@ -827,9 +896,9 @@ java_library { "android.jar_defaults", "android_stubs_dists_default", ], - srcs: [":services-non-updatable-stubs{.exportable}"], installable: false, static_libs: [ + "android-non-updatable.stubs.exportable.system_server", "android_module_lib_stubs_current_exportable", ], dist: { diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index fdf9abc49604082346f8e823973b8469bed42e97..c2f6e3072507fcdddc950bed3302b88e2657923e 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -70,8 +70,9 @@ namespace android { using ui::DisplayMode; static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; -static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip"; -static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"; +static const char PRODUCT_BOOTANIMATION_DIR[] = "/product/media/"; +static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "bootanimation-dark.zip"; +static const char PRODUCT_BOOTANIMATION_FILE[] = "bootanimation.zip"; static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip"; static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; @@ -749,8 +750,11 @@ bool BootAnimation::findBootAnimationFileInternal(const std::vector void BootAnimation::findBootAnimationFile() { ATRACE_CALL(); const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1; + const std::string productBootanimationFile = PRODUCT_BOOTANIMATION_DIR + + android::base::GetProperty("ro.product.bootanim.file", playDarkAnim ? + PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE); static const std::vector bootFiles = { - APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, + APEX_BOOTANIMATION_FILE, productBootanimationFile, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE }; static const std::vector shutdownFiles = { diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING index af54a2decd895f96d2ff9fb9f40e2e726061706d..0f502c9904e5b5a7cefa3ffd961b95a025abda67 100644 --- a/cmds/locksettings/TEST_MAPPING +++ b/cmds/locksettings/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit-large": [ { - "name": "CtsDevicePolicyManagerTestCases", - "options": [ - { - "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - } - ] + "name": "CtsDevicePolicyManagerTestCases_LockSettingsTest" } ], "postsubmit": [ diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp index 966bf13adfe464f21c3bd9e7a15849eec264945f..5c5b220d20e9ce0a3c2df92e55c1a4a5fe5c54ee 100644 --- a/cmds/uiautomator/library/Android.bp +++ b/cmds/uiautomator/library/Android.bp @@ -28,9 +28,9 @@ droidstubs { "testrunner-src/**/*.java", ], libs: [ - "android.test.runner", + "android.test.runner.stubs.system", "junit", - "android.test.base", + "android.test.base.stubs.system", "unsupportedappusage", ], installable: false, @@ -56,9 +56,9 @@ droiddoc { ":uiautomator-stubs", ], libs: [ - "android.test.runner", + "android.test.runner.stubs", "junit", - "android.test.base", + "android.test.base.stubs", ], sdk_version: "current", installable: false, diff --git a/core/api/current.txt b/core/api/current.txt index 520c7d1d02f203abc21db71c728a1608df40eec0..d0e20031c74ddb14fd689d614bb9d92edba9fda4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16126,24 +16126,30 @@ package android.graphics { ctor public Gainmap(@NonNull android.graphics.Bitmap); ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap); method public int describeContents(); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") @Nullable public android.graphics.ColorSpace getAlternativeImagePrimaries(); method @NonNull public float getDisplayRatioForFullHdr(); method @NonNull public float[] getEpsilonHdr(); method @NonNull public float[] getEpsilonSdr(); method @NonNull public android.graphics.Bitmap getGainmapContents(); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public int getGainmapDirection(); method @NonNull public float[] getGamma(); method @NonNull public float getMinDisplayRatioForHdrTransition(); method @NonNull public float[] getRatioMax(); method @NonNull public float[] getRatioMin(); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public void setAlternativeImagePrimaries(@Nullable android.graphics.ColorSpace); method public void setDisplayRatioForFullHdr(@FloatRange(from=1.0f) float); method public void setEpsilonHdr(float, float, float); method public void setEpsilonSdr(float, float, float); method public void setGainmapContents(@NonNull android.graphics.Bitmap); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public void setGainmapDirection(int); method public void setGamma(float, float, float); method public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float); method public void setRatioMax(float, float, float); method public void setRatioMin(float, float, float); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public static final int GAINMAP_DIRECTION_HDR_TO_SDR = 1; // 0x1 + field @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public static final int GAINMAP_DIRECTION_SDR_TO_HDR = 0; // 0x0 } public class HardwareBufferRenderer implements java.lang.AutoCloseable { @@ -19207,10 +19213,10 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_INFO_AVAILABLE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_INFO_STRENGTH_DEFAULT_LEVEL; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_INFO_STRENGTH_MAXIMUM_LEVEL; - field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL; - field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_SINGLE_STRENGTH_MAX_LEVEL; - field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_TORCH_STRENGTH_DEFAULT_LEVEL; - field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_TORCH_STRENGTH_MAX_LEVEL; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_SINGLE_STRENGTH_MAX_LEVEL; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_TORCH_STRENGTH_DEFAULT_LEVEL; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_TORCH_STRENGTH_MAX_LEVEL; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key INFO_SESSION_CONFIGURATION_QUERY_VERSION; @@ -19370,11 +19376,9 @@ package android.hardware.camera2 { method @NonNull public java.util.List getSupportedExtensions(); method public boolean isCaptureProcessProgressAvailable(int); method public boolean isPostviewAvailable(int); - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key> EFV_PADDING_ZOOM_FACTOR_RANGE; field public static final int EXTENSION_AUTOMATIC = 0; // 0x0 field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1 field public static final int EXTENSION_BOKEH = 2; // 0x2 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5 field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1 field public static final int EXTENSION_HDR = 3; // 0x3 field public static final int EXTENSION_NIGHT = 4; // 0x4 @@ -19392,7 +19396,7 @@ package android.hardware.camera2 { public abstract static class CameraExtensionSession.ExtensionCaptureCallback { ctor public CameraExtensionSession.ExtensionCaptureCallback(); method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, int); + method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, int); method public void onCaptureProcessProgressed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @IntRange(from=0, to=100) int); method public void onCaptureProcessStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest); method public void onCaptureResultAvailable(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @NonNull android.hardware.camera2.TotalCaptureResult); @@ -19760,7 +19764,7 @@ package android.hardware.camera2 { public final class CaptureRequest extends android.hardware.camera2.CameraMetadata> implements android.os.Parcelable { method public int describeContents(); - method @FlaggedApi("com.android.internal.camera.flags.surface_leak_fix") protected void finalize(); + method protected void finalize(); method @Nullable public T get(android.hardware.camera2.CaptureRequest.Key); method @NonNull public java.util.List> getKeys(); method @Nullable public Object getTag(); @@ -19800,7 +19804,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureRequest.Key EDGE_MODE; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key EXTENSION_STRENGTH; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key FLASH_MODE; - field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CaptureRequest.Key FLASH_STRENGTH_LEVEL; + field @NonNull public static final android.hardware.camera2.CaptureRequest.Key FLASH_STRENGTH_LEVEL; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key HOT_PIXEL_MODE; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key JPEG_GPS_LOCATION; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key JPEG_ORIENTATION; @@ -19897,7 +19901,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key EXTENSION_STRENGTH; field @NonNull public static final android.hardware.camera2.CaptureResult.Key FLASH_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key FLASH_STATE; - field @FlaggedApi("com.android.internal.camera.flags.camera_manual_flash_strength_control") @NonNull public static final android.hardware.camera2.CaptureResult.Key FLASH_STRENGTH_LEVEL; + field @NonNull public static final android.hardware.camera2.CaptureResult.Key FLASH_STRENGTH_LEVEL; field @NonNull public static final android.hardware.camera2.CaptureResult.Key HOT_PIXEL_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key JPEG_GPS_LOCATION; field @NonNull public static final android.hardware.camera2.CaptureResult.Key JPEG_ORIENTATION; @@ -19917,7 +19921,7 @@ package android.hardware.camera2 { field @Deprecated @NonNull public static final android.hardware.camera2.CaptureResult.Key LENS_RADIAL_DISTORTION; field @NonNull public static final android.hardware.camera2.CaptureResult.Key LENS_STATE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION; + field @NonNull public static final android.hardware.camera2.CaptureResult.Key LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION; field @NonNull public static final android.hardware.camera2.CaptureResult.Key NOISE_REDUCTION_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key REPROCESS_EFFECTIVE_EXPOSURE_FACTOR; field @NonNull public static final android.hardware.camera2.CaptureResult.Key REQUEST_PIPELINE_DEPTH; @@ -19943,7 +19947,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_FACE_DETECT_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_HOT_PIXEL_MAP; field @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_HOT_PIXEL_MAP_MODE; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_INTRINSICS_SAMPLES; + field @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_INTRINSICS_SAMPLES; field @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_SHADING_CORRECTION_MAP; field @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_SHADING_MAP_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key STATISTICS_OIS_DATA_MODE; @@ -19974,30 +19978,6 @@ package android.hardware.camera2 { field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100 } - @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureRequest { - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key EFV_AUTO_ZOOM; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key EFV_MAX_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key EFV_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key EFV_ROTATE_VIEWPORT; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key EFV_STABILIZATION_MODE; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key> EFV_TRANSLATE_VIEWPORT; - } - - @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureResult { - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_AUTO_ZOOM; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_AUTO_ZOOM_PADDING_REGION; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_MAX_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_PADDING_REGION; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_ROTATE_VIEWPORT; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_STABILIZATION_MODE; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key EFV_TARGET_COORDINATES; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key> EFV_TRANSLATE_VIEWPORT; - } - public class MultiResolutionImageReader implements java.lang.AutoCloseable { ctor public MultiResolutionImageReader(@NonNull java.util.Collection, int, @IntRange(from=1) int); method public void close(); @@ -20127,10 +20107,10 @@ package android.hardware.camera2.params { method public boolean isMultiResolution(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class LensIntrinsicsSample { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public LensIntrinsicsSample(long, @NonNull float[]); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public float[] getLensIntrinsics(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestampNanos(); + public final class LensIntrinsicsSample { + ctor public LensIntrinsicsSample(long, @NonNull float[]); + method @NonNull public float[] getLensIntrinsics(); + method public long getTimestampNanos(); } public final class LensShadingMap { @@ -20459,23 +20439,23 @@ package android.hardware.fingerprint { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler); method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints(); method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected(); - field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 - field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 - field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 - field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 - field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 - field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 - field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc - field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 - field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 - field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 - field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb - field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 - field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 - field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa - field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 + field @Deprecated public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 + field @Deprecated public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc + field @Deprecated public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 + field @Deprecated public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 + field @Deprecated public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 + field @Deprecated public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb + field @Deprecated public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 + field @Deprecated public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 + field @Deprecated public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 + field @Deprecated public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa + field @Deprecated public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 } @Deprecated public abstract static class FingerprintManager.AuthenticationCallback { @@ -32633,6 +32613,13 @@ package android.os { method public boolean isCharging(); field public static final String ACTION_CHARGING = "android.os.action.CHARGING"; field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING"; + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1; // 0x1 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_FULL = 5; // 0x5 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4; // 0x4 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_LOW = 2; // 0x2 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3; // 0x3 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0; // 0x0 + field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1; // 0xffffffff field public static final int BATTERY_HEALTH_COLD = 7; // 0x7 field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4 field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2 @@ -32657,6 +32644,7 @@ package android.os { field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4 field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1 field public static final String EXTRA_BATTERY_LOW = "battery_low"; + field @FlaggedApi("android.os.battery_part_status_api") public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL"; field public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS"; field public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT"; field public static final String EXTRA_HEALTH = "health"; @@ -33248,6 +33236,7 @@ package android.os { } public interface IBinder { + method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException; method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException; @@ -33256,6 +33245,7 @@ package android.os { method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException; method public boolean pingBinder(); method @Nullable public android.os.IInterface queryLocalInterface(@NonNull String); + method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default boolean removeFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback); method public boolean transact(int, @NonNull android.os.Parcel, @Nullable android.os.Parcel, int) throws android.os.RemoteException; method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int); field public static final int DUMP_TRANSACTION = 1598311760; // 0x5f444d50 @@ -33273,6 +33263,12 @@ package android.os { method public default void binderDied(@NonNull android.os.IBinder); } + @FlaggedApi("android.os.binder_frozen_state_change_callback") public static interface IBinder.FrozenStateChangeCallback { + method public void onFrozenStateChanged(@NonNull android.os.IBinder, int); + field public static final int STATE_FROZEN = 0; // 0x0 + field public static final int STATE_UNFROZEN = 1; // 0x1 + } + public interface IInterface { method public android.os.IBinder asBinder(); } @@ -36322,9 +36318,9 @@ package android.provider { method @Deprecated public static int getTypeLabelResource(int); field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im"; field @Deprecated public static final String CUSTOM_PROTOCOL = "data6"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; field @Deprecated public static final String PROTOCOL = "data5"; field @Deprecated public static final int PROTOCOL_AIM = 0; // 0x0 field @Deprecated public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff @@ -36457,9 +36453,9 @@ package android.provider { method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence); method @Deprecated public static int getTypeLabelResource(int); field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; field @Deprecated public static final String SIP_ADDRESS = "data1"; field @Deprecated public static final int TYPE_HOME = 1; // 0x1 field @Deprecated public static final int TYPE_OTHER = 3; // 0x3 @@ -36932,15 +36928,18 @@ package android.provider { field public static final String CONTENT_DIRECTORY = "data"; } - @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccountAndState { - ctor public ContactsContract.RawContacts.DefaultAccountAndState(int, @Nullable android.accounts.Account); + @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount { + ctor public ContactsContract.RawContacts.DefaultAccount(); + } + + @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState { + ctor public ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState(int, @Nullable android.accounts.Account); method @Nullable public android.accounts.Account getCloudAccount(); method public int getState(); - method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account); - method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofLocal(); - method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofNotSet(); + method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account); + method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofLocal(); + method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofNotSet(); field public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; // 0x3 - field public static final int DEFAULT_ACCOUNT_STATE_INVALID = 0; // 0x0 field public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; // 0x2 field public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; // 0x1 } @@ -44087,7 +44086,7 @@ package android.telephony { } public static final class CarrierConfigManager.Gps { - field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool"; + field @FlaggedApi("android.location.flags.enable_ni_supl_message_injection_by_carrier_config_bugfix") public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = "gps.enable_ni_supl_message_injection_bool"; field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool"; field public static final String KEY_PREFIX = "gps."; } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 1e94c2fc219c83e280b9ee990dbe4a1e3ad40ba3..8447a7feb54ead5d341903fca14647db219a1f91 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -321,7 +321,10 @@ package android.net.netstats { package android.net.wifi { public final class WifiMigration { - method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static void migrateLegacyKeystoreToWifiBlobstore(); + method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static int migrateLegacyKeystoreToWifiBlobstore(); + field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION = 2; // 0x2 + field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE = 0; // 0x0 + field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED = 1; // 0x1 } } @@ -384,8 +387,10 @@ package android.os { field public static final int DEVICE_INITIAL_SDK_INT; } - public class Environment { - method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeDirectory(); + public class Handler { + method @FlaggedApi("android.os.mainline_vcn_platform_api") public final boolean hasMessagesOrCallbacks(); + method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeCallbacksAndEqualMessages(@Nullable Object); + method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeEqualMessages(int, @Nullable Object); } public class IpcDataCache { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9c807afb06a8cf2699cc6f8a1fe1739c8c3cb36a..cc0354c32de2421ebd056e59be5975787f32b12e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3835,6 +3835,7 @@ package android.content { field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence"; field public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller"; field public static final String PERMISSION_SERVICE = "permission"; + field @FlaggedApi("com.android.ranging.flags.ranging_stack_enabled") public static final String RANGING_SERVICE = "ranging"; field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness"; field public static final String ROLLBACK_SERVICE = "rollback"; field public static final String SAFETY_CENTER_SERVICE = "safety_center"; @@ -4949,94 +4950,94 @@ package android.hardware.camera2 { package android.hardware.camera2.extension { - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class AdvancedExtender { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public AdvancedExtender(@NonNull android.hardware.camera2.CameraManager); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List getAvailableCaptureRequestKeys(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List getAvailableCaptureResultKeys(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") @NonNull public abstract java.util.List> getAvailableCharacteristicsKeyValues(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getMetadataVendorId(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map> getSupportedCaptureOutputResolutions(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map> getSupportedPreviewOutputResolutions(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void initialize(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); - } - - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class CameraExtensionService extends android.app.Service { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected CameraExtensionService(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean onRegisterClient(@NonNull android.os.IBinder); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onUnregisterClient(@NonNull android.os.IBinder); - } - - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size); + public abstract class AdvancedExtender { + ctor public AdvancedExtender(@NonNull android.hardware.camera2.CameraManager); + method @NonNull public abstract java.util.List getAvailableCaptureRequestKeys(@NonNull String); + method @NonNull public abstract java.util.List getAvailableCaptureResultKeys(@NonNull String); + method @NonNull public abstract java.util.List> getAvailableCharacteristicsKeyValues(); + method public long getMetadataVendorId(@NonNull String); + method @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor(); + method @NonNull public abstract java.util.Map> getSupportedCaptureOutputResolutions(@NonNull String); + method @NonNull public abstract java.util.Map> getSupportedPreviewOutputResolutions(@NonNull String); + method public abstract void initialize(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); + method public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); + } + + public abstract class CameraExtensionService extends android.app.Service { + ctor protected CameraExtensionService(); + method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); + method @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int); + method public abstract boolean onRegisterClient(@NonNull android.os.IBinder); + method public abstract void onUnregisterClient(@NonNull android.os.IBinder); + } + + public final class CameraOutputSurface { + ctor public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size); method public int getColorSpace(); method public long getDynamicRangeProfile(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface(); + method public int getImageFormat(); + method @NonNull public android.util.Size getSize(); + method @NonNull public android.view.Surface getSurface(); method public void setDynamicRangeProfile(long); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.Set getCameraIds(); + public class CharacteristicsMap { + method @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String); + method @NonNull public java.util.Set getCameraIds(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List, @Nullable android.hardware.camera2.CaptureRequest); + public class ExtensionConfiguration { + ctor public ExtensionConfiguration(int, int, @NonNull java.util.List, @Nullable android.hardware.camera2.CaptureRequest); method public void setColorSpace(int); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionOutputConfiguration(@NonNull java.util.List, int, @Nullable String, int); + public class ExtensionOutputConfiguration { + ctor public ExtensionOutputConfiguration(@NonNull java.util.List, int, @Nullable String, int); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class RequestProcessor { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void abortCaptures(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void stopRepeating(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submitBurst(@NonNull java.util.List, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; + public final class RequestProcessor { + method public void abortCaptures(); + method public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; + method public void stopRepeating(); + method public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; + method public int submitBurst(@NonNull java.util.List, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List, @NonNull java.util.List>, int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List> getParameters(); + public static final class RequestProcessor.Request { + ctor public RequestProcessor.Request(@NonNull java.util.List, @NonNull java.util.List>, int); + method @NonNull public java.util.List> getParameters(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int, long); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long); + public static interface RequestProcessor.RequestCallback { + method public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int); + method public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult); + method public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure); + method public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult); + method public void onCaptureSequenceAborted(int); + method public void onCaptureSequenceCompleted(int, long); + method public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class SessionProcessor { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public SessionProcessor(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void deInitSession(@NonNull android.os.IBinder); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionEnd(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startMultiFrameCapture(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startRepeating(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void stopRepeating(); + public abstract class SessionProcessor { + ctor public SessionProcessor(); + method public abstract void deInitSession(@NonNull android.os.IBinder); + method @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface); + method public abstract void onCaptureSessionEnd(); + method public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String); + method public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest); + method public abstract int startMultiFrameCapture(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method public abstract int startRepeating(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method public abstract void stopRepeating(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface SessionProcessor.CaptureCallback { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(long, int, @NonNull java.util.Map); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(int, int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProcessStarted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(int, long); + public static interface SessionProcessor.CaptureCallback { + method public void onCaptureCompleted(long, int, @NonNull java.util.Map); + method public void onCaptureFailed(int, int); + method public void onCaptureProcessStarted(int); + method public void onCaptureSequenceAborted(int); + method public void onCaptureSequenceCompleted(int); + method public void onCaptureStarted(int, long); } } @@ -10773,6 +10774,7 @@ package android.os { public class Environment { method @NonNull public static java.io.File getDataCePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String); method @NonNull public static java.io.File getDataDePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String); + method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeDirectory(); method @NonNull public static java.util.Collection getInternalMediaDirectories(); method @NonNull public static java.io.File getOdmDirectory(); method @NonNull public static java.io.File getOemDirectory(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index caf699280e08786d91ef8ad53018a8cef1cc769f..72a68f85b6b7dd7c4ede0db631a9bf3d7fd5c8f2 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1797,18 +1797,20 @@ package android.hardware.input { public class InputSettings { method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context); - method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") public static int getAccessibilityRepeatKeysDelay(@NonNull android.content.Context); - method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") public static int getAccessibilityRepeatKeysTimeout(@NonNull android.content.Context); method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context); + method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysDelay(@NonNull android.content.Context); + method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static int getRepeatKeysTimeout(@NonNull android.content.Context); method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") public static boolean isAccessibilityMouseKeysEnabled(@NonNull android.content.Context); method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context); + method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") public static boolean isRepeatKeysEnabled(@NonNull android.content.Context); method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int); method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityMouseKeysEnabled(@NonNull android.content.Context, boolean); - method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityRepeatKeysDelay(@NonNull android.content.Context, int); - method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityRepeatKeysTimeout(@NonNull android.content.Context, int); method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int); method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float); + method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysDelay(@NonNull android.content.Context, int); + method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysEnabled(@NonNull android.content.Context, boolean); + method @FlaggedApi("com.android.input.flags.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setRepeatKeysTimeout(@NonNull android.content.Context, int); field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0 } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4350545a1b1d51753844d71b797fcdf8cc2af2e4..3bc3a93060de27d708bb684646d7e89407a3b5a0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -255,6 +255,7 @@ import libcore.io.ForwardingOs; import libcore.io.IoUtils; import libcore.io.Os; import libcore.net.event.NetworkEventDispatcher; +import libcore.util.NativeAllocationRegistry; import org.apache.harmony.dalvik.ddmc.DdmVmInternal; @@ -1609,6 +1610,32 @@ public final class ActivityThread extends ClientTransactionHandler } } + @NeverCompile + private void dumpMemInfoNativeAllocations(PrintWriter pw) { + pw.println(" "); + pw.println(" Native Allocations"); + printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "Count", "", "Total(kB)"); + printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "------", "", "------"); + + for (NativeAllocationRegistry.Metrics m : NativeAllocationRegistry.getMetrics()) { + // group into 3 major categories: Bitmap, HardwareBuffer and Other + final String className = switch (m.getClassName()) { + case "android.graphics.Bitmap" -> "Bitmap"; + case "android.hardware.HardwareBuffer" -> "HardwareBuffer"; + default -> "Other"; + }; + + if (m.getMallocedCount() != 0 || m.getMallocedBytes() != 0) { + printRow(pw, TWO_COUNT_COLUMNS, className + " (malloced):", + m.getMallocedCount(), "", m.getMallocedBytes() / 1024); + } + if (m.getNonmallocedCount() != 0 || m.getNonmallocedBytes() != 0) { + printRow(pw, TWO_COUNT_COLUMNS, className + " (nonmalloced):", + m.getNonmallocedCount(), "", m.getNonmallocedBytes() / 1024); + } + } + } + @NeverCompile private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, @@ -1707,6 +1734,10 @@ public final class ActivityThread extends ClientTransactionHandler printRow(pw, TWO_COUNT_COLUMNS, "Death Recipients:", binderDeathObjectCount, "WebViews:", webviewInstanceCount); + if (com.android.libcore.Flags.nativeMetrics()) { + dumpMemInfoNativeAllocations(pw); + } + // SQLite mem info pw.println(" "); pw.println(" SQL"); @@ -8288,12 +8319,12 @@ public final class ActivityThread extends ClientTransactionHandler } Context c = null; ApplicationInfo ai = info.applicationInfo; - if (context.getPackageName().equals(ai.packageName)) { + if (context != null && context.getPackageName().equals(ai.packageName)) { c = context; } else if (mInitialApplication != null && mInitialApplication.getPackageName().equals(ai.packageName)) { c = mInitialApplication; - } else { + } else if (context != null) { try { c = context.createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index dbf9afdd52ff4c6bebc466010bce22cfcfa5535a..ed6b85125e6625e7940cfa6961d7294a66ae42ef 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -16,6 +16,7 @@ package android.app; +import static android.app.PropertyInvalidatedCache.createSystemCacheKey; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -817,7 +818,7 @@ public class ApplicationPackageManager extends PackageManager { private final static PropertyInvalidatedCache mHasSystemFeatureCache = new PropertyInvalidatedCache( - 256, "cache_key.has_system_feature") { + 256, createSystemCacheKey("has_system_feature")) { @Override public Boolean recompute(HasSystemFeatureQuery query) { try { @@ -1127,7 +1128,7 @@ public class ApplicationPackageManager extends PackageManager { } private static final String CACHE_KEY_PACKAGES_FOR_UID_PROPERTY = - "cache_key.get_packages_for_uid"; + createSystemCacheKey("get_packages_for_uid"); private static final PropertyInvalidatedCache mGetPackagesForUidCache = new PropertyInvalidatedCache( diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index adeb0451cd4308f7d8714fd3b85b61c0e99e68e7..cd7e40cf174d1894a1decdaba8080040e5fcd3dc 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -112,6 +112,7 @@ per-file *VoiceInteract* = file:/core/java/android/service/voice/OWNERS # Wallpaper per-file *Wallpaper* = file:/core/java/android/service/wallpaper/OWNERS +per-file wallpaper.aconfig = file:/core/java/android/service/wallpaper/OWNERS # WindowManager per-file *Activity* = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 0c786cb20fdce5cfc6ab0930e065341737a9c174..0e761fce9346b1cad62cf59b7a0729b2ea1c943f 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -19,6 +19,7 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -282,6 +283,12 @@ public class PropertyInvalidatedCache { * @hide */ + /** + * The well-known key prefix. + * @hide + */ + private static final String CACHE_KEY_PREFIX = "cache_key"; + /** * The module used for unit tests and cts tests. It is expected that no process in * the system has permissions to write properties with this module. @@ -366,7 +373,44 @@ public class PropertyInvalidatedCache { } } - return "cache_key." + module + "." + new String(suffix); + return CACHE_KEY_PREFIX + "." + module + "." + new String(suffix); + } + + /** + * All legal keys start with one of the following strings. + */ + private static final String[] sValidKeyPrefix = { + CACHE_KEY_PREFIX + "." + MODULE_SYSTEM + ".", + CACHE_KEY_PREFIX + "." + MODULE_BLUETOOTH + ".", + CACHE_KEY_PREFIX + "." + MODULE_TELEPHONY + ".", + CACHE_KEY_PREFIX + "." + MODULE_TEST + ".", + }; + + /** + * Verify that the property name conforms to the standard. Log a warning if this is not true. + * Note that this is done once in the cache constructor; it does not have to be very fast. + */ + private void validateCacheKey(String name) { + if (Build.IS_USER) { + // Do not bother checking keys in user builds. The keys will have been tested in + // eng/userdebug builds already. + return; + } + for (int i = 0; i < sValidKeyPrefix.length; i++) { + if (name.startsWith(sValidKeyPrefix[i])) return; + } + Log.w(TAG, "invalid cache name: " + name); + } + + /** + * Create a cache key for the system module. The parameter is the API name. This reduces + * some of the boilerplate in system caches. It is not needed in other modules because other + * modules must use the {@link IpcDataCache} interfaces. + * @hide + */ + @NonNull + public static String createSystemCacheKey(@NonNull String api) { + return createPropertyName(MODULE_SYSTEM, api); } /** @@ -561,6 +605,7 @@ public class PropertyInvalidatedCache { public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName, @NonNull String cacheName) { mPropertyName = propertyName; + validateCacheKey(mPropertyName); mCacheName = cacheName; mMaxEntries = maxEntries; mComputer = new DefaultComputer<>(this); @@ -584,6 +629,7 @@ public class PropertyInvalidatedCache { public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api, @NonNull String cacheName, @NonNull QueryHandler computer) { mPropertyName = createPropertyName(module, api); + validateCacheKey(mPropertyName); mCacheName = cacheName; mMaxEntries = maxEntries; mComputer = computer; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 337939fd2388cca1ed688ae5172d97f7e73f174c..03bec71548a8be4a21a49bb894df59fd32b96801 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1667,11 +1667,20 @@ public final class SystemServiceRegistry { @Override public WearableSensingManager createService(ContextImpl ctx) throws ServiceNotFoundException { - IBinder iBinder = ServiceManager.getServiceOrThrow( + IBinder iBinder = ServiceManager.getService( Context.WEARABLE_SENSING_SERVICE); - IWearableSensingManager manager = - IWearableSensingManager.Stub.asInterface(iBinder); - return new WearableSensingManager(ctx.getOuterContext(), manager); + if (iBinder != null) { + IWearableSensingManager manager = + IWearableSensingManager.Stub.asInterface(iBinder); + return new WearableSensingManager(ctx.getOuterContext(), manager); + } + // Wear intentionally removes the service, so do not throw a + // ServiceNotFoundException when the service is not absent. + if (ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) + && android.server.Flags.removeWearableSensingServiceFromWear()) { + return null; + } + throw new ServiceNotFoundException(Context.WEARABLE_SENSING_SERVICE); }}); registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class, diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 2358d67c55e8fe241215781cb5905c75f42264ca..5ed1f4e355335d2ce6c43c521832c8b5524b3042 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -16,12 +16,7 @@ }, { "file_patterns": ["(/|^)AppOpsManager.java"], - "name": "CtsAppOpsTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsAppOpsTestCases" }, { "file_patterns": ["(/|^)AppOpsManager.java"], @@ -54,12 +49,7 @@ "file_patterns": ["INotificationManager\\.aidl"] }, { - "name": "CtsWindowManagerDeviceWindow", - "options": [ - { - "include-filter": "android.server.wm.window.ToastWindowTest" - } - ], + "name": "CtsWindowManagerDeviceWindow_window_toastwindowtest", "file_patterns": ["INotificationManager\\.aidl"] }, { @@ -67,42 +57,15 @@ "file_patterns": ["(/|^)InstantAppResolve[^/]*"] }, { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-filter": "android.autofillservice.cts.saveui.AutofillSaveDialogTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ], + "name": "CtsAutoFillServiceTestCases_saveui_autofillsavedialogtest", "file_patterns": ["(/|^)Activity.java"] }, { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-filter": "android.autofillservice.cts.saveui.PreSimpleSaveActivityTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ], + "name": "CtsAutoFillServiceTestCases_saveui_presimplesaveactivitytest", "file_patterns": ["(/|^)Activity.java"] }, { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-filter": "android.autofillservice.cts.saveui.SimpleSaveActivityTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.AppModeFull" - } - ], + "name": "CtsAutoFillServiceTestCases_saveui_simplesaveactivitytest", "file_patterns": ["(/|^)Activity.java"] }, { @@ -119,32 +82,10 @@ }, { "name": "CtsLocalVoiceInteraction", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ], "file_patterns": ["(/|^)VoiceInteract[^/]*"] }, { - "name": "CtsOsTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.os.cts.StrictModeTest" - } - ], + "name": "CtsOsTestCases_cts_strictmodetest_Presubmit", "file_patterns": ["(/|^)ContextImpl.java"] }, { @@ -153,12 +94,7 @@ }, { "file_patterns": ["(/|^)LocaleManager.java"], - "name": "CtsLocaleManagerTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsLocaleManagerTestCases" }, { "name": "FrameworksCoreTests_keyguard_manager", @@ -173,172 +109,51 @@ ] }, { - "name": "FrameworksCoreGameManagerTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.app" - } - ], + "name": "FrameworksCoreGameManagerTests_android_app", "file_patterns": [ "(/|^)GameManager[^/]*", "(/|^)GameMode[^/]*" ] }, { - "name": "HdmiCecTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.hardware.hdmi" - } - ], + "name": "HdmiCecTests_hardware_hdmi", "file_patterns": [ "(/|^)DeviceFeature[^/]*", "(/|^)Hdmi[^/]*" ] }, { - "name": "CtsWindowManagerDeviceActivity", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceActivity_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceAm", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceAm_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceBackNavigation", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceBackNavigation_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceDisplay", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceDisplay_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceKeyguard", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceKeyguard_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceInsets", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceInsets_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceTaskFragment", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceTaskFragment_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceWindow", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceWindow_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { - "name": "CtsWindowManagerDeviceOther", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.content.wm.cts" - } - ], + "name": "CtsWindowManagerDeviceOther_wm_cts", "file_patterns": ["(/|^)ContextImpl.java"] }, { diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 081dfe60d28c13f910fc0ea2694543219857ddbb..d9f886d41aa8ffe0476bc8f817dcbc43cfef8937 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -134,6 +134,14 @@ flag { bug: "364338410" } +flag { + name: "lock_now_coexistence" + is_exported: true + namespace: "enterprise" + description: "Enables coexistence support for lockNow." + bug: "366559840" +} + # Fully rolled out and must not be used. flag { name: "security_log_v2_enabled" @@ -323,3 +331,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "user_provisioning_same_state" + namespace: "enterprise" + description: "Handle exceptions while setting same provisioning state." + bug: "326441417" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index f5c5a11f45fbcb03d693b347193d7933a6b9e8e0..83b5aa05c383694f87df46db883532ad69c04ef8 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -164,7 +164,13 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { */ @Nullable public Boolean getEnabled() { - return (Boolean) getProperty(PROPERTY_ENABLED); + // We can't use getPropertyBoolean here. getPropertyBoolean returns false instead of null + // if the value is missing. + boolean[] enabled = getPropertyBooleanArray(PROPERTY_ENABLED); + if (enabled == null || enabled.length == 0) { + return null; + } + return enabled[0]; } /** Returns the qualified id linking to the static metadata of the app function. */ @@ -180,13 +186,8 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { * * @param packageName the name of the package that owns the function. * @param functionId the id of the function. - * @param staticMetadataQualifiedId the qualified static metadata id that this runtime - * metadata refers to. */ - public Builder( - @NonNull String packageName, - @NonNull String functionId, - @NonNull String staticMetadataQualifiedId) { + public Builder(@NonNull String packageName, @NonNull String functionId) { super( APP_FUNCTION_RUNTIME_NAMESPACE, getDocumentIdForAppFunction( @@ -198,17 +199,24 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { // Set qualified id automatically setPropertyString( - PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID, staticMetadataQualifiedId); + PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID, + AppFunctionStaticMetadataHelper.getStaticMetadataQualifiedId( + packageName, functionId)); } /** * Sets an indicator specifying if the function is enabled or not. This would override the * default enabled state in the static metadata ({@link - * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). + * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to + * null to clear the override. */ @NonNull - public Builder setEnabled(boolean enabled) { - setPropertyBoolean(PROPERTY_ENABLED, enabled); + public Builder setEnabled(@Nullable Boolean enabled) { + if (enabled == null) { + setPropertyBoolean(PROPERTY_ENABLED); + } else { + setPropertyBoolean(PROPERTY_ENABLED, enabled); + } return this; } diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index c27141a1acbf3cd3a6979db068a195efd54032bc..0d981ea5a679eb31cdbd8607c24b3a001f3a28c8 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -26,6 +26,7 @@ import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.IBinder; @@ -60,29 +61,53 @@ public abstract class AppFunctionService extends Service { @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; - private final Binder mBinder = - new IAppFunctionService.Stub() { - @Override - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull IExecuteAppFunctionCallback callback) { - if (AppFunctionService.this.checkCallingPermission(BIND_APP_FUNCTION_SERVICE) - == PERMISSION_DENIED) { - throw new SecurityException("Can only be called by the system server."); - } - SafeOneTimeExecuteAppFunctionCallback safeCallback = - new SafeOneTimeExecuteAppFunctionCallback(callback); - try { - AppFunctionService.this.onExecuteFunction(request, safeCallback::onResult); - } catch (Exception ex) { - // Apps should handle exceptions. But if they don't, report the error on - // behalf of them. - safeCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - getResultCode(ex), ex.getMessage(), /* extras= */ null)); - } + /** + * Functional interface to represent the execution logic of an app function. + * + * @hide + */ + @FunctionalInterface + public interface OnExecuteFunction { + /** + * Performs the semantic of executing the function specified by the provided request and + * return the response through the provided callback. + */ + void perform( + @NonNull ExecuteAppFunctionRequest request, + @NonNull Consumer callback); + } + + /** @hide */ + @NonNull + public static Binder createBinder( + @NonNull Context context, @NonNull OnExecuteFunction onExecuteFunction) { + return new IAppFunctionService.Stub() { + @Override + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull IExecuteAppFunctionCallback callback) { + if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE) + == PERMISSION_DENIED) { + throw new SecurityException("Can only be called by the system server."); + } + SafeOneTimeExecuteAppFunctionCallback safeCallback = + new SafeOneTimeExecuteAppFunctionCallback(callback); + try { + onExecuteFunction.perform(request, safeCallback::onResult); + } catch (Exception ex) { + // Apps should handle exceptions. But if they don't, report the error on + // behalf of them. + safeCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + getResultCode(ex), ex.getMessage(), /* extras= */ null)); } - }; + } + }; + } + + private final Binder mBinder = createBinder( + AppFunctionService.this, + AppFunctionService.this::onExecuteFunction); @NonNull @Override diff --git a/core/java/android/app/appfunctions/TEST_MAPPING b/core/java/android/app/appfunctions/TEST_MAPPING new file mode 100644 index 0000000000000000000000000000000000000000..91e82ec0e95b930f469d80057b1770397ae1758d --- /dev/null +++ b/core/java/android/app/appfunctions/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "postsubmit": [ + { + "name": "FrameworksAppFunctionsTests" + }, + { + "name": "CtsAppFunctionTestCases" + } + ] +} \ No newline at end of file diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java index 7948cec545c3a10caa9b83ebf393e4dfd939e787..db663f8ed4c4c08cb74fa56b323933b191fb3e0f 100644 --- a/core/java/android/app/compat/ChangeIdStateCache.java +++ b/core/java/android/app/compat/ChangeIdStateCache.java @@ -16,6 +16,8 @@ package android.app.compat; +import static android.app.PropertyInvalidatedCache.createSystemCacheKey; + import android.annotation.NonNull; import android.app.PropertyInvalidatedCache; import android.content.Context; @@ -31,7 +33,7 @@ import com.android.internal.compat.IPlatformCompat; */ public final class ChangeIdStateCache extends PropertyInvalidatedCache { - private static final String CACHE_KEY = "cache_key.is_compat_change_enabled"; + private static final String CACHE_KEY = createSystemCacheKey("is_compat_change_enabled"); private static final int MAX_ENTRIES = 2048; private static boolean sDisabled = false; private volatile IPlatformCompat mPlatformCompat; diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 9891e8930936d748ec633b9b9e754d84f0419c9e..9b06adf4e89461a7aab71b66cc7822399dadfc19 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -236,4 +236,12 @@ flag { namespace: "systemui" description: "Guards new android.app.richongoingnotification api" bug: "337261753" +} + +flag { + name: "ui_rich_ongoing" + is_exported: true + namespace: "systemui" + description: "Guards new android.app.richongoingnotification promotion and new uis" + bug: "337261753" } \ No newline at end of file diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING index 7673acacbfbef1e92532067053ba1dff97a5a283..9e416385bbfb61efeff9d94d97b725435b5bf21a 100644 --- a/core/java/android/app/time/TEST_MAPPING +++ b/core/java/android/app/time/TEST_MAPPING @@ -1,31 +1,13 @@ { "presubmit": [ { - "name": "FrameworksTimeCoreTests", - "options": [ - { - "include-filter": "android.app." - } - ] + "name": "FrameworksTimeCoreTests_android_app" }, { - "name": "CtsTimeTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTimeTestCases" }, { - "name": "FrameworksTimeServicesTests", - "options": [ - { - "include-filter": "com.android.server.timezonedetector." - }, - { - "include-filter": "com.android.server.timedetector." - } - ] + "name": "FrameworksTimeServicesTests_time" } ] } diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING index c7ca6a230d43200cd46bb66c29f902616c2c7d8a..d876308f4a9bb395894e439988854bea8083bc5d 100644 --- a/core/java/android/app/timedetector/TEST_MAPPING +++ b/core/java/android/app/timedetector/TEST_MAPPING @@ -1,28 +1,13 @@ { "presubmit": [ { - "name": "FrameworksTimeCoreTests", - "options": [ - { - "include-filter": "android.app." - } - ] + "name": "FrameworksTimeCoreTests_android_app" }, { - "name": "CtsTimeTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTimeTestCases" }, { - "name": "FrameworksTimeServicesTests", - "options": [ - { - "include-filter": "com.android.server.timedetector." - } - ] + "name": "FrameworksTimeServicesTests_server_timedetector" } ] } diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING index c8d0bb2306cd4509cc1267e55395721330076b18..dca7bbf17ab99588851ff4ae74930f8187c8035e 100644 --- a/core/java/android/app/timezonedetector/TEST_MAPPING +++ b/core/java/android/app/timezonedetector/TEST_MAPPING @@ -1,28 +1,13 @@ { "presubmit": [ { - "name": "FrameworksTimeCoreTests", - "options": [ - { - "include-filter": "android.app." - } - ] + "name": "FrameworksTimeCoreTests_android_app" }, { - "name": "CtsTimeTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTimeTestCases" }, { - "name": "FrameworksTimeServicesTests", - "options": [ - { - "include-filter": "com.android.server.timezonedetector." - } - ] + "name": "FrameworksTimeServicesTests_server_timezonedetector" } ] } diff --git a/core/java/android/app/trust/TEST_MAPPING b/core/java/android/app/trust/TEST_MAPPING index 23923eeb83ee9f1fe528952300266f3f15a35d78..b0dd55100c8a196b274a02bdedc31485004f1521 100644 --- a/core/java/android/app/trust/TEST_MAPPING +++ b/core/java/android/app/trust/TEST_MAPPING @@ -1,28 +1,12 @@ { "presubmit": [ { - "name": "TrustTests", - "options": [ - { - "include-filter": "android.trust.test" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TrustTests_trust_test" } ], "trust-tablet": [ { - "name": "TrustTests", - "options": [ - { - "include-filter": "android.trust.test" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TrustTests_trust_test" } ] } \ No newline at end of file diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..409162202b5994855cb9b5cd9622e81fc1084eb0 --- /dev/null +++ b/core/java/android/app/wallpaper.aconfig @@ -0,0 +1,8 @@ +package: "android.app" +container: "system" +flag { + name: "remove_next_wallpaper_component" + namespace: "systemui" + description: "Remove deprecated field WallpaperData#nextWallpaperComponent. Only effective after rebooting." + bug: "365991991" +} diff --git a/core/java/android/companion/IOnAssociationsChangedListener.aidl b/core/java/android/companion/IOnAssociationsChangedListener.aidl index d3694564ab7be8187451da93910f89ab5c19491f..eba3804cf5b3feba1891e74e5a70e39c44c8678d 100644 --- a/core/java/android/companion/IOnAssociationsChangedListener.aidl +++ b/core/java/android/companion/IOnAssociationsChangedListener.aidl @@ -19,23 +19,6 @@ package android.companion; import android.companion.AssociationInfo; /** @hide */ -interface IOnAssociationsChangedListener { - - /* - * IMPORTANT: This method is intentionally NOT "oneway". - * - * The method is intentionally "blocking" to make sure that the clients of the - * addOnAssociationsChangedListener() API (@SystemAPI guarded by a "signature" permission) are - * able to prevent race conditions that may arise if their own clients (applications) - * effectively get notified about the changes before system services do. - * - * This is safe for 2 reasons: - * 1. The addOnAssociationsChangedListener() is only available to the system components - * (guarded by a "signature" permission). - * See android.permission.MANAGE_COMPANION_DEVICES. - * 2. On the Java side addOnAssociationsChangedListener() in CDM takes an Executor, and the - * proxy implementation of onAssociationsChanged() simply "post" a Runnable to it. - * See CompanionDeviceManager.OnAssociationsChangedListenerProxy class. - */ +oneway interface IOnAssociationsChangedListener { void onAssociationsChanged(in List associations); } \ No newline at end of file diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 12c5d0756fc9c7f262819b0ec517d98087b63e4e..91f7a8bae16358549601640b5228f3251fd7eff7 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4324,6 +4324,7 @@ public abstract class Context { SECURITY_STATE_SERVICE, //@hide: ECM_ENHANCED_CONFIRMATION_SERVICE, CONTACT_KEYS_SERVICE, + RANGING_SERVICE, }) @Retention(RetentionPolicy.SOURCE) @@ -6400,6 +6401,17 @@ public abstract class Context { @SystemApi public static final String UWB_SERVICE = "uwb"; + /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.ranging.RangingManager}. + * + * @see #getSystemService(String) + * @hide + */ + @FlaggedApi(com.android.ranging.flags.Flags.FLAG_RANGING_STACK_ENABLED) + @SystemApi + public static final String RANGING_SERVICE = "ranging"; + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.app.DreamManager} for controlling Dream states. diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING index e353a0107baba86dbb3146ac12cab266a4a529df..8d90b021fbfcb7a47fe2a2963d2ecdcebaf18863 100644 --- a/core/java/android/content/TEST_MAPPING +++ b/core/java/android/content/TEST_MAPPING @@ -1,24 +1,7 @@ { "presubmit": [ { - "name": "CtsOsTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.os.cts.StrictModeTest" - } - ], + "name": "CtsOsTestCases_cts_strictmodetest_Presubmit", "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"] }, { diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING index 82c47a03863b3f4ad356ff7cbe99a82bdf568f47..b36c8958ea7131dd79bf961d02fc83481542b354 100644 --- a/core/java/android/content/om/TEST_MAPPING +++ b/core/java/android/content/om/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.om" - } - ] + "name": "FrameworksServicesTests_server_om" }, { "name": "OverlayDeviceTests" diff --git a/core/java/android/content/pm/Android.bp b/core/java/android/content/pm/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..057b5da71d62ff00e827ee4642abaff270b8d5fc --- /dev/null +++ b/core/java/android/content/pm/Android.bp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package { + default_team: "trendy_team_framework_android_packages", + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "framework-pm-sources", + srcs: [ + "**/*.java", + "**/*.aidl", + ], + exclude_srcs: [ + "dex/**/*.java", + "overlay/**/*.java", + "permission/**/*.java", + ], + visibility: ["//frameworks/base"], +} diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 52c84dc0ac5dc96c69c27a8ab4b403ff3690ac12..26f919f99ee9c5248e79e4fd798496edede6606e 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -778,8 +778,18 @@ public class LauncherApps { public List getActivityList(String packageName, UserHandle user) { logErrorForInvalidProfileAccess(user); try { - return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(), - packageName, user), user); + final List activityList = convertToActivityList( + mService.getLauncherActivities( + mContext.getPackageName(), + packageName, + user + ), user); + if (activityList.isEmpty()) { + // b/350144057 + Log.d(TAG, "getActivityList: No launchable activities found for" + + "packageName=" + packageName + ", user=" + user); + } + return activityList; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index ffadd1e4083cd6b30d8185856094a776528e53ad..2cdae21bde92e47b2fc8e703db3762c631f69c18 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -205,6 +205,17 @@ } ] }, + { + "name": "CtsPackageInstallerCUJDeviceAdminTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, { "name": "CtsPackageInstallerCUJInstallationTestCases", "options":[ @@ -216,6 +227,17 @@ } ] }, + { + "name": "CtsPackageInstallerCUJMultiUsersTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, { "name": "CtsPackageInstallerCUJUninstallationTestCases", "options":[ diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 9eec7a4e8f71ec2431178557798ee4c7f354d134..34f3b61a8a88d17c13993701d65b47c5d9b5e3be 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -241,6 +241,16 @@ flag { is_fixed_read_only: true } +flag { + name: "caches_not_invalidated_at_start_read_only" + namespace: "multiuser" + description: "PIC need to be invalidated at start in order to work properly." + bug: "356167673" + metadata { + purpose: PURPOSE_BUGFIX + } + is_fixed_read_only: true +} # This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile. flag { diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING index 8a1982a339eae1d38383439df670e666fb208232..db98c402eeeb6b3e0a0b94b47a6dd2a4946895e7 100644 --- a/core/java/android/content/pm/verify/domain/TEST_MAPPING +++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "PackageManagerServiceUnitTests", - "options": [ - { - "include-filter": "com.android.server.pm.test.verify.domain" - } - ] + "name": "PackageManagerServiceUnitTests_verify_domain" }, { "name": "CtsDomainVerificationDeviceStandaloneTestCases" diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index ce0f9f598897b92f5726d73e53b02ff4631fe32e..8c87ad3353b6480e09503c22b8b04a94fa5591b7 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -277,6 +277,17 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { return new HardwareBuffer(nativeObject); } + /** + * @hide + */ + private static NativeAllocationRegistry getRegistry(long size) { + final long func = nGetNativeFinalizer(); + final Class cls = HardwareBuffer.class; + return com.android.libcore.Flags.nativeMetrics() + ? NativeAllocationRegistry.createNonmalloced(cls, func, size) + : NativeAllocationRegistry.createNonmalloced(cls.getClassLoader(), func, size); + } + /** * Private use only. See {@link #create(int, int, int, int, long)}. May also be * called from JNI using an already allocated native HardwareBuffer. @@ -285,10 +296,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { private HardwareBuffer(long nativeObject) { mNativeObject = nativeObject; long bufferSize = nEstimateSize(nativeObject); - ClassLoader loader = HardwareBuffer.class.getClassLoader(); - NativeAllocationRegistry registry = new NativeAllocationRegistry( - loader, nGetNativeFinalizer(), bufferSize); - mCleaner = registry.registerNativeAllocation(this, mNativeObject); + mCleaner = getRegistry(bufferSize).registerNativeAllocation(this, mNativeObject); mCloseGuard.open("HardwareBuffer.close"); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 7a8a16f4b98adbdad280b833e7223227ba9e9ecf..37983df75f2b2e776bf6660857172287a42abbc9 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1484,7 +1484,6 @@ public final class CameraCharacteristics extends CameraMetadata FLASH_SINGLE_STRENGTH_MAX_LEVEL = new Key("android.flash.singleStrengthMaxLevel", int.class); @@ -1500,7 +1499,6 @@ public final class CameraCharacteristics extends CameraMetadata FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL = new Key("android.flash.singleStrengthDefaultLevel", int.class); @@ -1524,7 +1522,6 @@ public final class CameraCharacteristics extends CameraMetadata FLASH_TORCH_STRENGTH_MAX_LEVEL = new Key("android.flash.torchStrengthMaxLevel", int.class); @@ -1540,7 +1537,6 @@ public final class CameraCharacteristics extends CameraMetadata FLASH_TORCH_STRENGTH_DEFAULT_LEVEL = new Key("android.flash.torchStrengthDefaultLevel", int.class); @@ -5976,28 +5972,6 @@ public final class CameraCharacteristics extends CameraMetadata JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION = new Key("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); - /** - *

Minimum and maximum padding zoom factors supported by this camera device for - * android.efv.paddingZoomFactor used for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension.

- *

The minimum and maximum padding zoom factors supported by the device for - * android.efv.paddingZoomFactor used as part of the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension feature. This extension specific camera characteristic can be queried using - * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.

- *

Units: A pair of padding zoom factors in floating-points: - * (minPaddingZoomFactor, maxPaddingZoomFactor)

- *

Range of valid values:

- *

1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key> EFV_PADDING_ZOOM_FACTOR_RANGE = - new Key>("android.efv.paddingZoomFactorRange", new TypeReference>() {{ }}); - /** * Mapping from INFO_SESSION_CONFIGURATION_QUERY_VERSION to session characteristics key. diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 04a810a89f47234cc5c74e38eb3d8318d23d854a..3cf508a6db00f260fac12b8f0ab2d85edf6dca42 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -140,12 +140,6 @@ public final class CameraExtensionCharacteristics { */ public static final int EXTENSION_NIGHT = 4; - /** - * An extension that aims to lock and stabilize a given region or object of interest. - */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; - /** * @hide */ @@ -154,8 +148,7 @@ public final class CameraExtensionCharacteristics { EXTENSION_FACE_RETOUCH, EXTENSION_BOKEH, EXTENSION_HDR, - EXTENSION_NIGHT, - EXTENSION_EYES_FREE_VIDEOGRAPHY}) + EXTENSION_NIGHT}) public @interface Extension { } @@ -266,10 +259,8 @@ public final class CameraExtensionCharacteristics { private static final String PROXY_SERVICE_NAME = "com.android.cameraextensions.CameraExtensionsProxyService"; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) private static final int FALLBACK_PACKAGE_NAME = com.android.internal.R.string.config_extensionFallbackPackageName; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) private static final int FALLBACK_SERVICE_NAME = com.android.internal.R.string.config_extensionFallbackServiceName; @@ -316,7 +307,7 @@ public final class CameraExtensionCharacteristics { intent.setClassName(vendorProxyPackage, vendorProxyService); } - if (Flags.concertMode() && useFallback) { + if (useFallback) { String packageName = ctx.getResources().getString(FALLBACK_PACKAGE_NAME); String serviceName = ctx.getResources().getString(FALLBACK_SERVICE_NAME); @@ -440,7 +431,7 @@ public final class CameraExtensionCharacteristics { releaseProxyConnectionLocked(ctx, extension); } - if (Flags.concertMode() && ret && useFallback && mIsFallbackEnabled) { + if (ret && useFallback && mIsFallbackEnabled) { try { InitializeSessionHandler cb = new InitializeSessionHandler(ctx); initializeSession(cb, extension); @@ -469,26 +460,24 @@ public final class CameraExtensionCharacteristics { boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/); - if (Flags.concertMode()) { - // Check if user enabled fallback impl - ContentResolver resolver = ctx.getContentResolver(); - int userEnabled = Settings.Secure.getInt(resolver, - Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1); - - boolean vendorImpl = true; - if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) { - // At this point, we are connected to either CameraExtensionsProxyService or - // the vendor extension proxy service. If the vendor does not support the - // extension, unregisterClient and re-register client with the proxy service - // containing the fallback impl - vendorImpl = isExtensionSupported(cameraId, extension, - characteristicsMapNative); - } + // Check if user enabled fallback impl + ContentResolver resolver = ctx.getContentResolver(); + int userEnabled = Settings.Secure.getInt(resolver, + Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1); - if (!vendorImpl) { - unregisterClient(ctx, token, extension); - ret = registerClientHelper(ctx, token, extension, true /*useFallback*/); - } + boolean vendorImpl = true; + if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) { + // At this point, we are connected to either CameraExtensionsProxyService or + // the vendor extension proxy service. If the vendor does not support the + // extension, unregisterClient and re-register client with the proxy service + // containing the fallback impl + vendorImpl = isExtensionSupported(cameraId, extension, + characteristicsMapNative); + } + + if (!vendorImpl) { + unregisterClient(ctx, token, extension); + ret = registerClientHelper(ctx, token, extension, true /*useFallback*/); } return ret; @@ -634,9 +623,6 @@ public final class CameraExtensionCharacteristics { public ExtensionConnectionManager() { IntArray extensionList = new IntArray(EXTENSION_LIST.length); extensionList.addAll(EXTENSION_LIST); - if (Flags.concertModeApi()) { - extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY); - } for (int extensionType : extensionList.toArray()) { mConnections.put(extensionType, new ExtensionConnection()); @@ -837,9 +823,6 @@ public final class CameraExtensionCharacteristics { IntArray extensionList = new IntArray(EXTENSION_LIST.length); extensionList.addAll(EXTENSION_LIST); - if (Flags.concertModeApi()) { - extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY); - } for (int extensionType : extensionList.toArray()) { try { @@ -1598,28 +1581,4 @@ public final class CameraExtensionCharacteristics { return Collections.unmodifiableSet(ret); } - - - /** - *

Minimum and maximum padding zoom factors supported by this camera device for - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for - * the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension.

- *

The minimum and maximum padding zoom factors supported by the device for - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension feature. This extension specific camera characteristic can be queried using - * {@link android.hardware.camera2.CameraExtensionCharacteristics#get}.

- *

Units: A pair of padding zoom factors in floating-points: - * (minPaddingZoomFactor, maxPaddingZoomFactor)

- *

Range of valid values:

- *

1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor

- *

Optional - The value for this key may be {@code null} on some devices.

- */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key> EFV_PADDING_ZOOM_FACTOR_RANGE = - CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE; } diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java index 2d9433e31ab28cc30cf4cc7b4e4dcba5802930c0..20f89a55dd3bc4dbf2b9011e3b534fc4ec477d19 100644 --- a/core/java/android/hardware/camera2/CameraExtensionSession.java +++ b/core/java/android/hardware/camera2/CameraExtensionSession.java @@ -156,7 +156,6 @@ public abstract class CameraExtensionSession implements AutoCloseable { * @see #capture * @see #setRepeatingRequest */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public void onCaptureFailed(@NonNull CameraExtensionSession session, @NonNull CaptureRequest request, @CaptureFailure.FailureReason int failure) { // default empty implementation diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index d40b2e342fbbad9e429efd32967efe37fb7c8b76..21627920f598013e067ba35c32ab316fdcb53664 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -818,7 +818,7 @@ public final class CameraManager { } boolean hasConcurrentStreams = - CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId, + CameraManagerGlobal.get().cameraIdHasConcurrentStreams(cameraId, mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); metadata.setHasMandatoryConcurrentStreams(hasConcurrentStreams); @@ -2629,24 +2629,26 @@ public final class CameraManager { * @return Whether the camera device was found in the set of combinations returned by * getConcurrentCameraIds */ - public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId, + public boolean cameraIdHasConcurrentStreams(String cameraId, int deviceId, int devicePolicy) { - DeviceCameraInfo info = new DeviceCameraInfo(cameraId, - devicePolicy == DEVICE_POLICY_DEFAULT ? DEVICE_ID_DEFAULT : deviceId); - if (!mDeviceStatus.containsKey(info)) { - // physical camera ids aren't advertised in concurrent camera id combinations. - if (DEBUG) { - Log.v(TAG, " physical camera id " + cameraId + " is hidden." + - " Available logical camera ids : " + mDeviceStatus); + synchronized (mLock) { + DeviceCameraInfo info = new DeviceCameraInfo(cameraId, + devicePolicy == DEVICE_POLICY_DEFAULT ? DEVICE_ID_DEFAULT : deviceId); + if (!mDeviceStatus.containsKey(info)) { + // physical camera ids aren't advertised in concurrent camera id combinations. + if (DEBUG) { + Log.v(TAG, " physical camera id " + cameraId + " is hidden." + + " Available logical camera ids : " + mDeviceStatus); + } + return false; } - return false; - } - for (Set comb : mConcurrentCameraIdCombinations) { - if (comb.contains(info)) { - return true; + for (Set comb : mConcurrentCameraIdCombinations) { + if (comb.contains(info)) { + return true; + } } + return false; } - return false; } public void setTorchMode( diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 4819f67cb5660ca8c438a2cdaa6780e3bbc633c9..a69a3713319204498612b7d51fe7d20ef13af883 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -3896,36 +3896,6 @@ public abstract class CameraMetadata { */ public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2; - // - // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE - // - - /** - *

No stabilization.

- * @see CaptureRequest#EFV_STABILIZATION_MODE - * @hide - */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final int EFV_STABILIZATION_MODE_OFF = 0; - - /** - *

Gimbal stabilization mode.

- * @see CaptureRequest#EFV_STABILIZATION_MODE - * @hide - */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; - - /** - *

Locked stabilization mode which uses the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * stabilization to directionally steady the target region.

- * @see CaptureRequest#EFV_STABILIZATION_MODE - * @hide - */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final int EFV_STABILIZATION_MODE_LOCKED = 2; - // // Enumeration values for CaptureResult#CONTROL_AE_STATE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index ac721168a5f3e64eea3c83a97d5ca88d9b7e6af6..3f5ae919657749dcfae7aa7172a7dfcf8a413ed6 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -612,9 +612,7 @@ public final class CaptureRequest extends CameraMetadata> Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader(), Surface.class); if (parcelableArray != null) { - if (Flags.surfaceLeakFix()) { - mReleaseSurfaces = true; - } + mReleaseSurfaces = true; for (Parcelable p : parcelableArray) { Surface s = (Surface) p; mSurfaceSet.add(s); @@ -798,7 +796,6 @@ public final class CaptureRequest extends CameraMetadata> } @SuppressWarnings("Finalize") - @FlaggedApi(Flags.FLAG_SURFACE_LEAK_FIX) @Override protected void finalize() { if (mReleaseSurfaces) { @@ -2733,7 +2730,6 @@ public final class CaptureRequest extends CameraMetadata> */ @PublicKey @NonNull - @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL) public static final Key FLASH_STRENGTH_LEVEL = new Key("android.flash.strengthLevel", int.class); @@ -4328,146 +4324,6 @@ public final class CaptureRequest extends CameraMetadata> public static final Key EXTENSION_STRENGTH = new Key("android.extension.strength", int.class); - /** - *

Used to apply an additional digital zoom factor for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. - * This additional zoom factor serves as a buffer to provide more flexibility for the - * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } - * mode. If android.efv.paddingZoomFactor is not set, the default will be used. - * The effectiveness of the stabilization may be influenced by the amount of padding zoom - * applied. A higher padding zoom factor can stabilize the target region more effectively - * with greater flexibility but may potentially impact image quality. Conversely, a lower - * padding zoom factor may be used to prioritize preserving image quality, albeit with less - * leeway in stabilizing the target region. It is recommended to set the - * android.efv.paddingZoomFactor to at least 1.5.

- *

If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden. - * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the - * padding zoom factor during android.efv.autoZoom.

- *

Range of valid values:
- * android.efv.paddingZoomFactorRange

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_PADDING_ZOOM_FACTOR = - new Key("android.efv.paddingZoomFactor", float.class); - - /** - *

Used to enable or disable auto zoom for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Turn on auto zoom to let the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature decide at any given point a combination of - * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor - * to keep the target region in view and stabilized. The combination chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested - * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting - * to control image quality further using android.efv.maxPaddingZoomFactor.

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_AUTO_ZOOM = - new Key("android.efv.autoZoom", boolean.class); - - /** - *

Used to limit the android.efv.paddingZoomFactor if - * android.efv.autoZoom is enabled for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

If android.efv.autoZoom is enabled, this key can be used to set a limit - * on the android.efv.paddingZoomFactor chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode - * to control image quality.

- *

Range of valid values:
- * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to - * the android.efv.paddingZoomFactor to effectively utilize this key.

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_MAX_PADDING_ZOOM_FACTOR = - new Key("android.efv.maxPaddingZoomFactor", float.class); - - /** - *

Set the stabilization mode for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension

- *

The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked - * video stabilization. Locked mode uses the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * stabilization feature to fixate on the current region, utilizing it as the target area for - * stabilization.

- *

Possible values:

- *
    - *
  • {@link #EFV_STABILIZATION_MODE_OFF OFF}
  • - *
  • {@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}
  • - *
  • {@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}
  • - *
- * - *

Optional - The value for this key may be {@code null} on some devices.

- * @see #EFV_STABILIZATION_MODE_OFF - * @see #EFV_STABILIZATION_MODE_GIMBAL - * @see #EFV_STABILIZATION_MODE_LOCKED - * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_STABILIZATION_MODE = - new Key("android.efv.stabilizationMode", int.class); - - /** - *

Used to update the target region for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

A android.util.Pair that represents the desired - * shift of the current locked view (or target region) in - * pixels. Negative values indicate left and upward shifts, while positive values indicate - * right and downward shifts in the active array coordinate system.

- *

Range of valid values:
- * android.util.Pair represents the - * shift. The range for the horizontal shift is - * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)]. - * The range for the vertical shift is - * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key> EFV_TRANSLATE_VIEWPORT = - new Key>("android.efv.translateViewport", new TypeReference>() {{ }}); - - /** - *

Representing the desired clockwise rotation - * of the target region in degrees for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Value representing the desired clockwise rotation of the target - * region in degrees.

- *

Range of valid values:
- * 0 to 360

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_ROTATE_VIEWPORT = - new Key("android.efv.rotateViewport", float.class); - /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 34ce92c0f498711b963d326af15664d8b0efbbcd..a18a634918f91be876f3c28ded8dabd6567f73f3 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -3022,7 +3022,6 @@ public class CaptureResult extends CameraMetadata> { */ @PublicKey @NonNull - @FlaggedApi(Flags.FLAG_CAMERA_MANUAL_FLASH_STRENGTH_CONTROL) public static final Key FLASH_STRENGTH_LEVEL = new Key("android.flash.strengthLevel", int.class); @@ -5294,7 +5293,6 @@ public class CaptureResult extends CameraMetadata> { @PublicKey @NonNull @SyntheticKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key STATISTICS_LENS_INTRINSICS_SAMPLES = new Key("android.statistics.lensIntrinsicsSamples", android.hardware.camera2.params.LensIntrinsicsSample[].class); @@ -5308,7 +5306,6 @@ public class CaptureResult extends CameraMetadata> { * @see CaptureResult#SENSOR_TIMESTAMP * @hide */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key STATISTICS_LENS_INTRINSIC_TIMESTAMPS = new Key("android.statistics.lensIntrinsicTimestamps", long[].class); @@ -5324,7 +5321,6 @@ public class CaptureResult extends CameraMetadata> { * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE * @hide */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key STATISTICS_LENS_INTRINSIC_SAMPLES = new Key("android.statistics.lensIntrinsicSamples", float[].class); @@ -5815,7 +5811,6 @@ public class CaptureResult extends CameraMetadata> { */ @PublicKey @NonNull - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION = new Key("android.logicalMultiCamera.activePhysicalSensorCropRegion", android.graphics.Rect.class); @@ -5940,214 +5935,6 @@ public class CaptureResult extends CameraMetadata> { public static final Key EXTENSION_STRENGTH = new Key("android.extension.strength", int.class); - /** - *

The padding region for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

An array [left, top, right, bottom] of the padding in pixels remaining on all four sides - * before the target region starts to go out of bounds.

- *

The padding region denotes the area surrounding the stabilized target region within which - * the camera can be moved while maintaining the target region in view. As the camera moves, - * the padding region adjusts to represent the proximity of the target region to the - * boundary, which is the point at which the target region will start to go out of bounds.

- *

Range of valid values:
- * The padding is the number of remaining pixels of padding in each direction. - * The pixels reference the active array coordinate system. Negative values indicate the target - * region is out of bounds. The value for this key may be null for when the stabilization mode is - * in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_OFF } - * or {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_GIMBAL } mode.

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_PADDING_REGION = - new Key("android.efv.paddingRegion", int[].class); - - /** - *

The padding region when android.efv.autoZoom is enabled for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

An array [left, top, right, bottom] of the padding in pixels remaining on all four sides - * before the target region starts to go out of bounds.

- *

This may differ from android.efv.paddingRegion as the field of view can change - * during android.efv.autoZoom, altering the boundary region and thus updating the padding between the - * target region and the boundary.

- *

Range of valid values:
- * The padding is the number of remaining pixels of padding in each direction - * when android.efv.autoZoom is enabled. Negative values indicate the target region is out of bounds. - * The value for this key may be null for when the android.efv.autoZoom is not enabled.

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_AUTO_ZOOM_PADDING_REGION = - new Key("android.efv.autoZoomPaddingRegion", int[].class); - - /** - *

List of coordinates representing the target region relative to the - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE } - * for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in - * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

A list of android.graphics.PointF that define the coordinates of the target region - * relative to the - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }. - * The array represents the target region coordinates as: top-left, top-right, bottom-left, - * bottom-right.

- *

Range of valid values:
- * The list of target coordinates will define a region within the bounds of the - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_TARGET_COORDINATES = - new Key("android.efv.targetCoordinates", android.graphics.PointF[].class); - - /** - *

Used to apply an additional digital zoom factor for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. - * This additional zoom factor serves as a buffer to provide more flexibility for the - * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } - * mode. If android.efv.paddingZoomFactor is not set, the default will be used. - * The effectiveness of the stabilization may be influenced by the amount of padding zoom - * applied. A higher padding zoom factor can stabilize the target region more effectively - * with greater flexibility but may potentially impact image quality. Conversely, a lower - * padding zoom factor may be used to prioritize preserving image quality, albeit with less - * leeway in stabilizing the target region. It is recommended to set the - * android.efv.paddingZoomFactor to at least 1.5.

- *

If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden. - * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the - * padding zoom factor during android.efv.autoZoom.

- *

Range of valid values:
- * android.efv.paddingZoomFactorRange

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_PADDING_ZOOM_FACTOR = - new Key("android.efv.paddingZoomFactor", float.class); - - /** - *

Set the stabilization mode for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension

- *

The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked - * video stabilization. Locked mode uses the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * stabilization feature to fixate on the current region, utilizing it as the target area for - * stabilization.

- *

Possible values:

- *
    - *
  • {@link #EFV_STABILIZATION_MODE_OFF OFF}
  • - *
  • {@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}
  • - *
  • {@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}
  • - *
- * - *

Optional - The value for this key may be {@code null} on some devices.

- * @see #EFV_STABILIZATION_MODE_OFF - * @see #EFV_STABILIZATION_MODE_GIMBAL - * @see #EFV_STABILIZATION_MODE_LOCKED - * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_STABILIZATION_MODE = - new Key("android.efv.stabilizationMode", int.class); - - /** - *

Used to enable or disable auto zoom for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Turn on auto zoom to let the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature decide at any given point a combination of - * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor - * to keep the target region in view and stabilized. The combination chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested - * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting - * to control image quality further using android.efv.maxPaddingZoomFactor.

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_AUTO_ZOOM = - new Key("android.efv.autoZoom", boolean.class); - - /** - *

Representing the desired clockwise rotation - * of the target region in degrees for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Value representing the desired clockwise rotation of the target - * region in degrees.

- *

Range of valid values:
- * 0 to 360

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_ROTATE_VIEWPORT = - new Key("android.efv.rotateViewport", float.class); - - /** - *

Used to update the target region for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

A android.util.Pair that represents the desired - * shift of the current locked view (or target region) in - * pixels. Negative values indicate left and upward shifts, while positive values indicate - * right and downward shifts in the active array coordinate system.

- *

Range of valid values:
- * android.util.Pair represents the - * shift. The range for the horizontal shift is - * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)]. - * The range for the vertical shift is - * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key> EFV_TRANSLATE_VIEWPORT = - new Key>("android.efv.translateViewport", new TypeReference>() {{ }}); - - /** - *

Used to limit the android.efv.paddingZoomFactor if - * android.efv.autoZoom is enabled for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

If android.efv.autoZoom is enabled, this key can be used to set a limit - * on the android.efv.paddingZoomFactor chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode - * to control image quality.

- *

Range of valid values:
- * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to - * the android.efv.paddingZoomFactor to effectively utilize this key.

- *

Optional - The value for this key may be {@code null} on some devices.

- * @hide - */ - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_MAX_PADDING_ZOOM_FACTOR = - new Key("android.efv.maxPaddingZoomFactor", float.class); - /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java deleted file mode 100644 index b681ce40dfd919b28a5eb62da4def02121c4b086..0000000000000000000000000000000000000000 --- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java +++ /dev/null @@ -1,229 +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 android.hardware.camera2; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; -import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.CaptureRequest.Key; -import android.hardware.camera2.impl.ExtensionKey; -import android.hardware.camera2.impl.PublicKey; - -import com.android.internal.camera.flags.Flags; - -/** - * ExtensionCaptureRequest contains definitions for extension-specific CaptureRequest keys that - * can be used to configure a {@link android.hardware.camera2.CaptureRequest} during a - * {@link android.hardware.camera2.CameraExtensionSession}. - * - * Note that ExtensionCaptureRequest is not intended to be used as a replacement - * for CaptureRequest in the extensions. It serves as a supplementary class providing - * extension-specific CaptureRequest keys. Developers should use these keys in conjunction - * with regular CaptureRequest objects during a - * {@link android.hardware.camera2.CameraExtensionSession}. - * - * @see CaptureRequest - * @see CameraExtensionSession - */ -@FlaggedApi(Flags.FLAG_CONCERT_MODE_API) -public final class ExtensionCaptureRequest { - - /** To avoid exposing constructor */ - private ExtensionCaptureRequest() {} - - /** - *

Used to apply an additional digital zoom factor for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. - * This additional zoom factor serves as a buffer to provide more flexibility for the - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } - * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used. - * The effectiveness of the stabilization may be influenced by the amount of padding zoom - * applied. A higher padding zoom factor can stabilize the target region more effectively - * with greater flexibility but may potentially impact image quality. Conversely, a lower - * padding zoom factor may be used to prioritize preserving image quality, albeit with less - * leeway in stabilizing the target region. It is recommended to set the - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.

- *

If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden. - * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the - * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.

- *

Range of valid values:
- * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM - * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR - * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR - * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR; - - /** - *

Used to enable or disable auto zoom for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Turn on auto zoom to let the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature decide at any given point a combination of - * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } - * to keep the target region in view and stabilized. The combination chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested - * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting - * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR - * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM; - - /** - *

Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if - * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit - * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode - * to control image quality.

- *

Range of valid values:
- * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE Range}. Use a value greater than or equal to - * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to - * effectively utilize this key.

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM - * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR - * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR; - - /** - *

Set the stabilization mode for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension

- *

The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked - * video stabilization. Locked mode uses the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * stabilization feature to fixate on the current region, utilizing it as the target area for - * stabilization.

- *

Possible values:

- *
    - *
  • {@link #EFV_STABILIZATION_MODE_OFF OFF}
  • - *
  • {@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}
  • - *
  • {@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}
  • - *
- * - *

Optional - The value for this key may be {@code null} on some devices.

- * @see #EFV_STABILIZATION_MODE_OFF - * @see #EFV_STABILIZATION_MODE_GIMBAL - * @see #EFV_STABILIZATION_MODE_LOCKED - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE; - - /** - *

Used to update the target region for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

A android.util.Pair that represents the desired - * shift of the current locked view (or target region) in - * pixels. Negative values indicate left and upward shifts, while positive values indicate - * right and downward shifts in the active array coordinate system.

- *

Range of valid values:
- * android.util.Pair represents the - * shift. The range for the horizontal shift is - * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)]. - * The range for the vertical shift is - * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see ExtensionCaptureResult#EFV_PADDING_REGION - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT; - - /** - *

Representing the desired clockwise rotation - * of the target region in degrees for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Value representing the desired clockwise rotation of the target - * region in degrees.

- *

Range of valid values:
- * 0 to 360

- *

Optional - The value for this key may be {@code null} on some devices.

- */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT; - - - // - // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE - // - - /** - *

No stabilization.

- * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE - */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF; - - /** - *

Gimbal stabilization mode.

- * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE - */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL; - - /** - *

Locked stabilization mode which uses the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * stabilization to directionally steady the target region.

- * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE - */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED; - -} diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java deleted file mode 100644 index b7ba78cb34b533fee3a1a6a827fe19cab1e39d76..0000000000000000000000000000000000000000 --- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java +++ /dev/null @@ -1,274 +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 android.hardware.camera2; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; -import android.hardware.camera2.CameraExtensionCharacteristics; -import android.hardware.camera2.CaptureResult; -import android.hardware.camera2.CaptureResult.Key; -import android.hardware.camera2.impl.ExtensionKey; -import android.hardware.camera2.impl.PublicKey; - -import com.android.internal.camera.flags.Flags; - -/** - * ExtensionCaptureResult contains definitions for extension-specific CaptureResult keys that - * are available during a {@link android.hardware.camera2.CameraExtensionSession} after a - * {@link android.hardware.camera2.CaptureRequest} is processed. - * - * Note that ExtensionCaptureResult is not intended to be used as a replacement - * for CaptureResult in the extensions. It serves as a supplementary class providing - * extension-specific CaptureResult keys. Developers should use these keys in conjunction - * with regular CaptureResult objects during a - * {@link android.hardware.camera2.CameraExtensionSession}. - * - * @see CaptureResult - * @see CaptureRequest - * @see CameraExtensionSession - */ -@FlaggedApi(Flags.FLAG_CONCERT_MODE_API) -public final class ExtensionCaptureResult { - - /** To avoid exposing constructor */ - private ExtensionCaptureResult() {} - - /** - *

The padding region for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

An array [left, top, right, bottom] of the padding in pixels remaining on all four sides - * before the target region starts to go out of bounds.

- *

The padding region denotes the area surrounding the stabilized target region within which - * the camera can be moved while maintaining the target region in view. As the camera moves, - * the padding region adjusts to represent the proximity of the target region to the - * boundary, which is the point at which the target region will start to go out of bounds.

- *

Range of valid values:
- * The padding is the number of remaining pixels of padding in each direction. - * The pixels reference the active array coordinate system. Negative values indicate the target region - * is out of bounds. The value for this key may be null for when the stabilization mode is - * in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF } - * or {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL } mode.

- *

Optional - The value for this key may be {@code null} on some devices.

- */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION; - - /** - *

The padding region when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

An array [left, top, right, bottom] of the padding in pixels remaining on all four sides - * before the target region starts to go out of bounds.

- *

This may differ from {@link ExtensionCaptureResult#EFV_PADDING_REGION } as the field of view can change - * during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }, altering the boundary region and thus updating the padding between the - * target region and the boundary.

- *

Range of valid values:
- * The padding is the number of remaining pixels of padding in each direction - * when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled. Negative values indicate the target region is out of bounds. - * The value for this key may be null for when the {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is not enabled.

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM - * @see ExtensionCaptureResult#EFV_PADDING_REGION - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION; - - /** - *

List of coordinates representing the target region relative to the - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE } - * for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

A list of android.graphics.PointF that define the coordinates of the target region - * relative to the - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }. - * The array represents the target region coordinates as: top-left, top-right, bottom-left, - * bottom-right.

- *

Range of valid values:
- * The list of target coordinates will define a region within the bounds of the - * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }

- *

Optional - The value for this key may be {@code null} on some devices.

- */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES; - - /** - *

Used to apply an additional digital zoom factor for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. - * This additional zoom factor serves as a buffer to provide more flexibility for the - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } - * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used. - * The effectiveness of the stabilization may be influenced by the amount of padding zoom - * applied. A higher padding zoom factor can stabilize the target region more effectively - * with greater flexibility but may potentially impact image quality. Conversely, a lower - * padding zoom factor may be used to prioritize preserving image quality, albeit with less - * leeway in stabilizing the target region. It is recommended to set the - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.

- *

If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden. - * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the - * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.

- *

Range of valid values:
- * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM - * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR - * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR - * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR; - - /** - *

Set the stabilization mode for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension

- *

The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked - * video stabilization. Locked mode uses the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * stabilization feature to fixate on the current region, utilizing it as the target area for - * stabilization.

- *

Possible values:

- *
    - *
  • {@link #EFV_STABILIZATION_MODE_OFF OFF}
  • - *
  • {@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}
  • - *
  • {@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}
  • - *
- * - *

Optional - The value for this key may be {@code null} on some devices.

- * @see #EFV_STABILIZATION_MODE_OFF - * @see #EFV_STABILIZATION_MODE_GIMBAL - * @see #EFV_STABILIZATION_MODE_LOCKED - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE; - - /** - *

Used to enable or disable auto zoom for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Turn on auto zoom to let the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * feature decide at any given point a combination of - * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } - * to keep the target region in view and stabilized. The combination chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested - * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting - * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see CaptureRequest#CONTROL_ZOOM_RATIO - * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR - * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM; - - /** - *

Representing the desired clockwise rotation - * of the target region in degrees for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

Value representing the desired clockwise rotation of the target - * region in degrees.

- *

Range of valid values:
- * 0 to 360

- *

Optional - The value for this key may be {@code null} on some devices.

- */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT; - - /** - *

Used to update the target region for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

A android.util.Pair that represents the desired - * shift of the current locked view (or target region) in - * pixels. Negative values indicate left and upward shifts, while positive values indicate - * right and downward shifts in the active array coordinate system.

- *

Range of valid values:
- * android.util.Pair represents the - * shift. The range for the horizontal shift is - * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)]. - * The range for the vertical shift is - * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see ExtensionCaptureResult#EFV_PADDING_REGION - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT; - - /** - *

Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if - * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.

- *

If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit - * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the - * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } - * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode - * to control image quality.

- *

Range of valid values:
- * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }. Use a value greater than or equal to - * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to - * effectively utilize this key.

- *

Optional - The value for this key may be {@code null} on some devices.

- * - * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM - * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR - * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE - */ - @PublicKey - @NonNull - @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) - public static final Key EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR; - -} diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java index 8fa09a802aa4fe883efdc6a957962b71005c59a1..df66f590148f6d35eb6c5aa8b0bb650a96b4e72c 100644 --- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java +++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java @@ -53,7 +53,6 @@ import java.util.Map; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract class AdvancedExtender { private HashMap mMetadataVendorIdMap = new HashMap<>(); private final CameraManager mCameraManager; @@ -66,7 +65,6 @@ public abstract class AdvancedExtender { * * @param cameraManager the system camera manager */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public AdvancedExtender(@NonNull CameraManager cameraManager) { mCameraManager = cameraManager; try { @@ -101,7 +99,6 @@ public abstract class AdvancedExtender { * @param cameraId The camera2 id string of the camera. * @return the camera metadata vendor Id associated with the given camera */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public long getMetadataVendorId(@NonNull String cameraId) { long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ? mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE; @@ -123,7 +120,6 @@ public abstract class AdvancedExtender { * CameraCharacteristics. * @return true if the extension is supported, otherwise false */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract boolean isExtensionAvailable(@NonNull String cameraId, @NonNull CharacteristicsMap charsMap); @@ -144,7 +140,6 @@ public abstract class AdvancedExtender { * physical camera ids and their * CameraCharacteristics. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void initialize(@NonNull String cameraId, @NonNull CharacteristicsMap map); /** @@ -159,7 +154,6 @@ public abstract class AdvancedExtender { * be identical to the supported preview output format returned here. * @param cameraId The camera2 id string of the camera. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract Map> getSupportedPreviewOutputResolutions( @NonNull String cameraId); @@ -179,7 +173,6 @@ public abstract class AdvancedExtender { * writes the output to the output surface. * @param cameraId The camera2 id string of the camera. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract Map> getSupportedCaptureOutputResolutions( @NonNull String cameraId); @@ -189,7 +182,6 @@ public abstract class AdvancedExtender { * implements all the interactions required for starting an extension * and cleanup. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract SessionProcessor getSessionProcessor(); @@ -227,7 +219,6 @@ public abstract class AdvancedExtender { * @return The list of supported orthogonal capture keys, or empty * list if no capture settings are not supported. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract List getAvailableCaptureRequestKeys( @NonNull String cameraId); @@ -245,7 +236,6 @@ public abstract class AdvancedExtender { * @return The list of supported capture result keys, or * empty list if capture results are not supported. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract List getAvailableCaptureResultKeys( @NonNull String cameraId); @@ -270,7 +260,6 @@ public abstract class AdvancedExtender { * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}. */ - @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET) @NonNull public abstract List> getAvailableCharacteristicsKeyValues(); @@ -377,7 +366,6 @@ public abstract class AdvancedExtender { return false; } - @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET) @Override public CameraMetadataNative getAvailableCharacteristicsKeyValues(String cameraId) { List> entries = diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java index 01698d54150c320df75efdc00320bd00932850a8..9cc4f16fa627bf1aa47db2a0ff3fd4777ce393c6 100644 --- a/core/java/android/hardware/camera2/extension/CameraExtensionService.java +++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java @@ -42,7 +42,6 @@ interface CameraUsageTracker { * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract class CameraExtensionService extends Service { private static final String TAG = "CameraExtensionService"; private CameraUsageTracker mCameraUsageTracker; @@ -87,10 +86,8 @@ public abstract class CameraExtensionService extends Service { } }; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) protected CameraExtensionService() { } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @Override @NonNull public final IBinder onBind(@Nullable Intent intent) { @@ -186,7 +183,6 @@ public abstract class CameraExtensionService extends Service { * unexpectedly. * @return true if the registration is successful, false otherwise */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract boolean onRegisterClient(@NonNull IBinder token); /** @@ -194,7 +190,6 @@ public abstract class CameraExtensionService extends Service { * * @param token Binder token */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void onUnregisterClient(@NonNull IBinder token); /** @@ -204,7 +199,6 @@ public abstract class CameraExtensionService extends Service { * extension type * @return Valid advanced extender of the requested type */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract AdvancedExtender onInitializeAdvancedExtension(@Extension int extensionType); } diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java index 32139b8e314b838aaf0a66bf8508e8ada057a0fa..e8b20e37f40c4e4bc3d387b0e3fc79bb79ff73f9 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java +++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java @@ -42,11 +42,9 @@ import com.android.internal.camera.flags.Flags; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public final class CameraOutputSurface { private final OutputSurface mOutputSurface; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) CameraOutputSurface(@NonNull OutputSurface surface) { mOutputSurface = surface; } @@ -59,7 +57,6 @@ public final class CameraOutputSurface { * @param size Requested size of the camera * output */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public CameraOutputSurface(@NonNull Surface surface, @NonNull Size size) { mOutputSurface = new OutputSurface(); @@ -75,7 +72,6 @@ public final class CameraOutputSurface { /** * Return the current output {@link Surface} */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public Surface getSurface() { return mOutputSurface.surface; @@ -84,7 +80,6 @@ public final class CameraOutputSurface { /** * Return the current requested output size */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public android.util.Size getSize() { if (mOutputSurface.size != null) { @@ -96,7 +91,6 @@ public final class CameraOutputSurface { /** * Return the current surface output {@link android.graphics.ImageFormat} */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public @ImageFormat.Format int getImageFormat() { return mOutputSurface.imageFormat; } diff --git a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java index 495abc8100aea274954bcfcd8d59be3597ecc82c..e578f6ef90647da55d78201cf9e54ba0930f8273 100644 --- a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java +++ b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java @@ -36,7 +36,6 @@ import java.util.Set; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public class CharacteristicsMap { private final HashMap mCharMap; @@ -46,7 +45,6 @@ public class CharacteristicsMap { * @param charsMap Maps camera ids to respective * {@link CameraCharacteristics} */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) CharacteristicsMap(@NonNull Map charsMap) { mCharMap = new HashMap<>(); for (Map.Entry entry : charsMap.entrySet()) { @@ -59,7 +57,6 @@ public class CharacteristicsMap { * * @return Set of the camera ids stored in the map */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public Set getCameraIds() { return mCharMap.keySet(); @@ -74,7 +71,6 @@ public class CharacteristicsMap { * @return Valid {@link CameraCharacteristics} instance of null * in case the camera id is not part of the map */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @Nullable public CameraCharacteristics get(@NonNull String cameraId) { return mCharMap.get(cameraId); diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java index 32de1ce8f0d69d038c26cb3f4b97ff60db0d2968..43dfaa58554f984c0b8422ca8f3c2db838277193 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java @@ -43,7 +43,6 @@ import java.util.List; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public class ExtensionConfiguration { private final int mSessionType; private final int mSessionTemplateId; @@ -65,7 +64,6 @@ public class ExtensionConfiguration { * @param sessionParams An optional set of camera capture * session parameter values */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public ExtensionConfiguration(@CameraDevice.SessionOperatingMode int sessionType, @CameraDevice.RequestTemplate int sessionTemplateId, @NonNull List outputs, @@ -87,7 +85,6 @@ public class ExtensionConfiguration { mColorSpace = colorSpace; } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) CameraSessionConfig getCameraSessionConfig() { if (mOutputs.isEmpty()) { return null; diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java index 8a47430e7eb429b6121e7ac1fe40986ac30d8ba1..ff0d8d7b53a5a327cadb66d2bf73e2eab049b4d9 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java @@ -35,7 +35,6 @@ import java.util.List; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public class ExtensionOutputConfiguration { private final List mSurfaces; private final String mPhysicalCameraId; @@ -57,7 +56,6 @@ public class ExtensionOutputConfiguration { * @param surfaceGroupId In case of surface group, this field must * contain the surface group id */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public ExtensionOutputConfiguration(@NonNull List outputs, int outputConfigId, @Nullable String physicalCameraId, int surfaceGroupId) { mSurfaces = outputs; diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java index 0ad27c212d671df2bab0fe7366db428c7534a8e5..936d57baadd3ea7186e02361264d9684d43e705c 100644 --- a/core/java/android/hardware/camera2/extension/RequestProcessor.java +++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java @@ -45,19 +45,16 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public final class RequestProcessor { private final static String TAG = "RequestProcessor"; private final IRequestProcessorImpl mRequestProcessor; private final long mVendorId; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) RequestProcessor (@NonNull IRequestProcessorImpl requestProcessor, long vendorId) { mRequestProcessor = requestProcessor; mVendorId = vendorId; } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public interface RequestCallback { /** * This method is called when the camera device has started @@ -76,7 +73,6 @@ public final class RequestProcessor { * @param frameNumber the frame number for this capture * */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureStarted(@NonNull Request request, long frameNumber, long timestamp); /** @@ -117,7 +113,6 @@ public final class RequestProcessor { * which includes a subset of the {@link * TotalCaptureResult} fields. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureProgressed(@NonNull Request request, @NonNull CaptureResult partialResult); /** @@ -138,7 +133,6 @@ public final class RequestProcessor { * parameters and the state of the camera * system during capture. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureCompleted(@NonNull Request request, @Nullable TotalCaptureResult totalCaptureResult); @@ -163,7 +157,6 @@ public final class RequestProcessor { * @param failure The output failure from the capture, including the * failure reason and the frame number. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureFailed(@NonNull Request request, @NonNull CaptureFailure failure); /** @@ -182,7 +175,6 @@ public final class RequestProcessor { * @param outputStreamId The output stream id that the buffer will not * be produced for */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureBufferLost(@NonNull Request request, long frameNumber, int outputStreamId); /** @@ -203,7 +195,6 @@ public final class RequestProcessor { * or {@link CaptureFailure#getFrameNumber}) in * the capture sequence. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceCompleted(int sequenceId, long frameNumber); /** @@ -221,17 +212,14 @@ public final class RequestProcessor { * @param sequenceId A sequence ID returned by the RequestProcessor * capture family of methods */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceAborted(int sequenceId); } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public final static class Request { private final List mOutputIds; private final List> mParameters; private final @CameraDevice.RequestTemplate int mTemplateId; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public Request(@NonNull List outputConfigIds, @NonNull List> parameters, @CameraDevice.RequestTemplate int templateId) { @@ -244,7 +232,6 @@ public final class RequestProcessor { * Gets the target ids of {@link ExtensionOutputConfiguration} which identifies * corresponding Surface to be the targeted for the request. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull List getOutputConfigIds() { return mOutputIds; @@ -253,7 +240,6 @@ public final class RequestProcessor { /** * Gets all the parameters. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public List> getParameters() { return mParameters; @@ -265,7 +251,6 @@ public final class RequestProcessor { * * @see CameraDevice.RequestTemplate */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) Integer getTemplateId() { return mTemplateId; } @@ -322,7 +307,6 @@ public final class RequestProcessor { * @param callback Request callback implementation * @return the id of the capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public int submit(@NonNull Request request, @NonNull Executor executor, @NonNull RequestCallback callback) throws CameraAccessException { ArrayList requests = new ArrayList<>(1); @@ -355,7 +339,6 @@ public final class RequestProcessor { * @param callback Request callback implementation * @return the id of the capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public int submitBurst(@NonNull List requests, @NonNull Executor executor, @NonNull RequestCallback callback) throws CameraAccessException { List parcelableRequests = @@ -386,7 +369,6 @@ public final class RequestProcessor { * @param callback Request callback implementation * @return the id of the capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public int setRepeating(@NonNull Request request, @NonNull Executor executor, @NonNull RequestCallback callback) throws CameraAccessException { ArrayList requests = new ArrayList<>(1); @@ -413,7 +395,6 @@ public final class RequestProcessor { /** * Abort all ongoing capture requests. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public void abortCaptures() { try { mRequestProcessor.abortCaptures(); @@ -425,7 +406,6 @@ public final class RequestProcessor { /** * Stop the current repeating request. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public void stopRepeating() { try { mRequestProcessor.stopRepeating(); diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java index 0ec5c0ae977f65fffdf12310d666dd462036bf68..cf9f30d89762a6044dfe4e724c5d5173fd7e0a43 100644 --- a/core/java/android/hardware/camera2/extension/SessionProcessor.java +++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java @@ -76,7 +76,6 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract class SessionProcessor { private static final String TAG = "SessionProcessor"; private CameraUsageTracker mCameraUsageTracker; @@ -84,7 +83,6 @@ public abstract class SessionProcessor { /** * Initialize a session process instance */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public SessionProcessor() {} void setCameraUsageTracker(CameraUsageTracker tracker) { @@ -97,7 +95,6 @@ public abstract class SessionProcessor { * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public interface CaptureCallback { /** * This method is called when the camera device has started @@ -116,7 +113,6 @@ public abstract class SessionProcessor { * first frame in a multi-frame capture, * in nanoseconds. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureStarted(int captureSequenceId, long timestamp); /** @@ -126,7 +122,6 @@ public abstract class SessionProcessor { * * @param captureSequenceId id of the current capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureProcessStarted(int captureSequenceId); /** @@ -139,7 +134,6 @@ public abstract class SessionProcessor { * @param captureSequenceId id of the current capture sequence * @param failure The capture failure reason */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureFailed(int captureSequenceId, @CaptureFailure.FailureReason int failure); /** @@ -154,7 +148,6 @@ public abstract class SessionProcessor { * * @param captureSequenceId id of the current capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceCompleted(int captureSequenceId); /** @@ -162,7 +155,6 @@ public abstract class SessionProcessor { * * @param captureSequenceId id of the current capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceAborted(int captureSequenceId); /** @@ -192,7 +184,6 @@ public abstract class SessionProcessor { * settings and results are always supported and * applied by the corresponding framework. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureCompleted(long shutterTimestamp, int requestId, @NonNull Map results); } @@ -231,7 +222,6 @@ public abstract class SessionProcessor { * ensure this list will always produce a valid camera capture * session. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract ExtensionConfiguration initSession(@NonNull IBinder token, @NonNull String cameraId, @NonNull CharacteristicsMap map, @@ -247,7 +237,6 @@ public abstract class SessionProcessor { * @param token Binder token that can be used to unlink any previously * linked death notifier callbacks */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void deInitSession(@NonNull IBinder token); /** @@ -268,7 +257,6 @@ public abstract class SessionProcessor { * * */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor, @NonNull String statsKey); @@ -279,7 +267,6 @@ public abstract class SessionProcessor { * will no longer accept any requests after onCaptureSessionEnd() * returns. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void onCaptureSessionEnd(); /** @@ -293,7 +280,6 @@ public abstract class SessionProcessor { * @param callback a callback to report the status. * @return the id of the capture sequence. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract int startRepeating(@NonNull Executor executor, @NonNull CaptureCallback callback); @@ -305,7 +291,6 @@ public abstract class SessionProcessor { * forward calling {@link RequestProcessor#setRepeating} will simply * do nothing. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void stopRepeating(); /** @@ -326,7 +311,6 @@ public abstract class SessionProcessor { * @param callback a callback to report the status. * @return the id of the capture sequence. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract int startMultiFrameCapture(@NonNull Executor executor, @NonNull CaptureCallback callback); @@ -338,7 +322,6 @@ public abstract class SessionProcessor { * the value. *@param captureRequest Request that includes all client parameter */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void setParameters(@NonNull CaptureRequest captureRequest); /** @@ -357,7 +340,6 @@ public abstract class SessionProcessor { * @return the id of the capture sequence. * */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract int startTrigger(@NonNull CaptureRequest captureRequest, @NonNull Executor executor, @NonNull CaptureCallback callback); @@ -372,11 +354,9 @@ public abstract class SessionProcessor { Map charsMap, OutputSurface previewSurface, OutputSurface imageCaptureSurface, OutputSurface postviewSurface) throws RemoteException { - if (Flags.surfaceLeakFix()) { - mPreviewSurface = previewSurface; - mPostviewSurface = postviewSurface; - mImageCaptureSurface = imageCaptureSurface; - } + mPreviewSurface = previewSurface; + mPostviewSurface = postviewSurface; + mImageCaptureSurface = imageCaptureSurface; ExtensionConfiguration config = SessionProcessor.this.initSession(token, cameraId, new CharacteristicsMap(charsMap), new CameraOutputSurface(previewSurface), @@ -399,16 +379,14 @@ public abstract class SessionProcessor { @Override public void deInitSession(IBinder token) throws RemoteException { SessionProcessor.this.deInitSession(token); - if (Flags.surfaceLeakFix()) { - if ((mPreviewSurface != null) && (mPreviewSurface.surface != null)) { - mPreviewSurface.surface.release(); - } - if ((mImageCaptureSurface != null) && (mImageCaptureSurface.surface != null)) { - mImageCaptureSurface.surface.release(); - } - if ((mPostviewSurface != null) && (mPostviewSurface.surface != null)) { - mPostviewSurface.surface.release(); - } + if ((mPreviewSurface != null) && (mPreviewSurface.surface != null)) { + mPreviewSurface.surface.release(); + } + if ((mImageCaptureSurface != null) && (mImageCaptureSurface.surface != null)) { + mImageCaptureSurface.surface.release(); + } + if ((mPostviewSurface != null) && (mPostviewSurface.surface != null)) { + mPostviewSurface.surface.release(); } } diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 323712d817afaf6929c64f5d14f91177a4e2e403..fc03b517e6d42b9283659456e6a60c17c0c2b524 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -873,17 +873,15 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @Override public void onCaptureProcessFailed(int captureSequenceId, int captureFailureReason) { - if (Flags.concertMode()) { - final long ident = Binder.clearCallingIdentity(); - try { - mClientExecutor.execute( - () -> mClientCallbacks.onCaptureFailed( - CameraAdvancedExtensionSessionImpl.this, mClientRequest, - captureFailureReason - )); - } finally { - Binder.restoreCallingIdentity(ident); - } + final long ident = Binder.clearCallingIdentity(); + try { + mClientExecutor.execute( + () -> mClientCallbacks.onCaptureFailed( + CameraAdvancedExtensionSessionImpl.this, mClientRequest, + captureFailureReason + )); + } finally { + Binder.restoreCallingIdentity(ident); } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java index 4ddf602c447bfd7ed42f3cf6d3edc82fec7533bf..b5fb0502ec2ca5f78335d5985293645da2fe3e9a 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java @@ -205,10 +205,6 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup { */ @SuppressWarnings("AndroidFrameworkCompatChange") public static boolean isCameraDeviceSetupSupported(CameraCharacteristics chars) { - if (!Flags.featureCombinationQuery()) { - return false; - } - Integer queryVersion = chars.get( CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION); return queryVersion != null && queryVersion > Build.VERSION_CODES.UPSIDE_DOWN_CAKE; diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 0cd1c8ca89fe92822cfd7d8d25e644b08385f8b3..ef7f3f8ab58bd73ee40adfcbc7924b887c7f34dd 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1797,57 +1797,49 @@ public class CameraMetadataNative implements Parcelable { return false; } - if (Flags.concertMode()) { - long[] tsArray = new long[samples.length]; - float[] intrinsicsArray = new float[samples.length * 5]; - for (int i = 0; i < samples.length; i++) { - tsArray[i] = samples[i].getTimestampNanos(); - System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5); + long[] tsArray = new long[samples.length]; + float[] intrinsicsArray = new float[samples.length * 5]; + for (int i = 0; i < samples.length; i++) { + tsArray[i] = samples[i].getTimestampNanos(); + System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5); - } - setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray); - setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray); - - return true; - } else { - return false; } + setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray); + setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray); + + return true; } private LensIntrinsicsSample[] getLensIntrinsicSamples() { - if (Flags.concertMode()) { - long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS); - float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES); + long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS); + float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES); - if (timestamps == null) { - if (intrinsics != null) { - throw new AssertionError("timestamps is null but intrinsics is not"); - } - - return null; + if (timestamps == null) { + if (intrinsics != null) { + throw new AssertionError("timestamps is null but intrinsics is not"); } - if (intrinsics == null) { - throw new AssertionError("timestamps is not null but intrinsics is"); - } else if ((intrinsics.length % 5) != 0) { - throw new AssertionError("intrinsics are not multiple of 5"); - } + return null; + } - if ((intrinsics.length / 5) != timestamps.length) { - throw new AssertionError(String.format( - "timestamps has %d entries but intrinsics has %d", timestamps.length, - intrinsics.length / 5)); - } + if (intrinsics == null) { + throw new AssertionError("timestamps is not null but intrinsics is"); + } else if ((intrinsics.length % 5) != 0) { + throw new AssertionError("intrinsics are not multiple of 5"); + } - LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length]; - for (int i = 0; i < timestamps.length; i++) { - float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5); - samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic); - } - return samples; - } else { - return null; + if ((intrinsics.length / 5) != timestamps.length) { + throw new AssertionError(String.format( + "timestamps has %d entries but intrinsics has %d", timestamps.length, + intrinsics.length / 5)); } + + LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length]; + for (int i = 0; i < timestamps.length; i++) { + float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5); + samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic); + } + return samples; } private Capability[] getExtendedSceneModeCapabilities() { diff --git a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java index 9a4ec5c94b5bf247d7900bd21eed2589d0775f88..9157ef1093562a275d44c98f5dbcd6855511c97f 100644 --- a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java +++ b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java @@ -31,7 +31,6 @@ import java.util.Arrays; * Immutable class to store an * {@link CaptureResult#STATISTICS_LENS_INTRINSICS_SAMPLES lens intrinsics intra-frame sample}. */ -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public final class LensIntrinsicsSample { /** * Create a new {@link LensIntrinsicsSample}. @@ -46,7 +45,6 @@ public final class LensIntrinsicsSample { * * @throws IllegalArgumentException if lensIntrinsics length is different from 5 */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public LensIntrinsicsSample(final long timestampNs, @NonNull final float[] lensIntrinsics) { mTimestampNs = timestampNs; Preconditions.checkArgument(lensIntrinsics.length == 5); @@ -61,7 +59,6 @@ public final class LensIntrinsicsSample { * * @return a long value (guaranteed to be finite) */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public long getTimestampNanos() { return mTimestampNs; } @@ -72,7 +69,6 @@ public final class LensIntrinsicsSample { * @return a floating point value (guaranteed to be finite) * @see CaptureResult#LENS_INTRINSIC_CALIBRATION */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public float[] getLensIntrinsics() { return mLensIntrinsics; diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 9bd4860e7cccfcb39887892ada87b68c45736c9d..50c6b5b8b99552c2631972a6ae228ef22a42a097 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -165,12 +165,10 @@ public final class SessionConfiguration implements Parcelable { source.readTypedList(outConfigs, OutputConfiguration.CREATOR); // Ignore the values for hasSessionParameters and settings because we cannot reconstruct // the CaptureRequest object. - if (Flags.featureCombinationQuery()) { - boolean hasSessionParameters = source.readBoolean(); - if (hasSessionParameters) { - CameraMetadataNative settings = new CameraMetadataNative(); - settings.readFromParcel(source); - } + boolean hasSessionParameters = source.readBoolean(); + if (hasSessionParameters) { + CameraMetadataNative settings = new CameraMetadataNative(); + settings.readFromParcel(source); } if ((inputWidth > 0) && (inputHeight > 0) && (inputFormat != -1)) { @@ -212,14 +210,12 @@ public final class SessionConfiguration implements Parcelable { dest.writeBoolean(/*isMultiResolution*/ false); } dest.writeTypedList(mOutputConfigurations); - if (Flags.featureCombinationQuery()) { - if (mSessionParameters != null) { - dest.writeBoolean(/*hasSessionParameters*/true); - CameraMetadataNative metadata = mSessionParameters.getNativeCopy(); - metadata.writeToParcel(dest, /*flags*/0); - } else { - dest.writeBoolean(/*hasSessionParameters*/false); - } + if (mSessionParameters != null) { + dest.writeBoolean(/*hasSessionParameters*/true); + CameraMetadataNative metadata = mSessionParameters.getNativeCopy(); + metadata.writeToParcel(dest, /*flags*/0); + } else { + dest.writeBoolean(/*hasSessionParameters*/false); } } diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 9612a53be96e8d69bb02dc38cb4f864e131530c1..7185719abdd5bf05ac46b537ce9e4a22e3425b7e 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -1445,7 +1445,7 @@ public final class DisplayManagerGlobal { * system's display configuration. */ public static final String CACHE_KEY_DISPLAY_INFO_PROPERTY = - "cache_key.display_info"; + PropertyInvalidatedCache.createSystemCacheKey("display_info"); /** * Invalidates the contents of the display info cache for all applications. Can only diff --git a/core/java/android/hardware/fingerprint/FingerprintCallback.java b/core/java/android/hardware/fingerprint/FingerprintCallback.java index e4fbe6e097096bdee24b262585dff40e50437cad..24e9f9ddef77a9e5558d92bd7a0b65626553cd04 100644 --- a/core/java/android/hardware/fingerprint/FingerprintCallback.java +++ b/core/java/android/hardware/fingerprint/FingerprintCallback.java @@ -189,7 +189,7 @@ public class FingerprintCallback { mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD); } final String msg = getAcquiredString(context, acquireInfo, vendorCode); - if (msg == null) { + if (msg == null || msg.isEmpty()) { return; } // emulate HAL 2.1 behavior and send real acquiredInfo diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 8592dedbb2bb7166df672c102e86d23477c9478d..177ee6f1540a3e98fd7a125847c3b2321a3442ed 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -20,15 +20,15 @@ import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FL import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS; import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG; import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG; -import static com.android.hardware.input.Flags.FLAG_KEYBOARD_REPEAT_KEYS; import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag; import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag; import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag; import static com.android.hardware.input.Flags.keyboardA11yMouseKeys; -import static com.android.hardware.input.Flags.keyboardRepeatKeys; import static com.android.hardware.input.Flags.touchpadTapDragging; import static com.android.hardware.input.Flags.touchpadVisualizer; import static com.android.input.flags.Flags.enableInputFilterRustImpl; +import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; +import static com.android.input.flags.Flags.keyboardRepeatKeys; import android.Manifest; import android.annotation.FlaggedApi; @@ -800,7 +800,7 @@ public class InputSettings { * *

* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular - * key on the physical keyboard is held down. This accessibility feature allows the user + * key on the physical keyboard is held down. This feature allows the user * to configure the timeout before the key repeats begin as well as the delay * between successive key repeats. *

@@ -812,7 +812,31 @@ public class InputSettings { } /** - * Get Accessibility repeat keys timeout duration in milliseconds. + * Whether "Repeat keys" feature is enabled. + * Repeat keys is ON by default. + * The repeat keys timeout and delay would have the default values in the default ON case. + * + *

+ * 'Repeat keys’ is a feature which allows users to generate key repeats when a particular + * key on the physical keyboard is held down. This feature allows the user + * to configure the timeout before the key repeats begin as well as the delay + * between successive key repeats. + *

+ * + * @hide + */ + @TestApi + @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS) + public static boolean isRepeatKeysEnabled(@NonNull Context context) { + if (!isRepeatKeysFeatureFlagEnabled()) { + return true; + } + return Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.KEY_REPEAT_ENABLED, 1, UserHandle.USER_CURRENT) != 0; + } + + /** + * Get repeat keys timeout duration in milliseconds. * The default key repeat timeout is {@link ViewConfiguration#DEFAULT_KEY_REPEAT_TIMEOUT_MS}. * * @param context The application context @@ -823,7 +847,7 @@ public class InputSettings { * *

* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular - * key on the physical keyboard is held down. This accessibility feature allows the user + * key on the physical keyboard is held down. This feature allows the user * to configure the timeout before the key repeats begin as well as the delay * between successive key repeats. *

@@ -832,14 +856,17 @@ public class InputSettings { */ @TestApi @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS) - public static int getAccessibilityRepeatKeysTimeout(@NonNull Context context) { + public static int getRepeatKeysTimeout(@NonNull Context context) { + if (!isRepeatKeysFeatureFlagEnabled()) { + return ViewConfiguration.getKeyRepeatTimeout(); + } return Settings.Secure.getIntForUser(context.getContentResolver(), Settings.Secure.KEY_REPEAT_TIMEOUT_MS, ViewConfiguration.getKeyRepeatTimeout(), UserHandle.USER_CURRENT); } /** - * Get Accessibility repeat keys delay rate in milliseconds. + * Get repeat keys delay rate in milliseconds. * The default key repeat delay is {@link ViewConfiguration#DEFAULT_KEY_REPEAT_DELAY_MS}. * * @param context The application context @@ -850,7 +877,7 @@ public class InputSettings { * *

* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular - * key on the physical keyboard is held down. This accessibility feature allows the user + * key on the physical keyboard is held down. This feature allows the user * to configure the timeout before the key repeats begin as well as the delay * between successive key repeats. *

@@ -859,14 +886,41 @@ public class InputSettings { */ @TestApi @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS) - public static int getAccessibilityRepeatKeysDelay(@NonNull Context context) { + public static int getRepeatKeysDelay(@NonNull Context context) { + if (!isRepeatKeysFeatureFlagEnabled()) { + return ViewConfiguration.getKeyRepeatDelay(); + } return Settings.Secure.getIntForUser(context.getContentResolver(), Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(), UserHandle.USER_CURRENT); } /** - * Set Accessibility repeat keys timeout duration in milliseconds. + * Set repeat keys feature enabled/disabled. + * + *

+ * 'Repeat keys’ is a feature which allows users to generate key repeats when a particular + * key on the physical keyboard is held down. This feature allows the user + * to configure the timeout before the key repeats begin as well as the delay + * between successive key repeats. + *

+ * + * @hide + */ + @TestApi + @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS) + @RequiresPermission(Manifest.permission.WRITE_SETTINGS) + public static void setRepeatKeysEnabled(@NonNull Context context, + boolean enabled) { + if (!isRepeatKeysFeatureFlagEnabled()) { + return; + } + Settings.Secure.putIntForUser(context.getContentResolver(), + Settings.Secure.KEY_REPEAT_ENABLED, enabled ? 1 : 0, UserHandle.USER_CURRENT); + } + + /** + * Set repeat keys timeout duration in milliseconds. * * @param timeoutTimeMillis time duration for which a key should be pressed after which the * pressed key will be repeated. The timeout must be between @@ -875,7 +929,7 @@ public class InputSettings { * *

* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular - * key on the physical keyboard is held down. This accessibility feature allows the user + * key on the physical keyboard is held down. This feature allows the user * to configure the timeout before the key repeats begin as well as the delay * between successive key repeats. *

@@ -885,8 +939,12 @@ public class InputSettings { @TestApi @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS) @RequiresPermission(Manifest.permission.WRITE_SETTINGS) - public static void setAccessibilityRepeatKeysTimeout(@NonNull Context context, + public static void setRepeatKeysTimeout(@NonNull Context context, int timeoutTimeMillis) { + if (!isRepeatKeysFeatureFlagEnabled() + && !isRepeatKeysEnabled(context)) { + return; + } if (timeoutTimeMillis < MIN_KEY_REPEAT_TIMEOUT_MILLIS || timeoutTimeMillis > MAX_KEY_REPEAT_TIMEOUT_MILLIS) { throw new IllegalArgumentException( @@ -900,7 +958,7 @@ public class InputSettings { } /** - * Set Accessibility repeat key delay duration in milliseconds. + * Set repeat key delay duration in milliseconds. * * @param delayTimeMillis Time duration between successive key repeats when a key is * pressed down. The delay duration must be between @@ -908,7 +966,7 @@ public class InputSettings { * {@link #MAX_KEY_REPEAT_DELAY_MILLIS} *

* ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular - * key on the physical keyboard is held down. This accessibility feature allows the user + * key on the physical keyboard is held down. This feature allows the user * to configure the timeout before the key repeats begin as well as the delay * between successive key repeats. *

@@ -918,8 +976,12 @@ public class InputSettings { @TestApi @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS) @RequiresPermission(Manifest.permission.WRITE_SETTINGS) - public static void setAccessibilityRepeatKeysDelay(@NonNull Context context, + public static void setRepeatKeysDelay(@NonNull Context context, int delayTimeMillis) { + if (!isRepeatKeysFeatureFlagEnabled() + && !isRepeatKeysEnabled(context)) { + return; + } if (delayTimeMillis < MIN_KEY_REPEAT_DELAY_MILLIS || delayTimeMillis > MAX_KEY_REPEAT_DELAY_MILLIS) { throw new IllegalArgumentException( diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index c7ebc63a3d159db198e0904967dc17eff0a6dbff..bdbec5596adeeece5caf93123c3f943dd4b32c26 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -22,8 +22,6 @@ import android.annotation.Nullable; import android.view.Display; import android.view.KeyCharacterMap; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AnnotationValidations; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; @@ -65,31 +63,34 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS = 23; public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK = 24; public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE = 25; - public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION = 26; - public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS = 27; - public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 28; - public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 29; - public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 30; - public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 31; - public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 32; - public static final int KEY_GESTURE_TYPE_SLEEP = 33; - public static final int KEY_GESTURE_TYPE_WAKEUP = 34; - public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 35; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 36; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 37; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 38; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 39; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 40; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 41; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 42; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 43; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 44; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 45; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 46; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 47; - public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 48; - public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 49; - public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 50; + public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT = 26; + public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT = 27; + public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT = 28; + public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT = 29; + public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 30; + public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 31; + public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 32; + public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 33; + public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 34; + public static final int KEY_GESTURE_TYPE_SLEEP = 35; + public static final int KEY_GESTURE_TYPE_WAKEUP = 36; + public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 37; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 38; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 39; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 40; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 41; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 42; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 43; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 44; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 45; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 46; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 47; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 48; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 49; + public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 50; + public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 51; + public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 52; + public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 53; public static final int FLAG_CANCELLED = 1; @@ -130,8 +131,10 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, KEY_GESTURE_TYPE_SYSTEM_MUTE, - KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION, - KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS, + KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT, + KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT, + KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT, + KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT, KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, KEY_GESTURE_TYPE_LOCK_SCREEN, KEY_GESTURE_TYPE_OPEN_NOTES, @@ -155,6 +158,7 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME, KEY_GESTURE_TYPE_DESKTOP_MODE, KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION, + KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER }) @Retention(RetentionPolicy.SOURCE) public @interface KeyGestureType { @@ -164,6 +168,14 @@ public final class KeyGestureEvent { this.mKeyGestureEvent = keyGestureEvent; } + /** + * Tests whether this keyboard shortcut event has the given modifiers (i.e. all of the given + * modifiers were pressed when this shortcut was triggered). + */ + public boolean hasModifiers(int modifiers) { + return (getModifierState() & modifiers) == modifiers; + } + /** * Key gesture event builder used to create a KeyGestureEvent for tests in Java. * @@ -331,6 +343,7 @@ public final class KeyGestureEvent { case KEY_GESTURE_TYPE_HOME: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME; case KEY_GESTURE_TYPE_RECENT_APPS: + case KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS; case KEY_GESTURE_TYPE_BACK: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK; @@ -378,9 +391,11 @@ public final class KeyGestureEvent { return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK; case KEY_GESTURE_TYPE_SYSTEM_MUTE: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE; - case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION: + case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: + case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION; - case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS: + case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: + case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS; case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT; @@ -487,10 +502,14 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK"; case KEY_GESTURE_TYPE_SYSTEM_MUTE: return "KEY_GESTURE_TYPE_SYSTEM_MUTE"; - case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION: - return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION"; - case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS: - return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS"; + case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: + return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT"; + case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: + return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT"; + case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: + return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT"; + case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: + return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT"; case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT"; case KEY_GESTURE_TYPE_LOCK_SCREEN: @@ -537,6 +556,8 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_DESKTOP_MODE"; case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: return "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION"; + case KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: + return "KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER"; default: return Integer.toHexString(value); } diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 4478592ae8a57622d09ac59c4b5beaccadc78c80..75683f607c35b6692d26037d95da5f99fb6346d4 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -111,6 +111,20 @@ flag { } } +flag { + namespace: "input_native" + name: "use_key_gesture_event_handler" + description: "Use KeyGestureEvent handler APIs to control system shortcuts and key gestures" + bug: "358569822" +} + +flag { + namespace: "input_native" + name: "use_key_gesture_event_handler_multi_press_gestures" + description: "Use KeyGestureEvent handler APIs to control multi key press gestures" + bug: "358569822" +} + flag { name: "keyboard_repeat_keys" namespace: "input_native" @@ -124,3 +138,10 @@ flag { description: "Controls whether external mouse vertical scrolling can be reversed" bug: "352598211" } + +flag { + name: "mouse_swap_primary_button" + namespace: "input" + description: "Controls whether the connected mice's primary buttons, left and right, can be swapped." + bug: "352598211" +} diff --git a/core/java/android/net/TEST_MAPPING b/core/java/android/net/TEST_MAPPING index 3df56162bd2c7760d0b2d64a78ce7646ea6a2e0d..ea509bb24f0aa2dc7001815d5cb081fef9014846 100644 --- a/core/java/android/net/TEST_MAPPING +++ b/core/java/android/net/TEST_MAPPING @@ -19,21 +19,7 @@ ], "presubmit": [ { - "name": "FrameworksCoreTests", - "options": [ - { - "include-filter": "android.net" - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksCoreTests_android_net" } ] } diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index 6f11d3ae661c49c46c07beccb112976451cd7a1f..af93c964a8baaf89aed3542d3f304ce18b1b1e45 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -35,7 +35,6 @@ import android.os.PersistableBundle; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.server.vcn.util.PersistableBundleUtils; @@ -434,7 +433,14 @@ public final class VcnGatewayConnectionConfig { @NonNull public int[] getExposedCapabilities() { // Sorted set guarantees ordering - return ArrayUtils.convertToIntArray(new ArrayList<>(mExposedCapabilities)); + final int[] caps = new int[mExposedCapabilities.size()]; + + int i = 0; + for (int c : mExposedCapabilities) { + caps[i++] = c; + } + + return caps; } /** diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java index a97563724e505a7412db87d8c5735e4e134ae623..e1d1b3c65c996af07cec40daa7a40117c4bb777a 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java @@ -24,7 +24,6 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.internal.util.ArrayUtils; import java.util.Arrays; import java.util.Objects; @@ -114,8 +113,13 @@ public final class VcnUnderlyingNetworkSpecifier extends NetworkSpecifier implem @Override public boolean canBeSatisfiedBy(NetworkSpecifier other) { if (other instanceof TelephonyNetworkSpecifier) { - return ArrayUtils.contains( - mSubIds, ((TelephonyNetworkSpecifier) other).getSubscriptionId()); + final int targetSubId = ((TelephonyNetworkSpecifier) other).getSubscriptionId(); + for (int subId : mSubIds) { + if (targetSubId == subId) { + return true; + } + } + return false; } // TODO(b/180140053): Allow matching against WifiNetworkAgentSpecifier diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index f3efd89d4bc35598d2d91393062ba4148b23e6e3..8b267bf28c7e58a22ce9218b37fee9771756857f 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -141,6 +141,7 @@ public class BatteryManager { /** * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: * integer containing the charge counter present in the battery. + * It shows the available battery power in µAh * {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -165,6 +166,76 @@ public class BatteryManager { */ public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS"; + /** + * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: + * Int value representing the battery's capacity level. These constants are key indicators of + * battery status and system capabilities, guiding power management decisions for both the + * system and apps: + * {@link #BATTERY_CAPACITY_LEVEL_UNSUPPORTED}: Feature not supported on this device. + * {@link #BATTERY_CAPACITY_LEVEL_UNKNOWN}: Battery status is unavailable or uninitialized. + * {@link #BATTERY_CAPACITY_LEVEL_CRITICAL}: Battery is critically low and the Android + * framework has been notified to schedule a shutdown by this value + * {@link #BATTERY_CAPACITY_LEVEL_LOW}: Android framework must limit background jobs to + * avoid impacting charging speed + * {@link #BATTERY_CAPACITY_LEVEL_NORMAL}: Battery level and charging rates are normal, + * battery temperature is within normal range and adapter power is enough to charge the + * battery at an acceptable rate. Android framework can run light background tasks without + * affecting charging performance severely. + * {@link #BATTERY_CAPACITY_LEVEL_HIGH}: Battery level is high, battery temperature is + * within normal range and adapter power is enough to charge the battery at an acceptable + * rate while running background loads. Android framework can run background tasks without + * affecting charging or battery performance. + * {@link #BATTERY_CAPACITY_LEVEL_FULL}: The battery is full, battery temperature is + * within normal range and adapter power is enough to sustain running background loads. + * Android framework can run background tasks without affecting the battery level or + * battery performance. + */ + + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL"; + + /** + * Battery capacity level is unsupported. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1; + + /** + * Battery capacity level is unknown. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0; + + /** + * Battery capacity level is critical. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1; + + /** + * Battery capacity level is low. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_LOW = 2; + + /** + * Battery capacity level is normal. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3; + + /** + * Battery capacity level is high. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4; + + /** + * Battery capacity level is full. @see EXTRA_CAPACITY_LEVEL + */ + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_CAPACITY_LEVEL_FULL = 5; + /** * Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}: * Contains list of Bundles representing battery events diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index c22f46cdc2b5bdc1e53d70c2b9f25036161abcbe..80546cd6770f805168d9af0c06f5e7e1f792a7ce 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -650,13 +650,13 @@ public final class BinderProxy implements IBinder { * weakly referenced by JNI so the strong references here are needed to keep the callbacks * around until the proxy is GC'ed. */ - private List mFrozenStateChangeCallbacks = + private List mFrozenStateChangeCallbacks = Collections.synchronizedList(new ArrayList<>()); /** - * See {@link IBinder#addFrozenStateChangeCallback(IFrozenStateChangeCallback)} + * See {@link IBinder#addFrozenStateChangeCallback(FrozenStateChangeCallback)} */ - public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback) + public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback) throws RemoteException { addFrozenStateChangeCallbackNative(callback); mFrozenStateChangeCallbacks.add(callback); @@ -665,16 +665,16 @@ public final class BinderProxy implements IBinder { /** * See {@link IBinder#removeFrozenStateChangeCallback} */ - public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) { + public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) { mFrozenStateChangeCallbacks.remove(callback); return removeFrozenStateChangeCallbackNative(callback); } - private native void addFrozenStateChangeCallbackNative(IFrozenStateChangeCallback callback) + private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback) throws RemoteException; private native boolean removeFrozenStateChangeCallbackNative( - IFrozenStateChangeCallback callback); + FrozenStateChangeCallback callback); /** * Perform a dump on the remote object @@ -762,10 +762,9 @@ public final class BinderProxy implements IBinder { } private static void invokeFrozenStateChangeCallback( - IFrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) { + FrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) { try { - callback.onFrozenStateChanged(binderProxy, - IFrozenStateChangeCallback.State.values()[stateIndex]); + callback.onFrozenStateChanged(binderProxy, stateIndex); } catch (RuntimeException exc) { Log.w("BinderNative", "Uncaught exception from frozen state change callback", exc); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 2c7b9c02330eebdbec2f9f7512e7eb0690a42d8b..89a5e5d6637d76b7f4898298c04aedaf0f9841aa 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -415,7 +415,7 @@ public class Environment { * Returns the base directory for per-user system directory, device encrypted. * {@hide} */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SystemApi @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY) public static @NonNull File getDataSystemDeDirectory() { return buildPath(getDataDirectory(), "system_de"); diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 80f39bfbdc21786723723182fbb9edaa42b2a7c9..d0828c38466462ffdf7d99693cf60889b2202695 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -16,8 +16,10 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.util.Log; import android.util.Printer; @@ -819,16 +821,25 @@ public class Handler { } /** + * WARNING: This API is dangerous because if the implementation + * of equals() is broken, it would delete unrelated events. For example, + * if object.equals() always returns true, it'd remove all messages. + * + * For this reason, never expose this API to non-platform code. i.e. + * this shouldn't be exposed to SystemApi.PRIVILEGED_APPS. + * * Remove any pending posts of messages with code 'what' and whose obj is * 'object' that are in the message queue. If object is null, * all messages will be removed. - *

- * Similar to {@link #removeMessages(int, Object)} but uses object equality + * + *

Similar to {@link #removeMessages(int, Object)} but uses object equality * ({@link Object#equals(Object)}) instead of reference equality (==) in * determining whether object is the message's obj'. * *@hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API) public final void removeEqualMessages(int what, @Nullable Object object) { mQueue.removeEqualMessages(this, what, disallowNullArgumentIfShared(object)); } @@ -843,12 +854,25 @@ public class Handler { } /** + * WARNING: This API is dangerous because if the implementation + * of equals() is broken, it would delete unrelated events. For example, + * if object.equals() always returns true, it'd remove all messages. + * + * For this reason, never expose this API to non-platform code. i.e. + * this shouldn't be exposed to SystemApi.PRIVILEGED_APPS. + * * Remove any pending posts of callbacks and sent messages whose * obj is token. If token is null, * all callbacks and messages will be removed. * + *

Similar to {@link #removeCallbacksAndMessages(Object)} but uses object + * equality ({@link Object#equals(Object)}) instead of reference equality (==) in + * determining whether object is the message's obj'. + * *@hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API) public final void removeCallbacksAndEqualMessages(@Nullable Object token) { mQueue.removeCallbacksAndEqualMessages(this, disallowNullArgumentIfShared(token)); } @@ -864,6 +888,8 @@ public class Handler { * Return whether there are any messages or callbacks currently scheduled on this handler. * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API) public final boolean hasMessagesOrCallbacks() { return mQueue.hasMessages(this); } diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 8185e8e542e1ca20df84e46cc4e0e54525a73a08..a997f4c86704d35a7278e1184e297940da2225f7 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -16,11 +16,15 @@ package android.os; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Base interface for a remotable object, the core part of a lightweight @@ -377,9 +381,24 @@ public interface IBinder { */ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags); - /** @hide */ - interface IFrozenStateChangeCallback { - enum State {FROZEN, UNFROZEN}; + /** + * A callback interface for receiving frozen state change events. + */ + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + interface FrozenStateChangeCallback { + /** + * @hide + */ + @IntDef(prefix = {"STATE_"}, value = { + STATE_FROZEN, + STATE_UNFROZEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface State { + } + + int STATE_FROZEN = 0; + int STATE_UNFROZEN = 1; /** * Interface for receiving a callback when the process hosting an IBinder @@ -387,13 +406,13 @@ public interface IBinder { * @param who The IBinder whose hosting process has changed state. * @param state The latest state. */ - void onFrozenStateChanged(@NonNull IBinder who, State state); + void onFrozenStateChanged(@NonNull IBinder who, @State int state); } /** - * {@link addFrozenStateChangeCallback} provides a callback mechanism to notify about process - * frozen/unfrozen events. Upon registration and any subsequent state changes, the callback is - * invoked with the latest process frozen state. + * This method provides a callback mechanism to notify about process frozen/unfrozen events. + * Upon registration and any subsequent state changes, the callback is invoked with the latest + * process frozen state. * *

If the listener process (the one using this API) is itself frozen, state change events * might be combined into a single one with the latest frozen state. This single event would @@ -410,19 +429,19 @@ public interface IBinder { * *

@throws {@link UnsupportedOperationException} if the kernel binder driver does not support * this feature. - * @hide */ - default void addFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) throws RemoteException { throw new UnsupportedOperationException(); } /** - * Unregister a {@link IFrozenStateChangeCallback}. The callback will no longer be invoked when + * Unregister a {@link FrozenStateChangeCallback}. The callback will no longer be invoked when * the hosting process changes its frozen state. - * @hide */ - default boolean removeFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) { + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + default boolean removeFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) { throw new UnsupportedOperationException(); } } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 00ba3bf27c65df8462e7ed9fea6f1ba97c3ccec0..18f9b2b9d74f8526c11d25fb913111ef36ebebfe 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -139,7 +139,7 @@ interface IUserManager { boolean isUserForeground(int userId); boolean isUserVisible(int userId); int[] getVisibleUsers(); - int getMainDisplayIdAssignedToUser(); + int getMainDisplayIdAssignedToUser(int userId); boolean isForegroundUserAdmin(); boolean isUserNameSet(int userId); boolean hasRestrictedProfiles(int userId); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 026013c34e309f979bb41cd59fb01e4de3c86b4d..e4c12b6ff834926273710b512b0e738ab4afa46d 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1144,9 +1144,10 @@ public final class PowerManager { } private static final String CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY = - "cache_key.is_power_save_mode"; + PropertyInvalidatedCache.createSystemCacheKey("is_power_save_mode"); - private static final String CACHE_KEY_IS_INTERACTIVE_PROPERTY = "cache_key.is_interactive"; + private static final String CACHE_KEY_IS_INTERACTIVE_PROPERTY = + PropertyInvalidatedCache.createSystemCacheKey("is_interactive"); private static final int MAX_CACHE_ENTRIES = 1; diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 728db27055a8b771aaa1872ad23f8b2cda84f35f..effe5554aff45daf0c694fee3cf2f07fb4af586d 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -38,33 +38,15 @@ }, { "file_patterns": ["Bugreport[^/]*\\.java"], - "name": "BugreportManagerTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "BugreportManagerTestCases_android_server_os" }, { "file_patterns": ["Bugreport[^/]*\\.java"], - "name": "CtsBugreportTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsBugreportTestCases_android_server_os" }, { "file_patterns": ["Bugreport[^/]*\\.java"], - "name": "ShellTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ShellTests_android_server_os" }, { "file_patterns": [ @@ -99,12 +81,7 @@ "Parcel\\.java", "[^/]*Bundle[^/]*\\.java" ], - "name": "FrameworksMockingCoreTests", - "options": [ - { "include-filter": "android.os.BundleRecyclingTest"}, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, - { "exclude-annotation": "org.junit.Ignore" } - ] + "name": "FrameworksMockingCoreTests_os_bundlerecyclingtest" }, { "file_patterns": [ @@ -116,12 +93,7 @@ }, { "file_patterns": ["SharedMemory[^/]*\\.java"], - "name": "CtsOsTestCases", - "options": [ - { - "include-filter": "android.os.cts.SharedMemoryTest" - } - ] + "name": "CtsOsTestCases_cts_sharedmemorytest" }, { "file_patterns": ["Environment[^/]*\\.java"], diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index a4a7a983c44cc8768caa1abe0e0c2314cb021b5b..461f1e00c415c0916f4b2cf14eab227ff05d02aa 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3706,9 +3706,13 @@ public class UserManager { * @hide */ @TestApi + @UserHandleAware( + requiresAnyOfPermissionsIfNotCaller = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) public int getMainDisplayIdAssignedToUser() { try { - return mService.getMainDisplayIdAssignedToUser(); + return mService.getMainDisplayIdAssignedToUser(mUserId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -3763,7 +3767,8 @@ public class UserManager { } private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY = - "cache_key.is_user_unlocked"; + PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked"); private final PropertyInvalidatedCache mIsUserUnlockedCache = new PropertyInvalidatedCache( @@ -6694,7 +6699,9 @@ public class UserManager { } /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */ - private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props"; + private static final String CACHE_KEY_STATIC_USER_PROPERTIES = + PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "static_user_props"); private final PropertyInvalidatedCache mProfileTypeCache = new PropertyInvalidatedCache(32, CACHE_KEY_STATIC_USER_PROPERTIES) { @@ -6721,7 +6728,9 @@ public class UserManager { } /* Cache key for UserProperties object. */ - private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties"; + private static final String CACHE_KEY_USER_PROPERTIES = + PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "user_properties"); // TODO: It would be better to somehow have this as static, so that it can work cross-context. private final PropertyInvalidatedCache mUserPropertiesCache = diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 39bd15c968d7a150a759de35bb6f11c80bcbf31b..f670601bd0d48668d3a7bb73626ce9bb955d3742 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -152,6 +152,14 @@ flag { bug: "317250784" } +flag { + name: "binder_frozen_state_change_callback" + is_exported: true + namespace: "system_performance" + description: "Guards the frozen state change callback API." + bug: "361157077" +} + flag { name: "message_queue_tail_tracking" namespace: "system_performance" @@ -208,3 +216,11 @@ flag { bug: "346294653" is_exported: true } + +flag { + name: "mainline_vcn_platform_api" + namespace: "vcn" + description: "Expose platform APIs to mainline VCN" + is_exported: true + bug: "366598445" +} diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 7e51cb0201966049f06a353700cf70e713618b02..e98397d104d6ac816e1699cc5869c6635bcddbab 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1796,7 +1796,8 @@ public final class PermissionManager { } /** @hide */ - public static final String CACHE_KEY_PACKAGE_INFO = "cache_key.package_info"; + public static final String CACHE_KEY_PACKAGE_INFO = + PropertyInvalidatedCache.createSystemCacheKey("package_info"); /** @hide */ private static final PropertyInvalidatedCache sPermissionCache = diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING index b317b80d56776c11ed5691cd9fd4a6eb78b67835..3e5a131fbdc19af0de0f7e5ba3f07708b68ee49f 100644 --- a/core/java/android/permission/TEST_MAPPING +++ b/core/java/android/permission/TEST_MAPPING @@ -6,26 +6,10 @@ ], "postsubmit": [ { - "name": "CtsVirtualDevicesAudioTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest" - } - ] + "name": "CtsVirtualDevicesAudioTestCases_audio_virtualaudiopermissiontest" }, { - "name": "CtsVirtualDevicesAppLaunchTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest" - } - ] + "name": "CtsVirtualDevicesAppLaunchTestCases_applaunch_virtualdevicepermissiontest" } ] } diff --git a/core/java/android/print/TEST_MAPPING b/core/java/android/print/TEST_MAPPING index 4fa882265e53a3f3d1184885e56adadc13ed0684..1033b1a86edb5693b8fc08e4447ec3a0c207f4a0 100644 --- a/core/java/android/print/TEST_MAPPING +++ b/core/java/android/print/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsPrintTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - } - ] + "name": "CtsPrintTestCases_Presubmit" } ] } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index f6eb4b52984ded963739afd3938cd2454510ea76..a622810496788d594be15fb7a9b173d322cb736c 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -3018,170 +3018,180 @@ public final class ContactsContract { com.android.internal.R.string.config_rawContactsLocalAccountType)); } + + /** - * Represents the state of the default account, and the actual {@link Account} if it's - * a cloud account. - * If the default account is set to {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or - * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}, new raw contacts requested for insertion - * without a - * specified {@link Account} will be saved in the default account. - * The default account can have one of the following four states: - *

    - *
  • {@link #DEFAULT_ACCOUNT_STATE_INVALID}: An invalid state that should not - * occur on the device.
  • - *
  • {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}: The default account has not - * been set by the user.
  • - *
  • {@link #DEFAULT_ACCOUNT_STATE_LOCAL}: The default account is set to - * the local device storage. New raw contacts requested for insertion without a - * specified - * {@link Account} will be saved in a null or custom local account.
  • - *
  • {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a - * cloud-synced account. New raw contacts requested for insertion without a specified - * {@link Account} will be saved in {@link mCloudAccount}.
  • - *
+ * Class containing utility methods around the default account. + * New raw contacts requested to be inserted without a specified {@link Account} will be + * saved in the default account. */ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED) - public static final class DefaultAccountAndState { - // The state of the default account. - /** A state that is invalid. */ - public static final int DEFAULT_ACCOUNT_STATE_INVALID = 0; - - /** A state indicating that default account is not set. */ - public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; - - /** A state indicating that default account is set to local device storage. */ - public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; + public static final class DefaultAccount { /** - * A state indicating that the default account is set as an account that is synced - * to the cloud. - */ - public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; + * Represents the state of the default account, and the actual {@link Account} if it's + * a cloud account. + * If the default account is set to {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or + * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}, new raw contacts requested for insertion + * without a + * specified {@link Account} will be saved in the default account. + * The default account can have one of the following four states: + *
    + *
  • {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}: The default account has not + * been set by the user.
  • + *
  • {@link #DEFAULT_ACCOUNT_STATE_LOCAL}: The default account is set to + * the local device storage. New raw contacts requested for insertion without a + * specified + * {@link Account} will be saved in a null or custom local account.
  • + *
  • {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a + * cloud-synced account. New raw contacts requested for insertion without a specified + * {@link Account} will be saved in the default cloud account.
  • + *
+ */ + @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED) + public static final class DefaultAccountAndState { + /** A state indicating that default account is not set. */ + public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; + + /** A state indicating that default account is set to local device storage. */ + public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; - /** - * The state of the default account. One of - * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}, - * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or - * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. - */ - @DefaultAccountState - private final int mState; + /** + * A state indicating that the default account is set as an account that is synced + * to the cloud. + */ + public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; - /** - * The account of the default account, when {@link mState} is { - * - * @link #STATE_SET_TO_CLOUD}, or null otherwise. - */ - private final Account mCloudAccount; + /** + * The state of the default account. One of + * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}, + * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or + * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. + */ + @DefaultAccountState + private final int mState; - /** - * Constructs a new `DefaultAccountAndState` instance with the specified state and - * cloud - * account. - * - * @param state The state of the default account. - * @param cloudAccount The cloud account associated with the default account, - * or null if the state is not - * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. - */ - public DefaultAccountAndState(@DefaultAccountState int state, - @Nullable Account cloudAccount) { - if (state == DEFAULT_ACCOUNT_STATE_INVALID) { - throw new IllegalArgumentException("Invalid default account state."); + /** + * The account of the default account, when {@link mState} is { + * + * @link #STATE_SET_TO_CLOUD}, or null otherwise. + */ + private final Account mCloudAccount; + + /** + * Constructs a new `DefaultAccountAndState` instance with the specified state and + * cloud + * account. + * + * @param state The state of the default account. + * @param cloudAccount The cloud account associated with the default account, + * or null if the state is not + * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. + */ + public DefaultAccountAndState(@DefaultAccountState int state, + @Nullable Account cloudAccount) { + if (!isValidDefaultAccountState(state)) { + throw new IllegalArgumentException("Invalid default account state."); + } + if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) { + throw new IllegalArgumentException( + "Default account can be set to cloud if and only if the cloud " + + "account is provided."); + } + this.mState = state; + this.mCloudAccount = + (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null; } - if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) { - throw new IllegalArgumentException( - "Default account can be set to cloud if and only if the cloud " - + "account is provided."); + + /** + * Creates a `DefaultAccountAndState` instance representing a default account + * that is set to the cloud and associated with the specified cloud account. + * + * @param cloudAccount The non-null cloud account associated with the default + * contacts + * account. + * @return A new `DefaultAccountAndState` instance with state + * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. + */ + public static @NonNull DefaultAccountAndState ofCloud( + @NonNull Account cloudAccount) { + return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount); } - this.mState = state; - this.mCloudAccount = - (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null; - } - /** - * Creates a `DefaultAccountAndState` instance representing a default account - * that is set to the cloud and associated with the specified cloud account. - * - * @param cloudAccount The non-null cloud account associated with the default - * contacts - * account. - * @return A new `DefaultAccountAndState` instance with state - * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. - */ - public static @NonNull DefaultAccountAndState ofCloud( - @NonNull Account cloudAccount) { - return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount); - } + /** + * Creates a `DefaultAccountAndState` instance representing a default account + * that is set to the local device storage. + * + * @return A new `DefaultAccountAndState` instance with state + * {@link #DEFAULT_ACCOUNT_STATE_LOCAL}. + */ + public static @NonNull DefaultAccountAndState ofLocal() { + return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_LOCAL, null); + } - /** - * Creates a `DefaultAccountAndState` instance representing a default account - * that is set to the local device storage. - * - * @return A new `DefaultAccountAndState` instance with state - * {@link #DEFAULT_ACCOUNT_STATE_LOCAL}. - */ - public static @NonNull DefaultAccountAndState ofLocal() { - return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_LOCAL, null); - } + /** + * Creates a `DefaultAccountAndState` instance representing a default account + * that is not set. + * + * @return A new `DefaultAccountAndState` instance with state + * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}. + */ + public static @NonNull DefaultAccountAndState ofNotSet() { + return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null); + } - /** - * Creates a `DefaultAccountAndState` instance representing a default account - * that is not set. - * - * @return A new `DefaultAccountAndState` instance with state - * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}. - */ - public static @NonNull DefaultAccountAndState ofNotSet() { - return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null); - } + /** + * @return the state of the default account. + */ + @DefaultAccountState + public int getState() { + return mState; + } - /** - * @return the state of the default account. - */ - @DefaultAccountState - public int getState() { - return mState; - } + /** + * @return the cloud account associated with the default account, or null if the + * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. + */ + public @Nullable Account getCloudAccount() { + return mCloudAccount; + } - /** - * @return the cloud account associated with the default account, or null if the - * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. - */ - public @Nullable Account getCloudAccount() { - return mCloudAccount; - } + @Override + public int hashCode() { + return Objects.hash(mState, mCloudAccount); + } - @Override - public int hashCode() { - return Objects.hash(mState, mCloudAccount); - } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DefaultAccountAndState that)) { + return false; + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof DefaultAccountAndState that)) { - return false; + return mState == that.mState && Objects.equals(mCloudAccount, + that.mCloudAccount); } - return mState == that.mState && Objects.equals(mCloudAccount, - that.mCloudAccount); - } + private static boolean isValidDefaultAccountState(int state) { + return state == DEFAULT_ACCOUNT_STATE_NOT_SET + || state == DEFAULT_ACCOUNT_STATE_LOCAL + || state == DEFAULT_ACCOUNT_STATE_CLOUD; + } - /** - * Annotation for all default account states. - * - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = {"DEFAULT_ACCOUNT_STATE_"}, - value = {DEFAULT_ACCOUNT_STATE_INVALID, - DEFAULT_ACCOUNT_STATE_NOT_SET, - DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD}) - public @interface DefaultAccountState { + /** + * Annotation for all default account states. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"DEFAULT_ACCOUNT_STATE_"}, + value = {DEFAULT_ACCOUNT_STATE_NOT_SET, + DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD}) + public @interface DefaultAccountState { + } } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e32625e1f7a88c0047e3327534e72927fd031923..b8a8be159d122f93356e3e485e63d2f09e3e2f6f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -111,6 +111,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -3022,6 +3023,9 @@ public final class Settings { /** @hide - Private call() method to query the 'configuration' table */ public static final String CALL_METHOD_LIST_CONFIG = "LIST_config"; + /** @hide - Private call() method to query the 'configuration' tables' namespaces */ + public static final String CALL_METHOD_LIST_NAMESPACES_CONFIG = "LIST_namespaces_config"; + /** @hide - Private call() method to disable / re-enable syncs to the 'configuration' table */ public static final String CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG = "SET_SYNC_DISABLED_MODE_config"; @@ -9153,16 +9157,28 @@ public final class Settings { @Readable public static final String MULTI_PRESS_TIMEOUT = "multi_press_timeout"; + /** + * Whether to enable key repeats for Physical Keyboard. + * + * If set to false, continuous key presses on + * physical keyboard will not cause the pressed key to repeated. + * @hide + */ + @Readable + public static final String KEY_REPEAT_ENABLED = "key_repeat_enabled"; + /** * The duration before a key repeat begins in milliseconds. * @hide */ + @Readable public static final String KEY_REPEAT_TIMEOUT_MS = "key_repeat_timeout"; /** * The duration between successive key repeats in milliseconds. * @hide */ + @Readable public static final String KEY_REPEAT_DELAY_MS = "key_repeat_delay"; /** @@ -20458,6 +20474,10 @@ public final class Settings { * * The keys take the form {@code namespace/flag}, and the values are the flag values. * + * Note: this API is _not_ performant, and may make a large number of + * Binder calls. It is intended for use in testing and debugging, and + * should not be used in performance-sensitive code. + * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @@ -20469,13 +20489,33 @@ public final class Settings { Bundle arg = new Bundle(); arg.putInt(Settings.CALL_METHOD_USER_KEY, resolver.getUserId()); IContentProvider cp = sProviderHolder.getProvider(resolver); - Bundle b = cp.call(resolver.getAttributionSource(), - sProviderHolder.mUri.getAuthority(), CALL_METHOD_LIST_CONFIG, null, arg); - if (b != null) { - Map flagsToValues = - (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, - java.util.HashMap.class); - allFlags.putAll(flagsToValues); + + if (Flags.reduceBinderTransactionSizeForGetAllProperties()) { + Bundle b = cp.call(resolver.getAttributionSource(), + sProviderHolder.mUri.getAuthority(), + CALL_METHOD_LIST_NAMESPACES_CONFIG, null, arg); + if (b != null) { + HashSet namespaces = + (HashSet) b.getSerializable(Settings.NameValueTable.VALUE, + java.util.HashSet.class); + for (String namespace : namespaces) { + Map keyValues = + getStrings(namespace, new ArrayList()); + for (String key : keyValues.keySet()) { + allFlags.put(namespace + "/" + key, keyValues.get(key)); + } + } + } + } else { + Bundle b = cp.call(resolver.getAttributionSource(), + sProviderHolder.mUri.getAuthority(), + CALL_METHOD_LIST_CONFIG, null, arg); + if (b != null) { + Map flagsToValues = + (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, + java.util.HashMap.class); + allFlags.putAll(flagsToValues); + } } } catch (RemoteException e) { Log.w(TAG, "Can't query configuration table for " + CONTENT_URI, e); diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING index 2eb285dda0a229b04735818cdfff27ee6ae1095a..a6fe3016e8baab0873a257ead4d3e17e06bc36eb 100644 --- a/core/java/android/provider/TEST_MAPPING +++ b/core/java/android/provider/TEST_MAPPING @@ -24,12 +24,7 @@ "name": "SettingsProviderTest" }, { - "name": "CtsPackageManagerHostTestCases", - "options": [ - { - "include-filter": "android.appsecurity.cts.ReadableSettingsFieldsTest" - } - ] + "name": "CtsPackageManagerHostTestCases_cts_readablesettingsfieldstest" } ], "postsubmit": [ diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig index 5c0f8737ca274b18cb61df5b8e2f22c74bf3e177..4c636735b5ce2335544d7f7d4192e1a0f1b9d279 100644 --- a/core/java/android/provider/flags.aconfig +++ b/core/java/android/provider/flags.aconfig @@ -52,3 +52,14 @@ flag { description: "Enable the new ContactsContract Default Account APIs." bug: "359957527" } + +flag { + name: "reduce_binder_transaction_size_for_get_all_properties" + namespace: "core_experiments_team_internal" + description: "Reduce Binder transaction size in getAllProperties calls" + bug: "362652574" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/security/TEST_MAPPING b/core/java/android/security/TEST_MAPPING index 5a679b1a2bf7f8201ca44217f3986d20402cec11..e1c7f3c2f3d3618b988d8b0b34a299a181de54d1 100644 --- a/core/java/android/security/TEST_MAPPING +++ b/core/java/android/security/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsSecurityTestCases", - "options": [ - { - "include-filter": "android.security.cts.FileIntegrityManagerTest" - } - ], + "name": "CtsSecurityTestCases_cts_fileintegritymanagertest", "file_patterns": [ "FileIntegrityManager\\.java", "IFileIntegrityService\\.aidl" diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING index 468c4518602eea4c1dd61b8dc7a08967007592fc..dc7129cde5e5b10820806a8b3024018ec3ec7a0b 100644 --- a/core/java/android/service/notification/TEST_MAPPING +++ b/core/java/android/service/notification/TEST_MAPPING @@ -1,32 +1,10 @@ { "presubmit": [ { - "name": "CtsNotificationTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsNotificationTestCases_notification" }, { - "name": "FrameworksUiServicesTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "FrameworksUiServicesTests_notification" } ], "postsubmit": [ diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 752f174504f257ce9aef20077ccfec7c8ecf48be..d45b24ed69beaa0995d5083b04f5efc19c1ec1d5 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -313,6 +313,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant"; private static final String RULE_ATT_DISABLED_ORIGIN = "disabledOrigin"; private static final String RULE_ATT_LEGACY_SUPPRESSED_EFFECTS = "legacySuppressedEffects"; + private static final String RULE_ATT_CONDITION_OVERRIDE = "conditionOverride"; private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = @@ -1045,6 +1046,8 @@ public class ZenModeConfig implements Parcelable { DEFAULT_SUPPRESSED_VISUAL_EFFECTS); } else if (MANUAL_TAG.equals(tag)) { rt.manualRule = readRuleXml(parser); + // manualRule.enabled can never be false, but it was broken in some builds. + rt.manualRule.enabled = true; // Manual rule may be present prior to modes_ui if it were on, but in that // case it would not have a set policy, so make note of the need to set // it up later. @@ -1144,7 +1147,7 @@ public class ZenModeConfig implements Parcelable { if (manualRule != null) { out.startTag(null, MANUAL_TAG); - writeRuleXml(manualRule, out); + writeRuleXml(manualRule, out, forBackup); out.endTag(null, MANUAL_TAG); } final int N = automaticRules.size(); @@ -1153,7 +1156,7 @@ public class ZenModeConfig implements Parcelable { final ZenRule automaticRule = automaticRules.valueAt(i); out.startTag(null, AUTOMATIC_TAG); out.attribute(null, RULE_ATT_ID, id); - writeRuleXml(automaticRule, out); + writeRuleXml(automaticRule, out, forBackup); out.endTag(null, AUTOMATIC_TAG); } if (Flags.modesApi() && !forBackup) { @@ -1161,7 +1164,7 @@ public class ZenModeConfig implements Parcelable { final ZenRule deletedRule = deletedRules.valueAt(i); out.startTag(null, AUTOMATIC_DELETED_TAG); out.attribute(null, RULE_ATT_ID, deletedRule.id); - writeRuleXml(deletedRule, out); + writeRuleXml(deletedRule, out, forBackup); out.endTag(null, AUTOMATIC_DELETED_TAG); } } @@ -1220,12 +1223,15 @@ public class ZenModeConfig implements Parcelable { ORIGIN_UNKNOWN); rt.legacySuppressedEffects = safeInt(parser, RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, 0); + rt.conditionOverride = safeInt(parser, RULE_ATT_CONDITION_OVERRIDE, + ZenRule.OVERRIDE_NONE); } } return rt; } - public static void writeRuleXml(ZenRule rule, TypedXmlSerializer out) throws IOException { + public static void writeRuleXml(ZenRule rule, TypedXmlSerializer out, boolean forBackup) + throws IOException { out.attributeBoolean(null, RULE_ATT_ENABLED, rule.enabled); if (rule.name != null) { out.attribute(null, RULE_ATT_NAME, rule.name); @@ -1279,6 +1285,9 @@ public class ZenModeConfig implements Parcelable { out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin); out.attributeInt(null, RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, rule.legacySuppressedEffects); + if (rule.conditionOverride == ZenRule.OVERRIDE_ACTIVATE && !forBackup) { + out.attributeInt(null, RULE_ATT_CONDITION_OVERRIDE, rule.conditionOverride); + } } } } @@ -2622,9 +2631,12 @@ public class ZenModeConfig implements Parcelable { int legacySuppressedEffects; /** * Signals a user's action to (temporarily or permanently) activate or deactivate this - * rule, overruling the condition set by the owner. This value is not stored to disk, as - * it shouldn't survive reboots or be involved in B&R. It might be reset by certain - * owner-provided state transitions as well. + * rule, overruling the condition set by the owner. + * + *

An {@link #OVERRIDE_ACTIVATE} is stored to disk, since we want it to survive reboots + * (but it's not included in B&R), while an {@link #OVERRIDE_DEACTIVATE} is not (meaning + * that snoozed rules may reactivate on reboot). It might be reset by certain owner-provided + * state transitions as well. */ @FlaggedApi(Flags.FLAG_MODES_UI) @ConditionOverride diff --git a/core/java/android/service/quicksettings/TEST_MAPPING b/core/java/android/service/quicksettings/TEST_MAPPING index 2d45c5b252cccd805ee8cee40280363f66978cc7..986dc5fd2ba92be8559ce8c81e7f1847ba7f203f 100644 --- a/core/java/android/service/quicksettings/TEST_MAPPING +++ b/core/java/android/service/quicksettings/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit": [ { - "name": "CtsTileServiceTestCases", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTileServiceTestCases" } ] } \ No newline at end of file diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING index bf46ff2ffe062551359477c9ec2267800d53143a..2071717e5f601a2d0597c28f51bfacec723a2173 100644 --- a/core/java/android/service/timezone/TEST_MAPPING +++ b/core/java/android/service/timezone/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksTimeCoreTests", - "options": [ - { - "include-filter": "android.service." - } - ] + "name": "FrameworksTimeCoreTests_android_service" }, { "name": "CtsLocationTimeZoneManagerHostTest" diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index ad457ce6e18d7d537733404d1daa1606b481537f..384add5cf92958fd8d1aa3617d1c576fbbebd846 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -70,6 +70,7 @@ import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -370,6 +371,7 @@ public abstract class WallpaperService extends Service { private float mDefaultDimAmount = 0.05f; SurfaceControl mBbqSurfaceControl; BLASTBufferQueue mBlastBufferQueue; + IBinder mBbqApplyToken = new Binder(); private SurfaceControl mScreenshotSurfaceControl; private Point mScreenshotSize = new Point(); @@ -2390,6 +2392,7 @@ public abstract class WallpaperService extends Service { if (mBlastBufferQueue == null) { mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl, width, height, format); + mBlastBufferQueue.setApplyToken(mBbqApplyToken); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); diff --git a/core/java/android/speech/TEST_MAPPING b/core/java/android/speech/TEST_MAPPING index 7b125c2b0851e311189c31b4e0ab0d24f626deac..cb490f5b62d4d958dce055d415db275b4569c89f 100644 --- a/core/java/android/speech/TEST_MAPPING +++ b/core/java/android/speech/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit": [ { - "name": "CtsVoiceRecognitionTestCases", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVoiceRecognitionTestCases" } ] } diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index c2ad508c2b4424c4af2985e2e110a710bbadc4ae..ca887646b3aa2ffcb90c899db47f6955f88d6232 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -16,21 +16,14 @@ package android.text; -import com.android.text.flags.Flags; - /** * An aconfig feature flags that can be accessible from application process without * ContentProvider IPCs. * * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}. * + * TODO(nona): Remove this class. * @hide */ public class ClientFlags { - /** - * @see Flags#fixMisalignedContextMenu() - */ - public static boolean fixMisalignedContextMenu() { - return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU); - } } diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING index c9bd2cacb138457710c1dbe29d2f7e5aab9d4fd1..9f8a72cb5975ac56f7936d13041fd907c25f0c4a 100644 --- a/core/java/android/text/TEST_MAPPING +++ b/core/java/android/text/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit": [ { - "name": "CtsTextTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsTextTestCases_text" } ] } diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 076721f629ed3b4f41e1670bb13635cb0fb56305..f69a333ff81f5100c2c69f6bdbc3025278ccd6dc 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -19,11 +19,10 @@ package android.text; import android.annotation.NonNull; import android.app.AppGlobals; -import com.android.text.flags.Flags; - /** * Flags in the "text" namespace. * + * TODO(nona): Remove this class. * @hide */ public final class TextFlags { @@ -55,7 +54,6 @@ public final class TextFlags { * List of text flags to be transferred to the application process. */ public static final String[] TEXT_ACONFIGS_FLAGS = { - Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU, }; /** @@ -64,7 +62,6 @@ public final class TextFlags { * The order must be the same to the TEXT_ACONFIG_FLAGS. */ public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { - Flags.fixMisalignedContextMenu(), }; /** diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 3c61f4f5a33c2172a992ae77544e37473f0a6348..c83285a5c889b1cafdcdfaa0bce02e54895d87f1 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -83,27 +83,6 @@ flag { bug: "297962571" } -flag { - name: "fix_font_update_failure" - namespace: "text" - description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure." - is_fixed_read_only: true - bug: "331717791" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "fix_misaligned_context_menu" - namespace: "text" - description: "Fix the context menu misalignment and incosistent icon size." - bug: "332542108" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "missing_getter_apis" namespace: "text" @@ -153,26 +132,6 @@ flag { } } -flag { - name: "portuguese_hyphenator" - namespace: "text" - description: "Portuguese taiored hyphenator" - bug: "344656282" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "dont_break_email_in_nobreak_tag" - namespace: "text" - description: "Prevent line break inside email." - bug: "350691716" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "handwriting_gesture_with_transformation" namespace: "text" diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index f8c97eb5fa72ccb7cfd053f1189518a425345b40..53935e8109133e57e716da0c6fac122cb2f0dd43 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1341,7 +1341,7 @@ public final class Display { public HdrCapabilities getHdrCapabilities() { synchronized (mLock) { updateDisplayInfoLocked(); - if (mDisplayInfo.hdrCapabilities == null) { + if (mDisplayInfo.hdrCapabilities == null || mDisplayInfo.isForceSdr) { return null; } int[] supportedHdrTypes; @@ -1363,6 +1363,7 @@ public final class Display { supportedHdrTypes[index++] = enabledType; } } + return new HdrCapabilities(supportedHdrTypes, mDisplayInfo.hdrCapabilities.mMaxLuminance, mDisplayInfo.hdrCapabilities.mMaxAverageLuminance, @@ -2087,6 +2088,7 @@ public final class Display { /** * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static String stateToString(int state) { switch (state) { case STATE_UNKNOWN: @@ -2109,6 +2111,7 @@ public final class Display { } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static String stateReasonToString(@StateReason int reason) { switch (reason) { case STATE_REASON_UNKNOWN: diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 157cec8a4d0f6558a0e7a629ac8c40b037ceee3b..cac3e3c25098eba0ca56f3096e510256ab13ebce 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -230,6 +230,9 @@ public final class DisplayInfo implements Parcelable { /** The formats disabled by user **/ public int[] userDisabledHdrTypes = {}; + /** When true, all HDR capabilities are disabled **/ + public boolean isForceSdr; + /** * Indicates whether the display can be switched into a mode with minimal post * processing. @@ -440,6 +443,7 @@ public final class DisplayInfo implements Parcelable { && colorMode == other.colorMode && Arrays.equals(supportedColorModes, other.supportedColorModes) && Objects.equals(hdrCapabilities, other.hdrCapabilities) + && isForceSdr == other.isForceSdr && Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes) && minimalPostProcessingSupported == other.minimalPostProcessingSupported && logicalDensityDpi == other.logicalDensityDpi @@ -502,6 +506,7 @@ public final class DisplayInfo implements Parcelable { supportedColorModes = Arrays.copyOf( other.supportedColorModes, other.supportedColorModes.length); hdrCapabilities = other.hdrCapabilities; + isForceSdr = other.isForceSdr; userDisabledHdrTypes = other.userDisabledHdrTypes; minimalPostProcessingSupported = other.minimalPostProcessingSupported; logicalDensityDpi = other.logicalDensityDpi; @@ -567,6 +572,7 @@ public final class DisplayInfo implements Parcelable { supportedColorModes[i] = source.readInt(); } hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class); + isForceSdr = source.readBoolean(); minimalPostProcessingSupported = source.readBoolean(); logicalDensityDpi = source.readInt(); physicalXDpi = source.readFloat(); @@ -636,6 +642,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(supportedColorModes[i]); } dest.writeParcelable(hdrCapabilities, flags); + dest.writeBoolean(isForceSdr); dest.writeBoolean(minimalPostProcessingSupported); dest.writeInt(logicalDensityDpi); dest.writeFloat(physicalXDpi); @@ -874,6 +881,8 @@ public final class DisplayInfo implements Parcelable { sb.append(Arrays.toString(appsSupportedModes)); sb.append(", hdrCapabilities "); sb.append(hdrCapabilities); + sb.append(", isForceSdr "); + sb.append(isForceSdr); sb.append(", userDisabledHdrTypes "); sb.append(Arrays.toString(userDisabledHdrTypes)); sb.append(", minimalPostProcessingSupported "); diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 6b4340a02edca3ee0b508fff134006a7a12aac7c..5c415165137eec100f77fa79a06b601dfcdf9db3 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -38,6 +38,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,7 +56,6 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import com.android.internal.annotations.VisibleForTesting; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.Objects; @@ -146,7 +146,7 @@ public class InsetsState implements Parcelable { forceConsumingTypes |= type; } - if (Flags.enableCaptionCompatInsetForceConsumptionAlways() + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue() && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) { forceConsumingOpaqueCaptionBar = true; } diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index dd950e83dd521eef8615ca2a1c318756270ceb45..b21e85aeeb6a82a842c629dfa1ae8428540f512f 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -174,24 +174,26 @@ public final class PointerIcon implements Parcelable { @IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_FILL_"}, value = { POINTER_ICON_VECTOR_STYLE_FILL_BLACK, POINTER_ICON_VECTOR_STYLE_FILL_GREEN, - POINTER_ICON_VECTOR_STYLE_FILL_YELLOW, + POINTER_ICON_VECTOR_STYLE_FILL_RED, POINTER_ICON_VECTOR_STYLE_FILL_PINK, - POINTER_ICON_VECTOR_STYLE_FILL_BLUE + POINTER_ICON_VECTOR_STYLE_FILL_BLUE, + POINTER_ICON_VECTOR_STYLE_FILL_PURPLE }) @Retention(RetentionPolicy.SOURCE) public @interface PointerIconVectorStyleFill {} /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLACK = 0; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_GREEN = 1; - /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_YELLOW = 2; + /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_RED = 2; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PINK = 3; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLUE = 4; + /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PURPLE = 5; // If adding a PointerIconVectorStyleFill, update END value for {@link SystemSettingsValidators} /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BEGIN = POINTER_ICON_VECTOR_STYLE_FILL_BLACK; /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_END = - POINTER_ICON_VECTOR_STYLE_FILL_BLUE; + POINTER_ICON_VECTOR_STYLE_FILL_PURPLE; /** @hide */ @IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_STROKE_"}, value = { @@ -712,12 +714,14 @@ public final class PointerIcon implements Parcelable { com.android.internal.R.style.PointerIconVectorStyleFillBlack; case POINTER_ICON_VECTOR_STYLE_FILL_GREEN -> com.android.internal.R.style.PointerIconVectorStyleFillGreen; - case POINTER_ICON_VECTOR_STYLE_FILL_YELLOW -> - com.android.internal.R.style.PointerIconVectorStyleFillYellow; + case POINTER_ICON_VECTOR_STYLE_FILL_RED -> + com.android.internal.R.style.PointerIconVectorStyleFillRed; case POINTER_ICON_VECTOR_STYLE_FILL_PINK -> com.android.internal.R.style.PointerIconVectorStyleFillPink; case POINTER_ICON_VECTOR_STYLE_FILL_BLUE -> com.android.internal.R.style.PointerIconVectorStyleFillBlue; + case POINTER_ICON_VECTOR_STYLE_FILL_PURPLE -> + com.android.internal.R.style.PointerIconVectorStyleFillPurple; default -> com.android.internal.R.style.PointerIconVectorStyleFillBlack; }; } diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING index db3590814e088b302a4afdd2ff7e55ff08c3761a..ac6cd02b58aa15757aeb5502889f48c4efbebe10 100644 --- a/core/java/android/view/TEST_MAPPING +++ b/core/java/android/view/TEST_MAPPING @@ -4,39 +4,11 @@ "name": "CtsAccelerationTestCases" }, { - "name": "CtsOsTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.os.cts.StrictModeTest" - } - ], + "name": "CtsOsTestCases_cts_strictmodetest_Presubmit", "file_patterns": ["(/|^)ViewConfiguration.java", "(/|^)GestureDetector.java"] }, { - "name": "CtsViewReceiveContentTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ], + "name": "CtsViewReceiveContentTestCases_Presubmit", "file_patterns": ["ContentInfo\\.java", "OnReceiveContentListener\\.java", "View\\.java"] } ], diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e10cc28d0745dd0577ed7aa6d77f5734752dae82..d46e1f29597e5b96f32975196dc135aef499ce42 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -125,11 +125,11 @@ import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; -import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption; import static com.android.window.flags.Flags.insetsControlChangedItem; import static com.android.window.flags.Flags.insetsControlSeq; import static com.android.window.flags.Flags.setScPropertiesInClient; @@ -829,6 +829,7 @@ public final class ViewRootImpl implements ViewParent, private final SurfaceControl mSurfaceControl = new SurfaceControl(); private BLASTBufferQueue mBlastBufferQueue; + private IBinder mBbqApplyToken = new Binder(); private final HdrRenderState mHdrRenderState = new HdrRenderState(this); @@ -2743,6 +2744,10 @@ public final class ViewRootImpl implements ViewParent, mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); + // If we create and destroy BBQ without recreating the SurfaceControl, we can end up + // queuing buffers on multiple apply tokens causing out of order buffer submissions. We + // fix this by setting the same apply token on all BBQs created by this VRI. + mBlastBufferQueue.setApplyToken(mBbqApplyToken); Surface blastSurface; if (addSchandleToVriSurface()) { blastSurface = mBlastBufferQueue.createSurfaceWithHandle(); @@ -3209,10 +3214,10 @@ public final class ViewRootImpl implements ViewParent, typesToShow |= Type.navigationBars(); } if (captionIsHiddenByFlags && !captionWasHiddenByFlags - && enableCaptionCompatInsetForceConsumption()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { typesToHide |= Type.captionBar(); } else if (!captionIsHiddenByFlags && captionWasHiddenByFlags - && enableCaptionCompatInsetForceConsumption()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { typesToShow |= Type.captionBar(); } if (typesToHide != 0) { diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index d2747e465071bd98066bd2c9496e6b0e1083ef8b..5129461095a3a848639d8b05988a2706d0d5f1ab 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -587,7 +587,14 @@ public class WindowlessWindowManager implements IWindowSession { @Override public void updateRequestedVisibleTypes(IWindow window, - @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token imeStatsToken) { + @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token imeStatsToken) + throws RemoteException { + if (android.view.inputmethod.Flags.refactorInsetsController()) { + // Embedded windows do not control insets (except for IME). The host window is + // responsible for controlling the insets. + mRealWm.updateRequestedVisibleTypes(window, + requestedVisibleTypes & WindowInsets.Type.ime(), imeStatsToken); + } } @Override diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index b9751c8cab5252e7da1b63eaf80cda6d3308dcc7..d90455ab971daedbfc70ca276d9b582ed772c1b0 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -920,7 +920,8 @@ public interface ImeTracker { final Configuration.Builder builder = Configuration.Builder.withSurface( cujType, jankContext.getDisplayContext(), - jankContext.getTargetSurfaceControl()) + jankContext.getTargetSurfaceControl(), + jankContext.getDisplayContext().getMainThreadHandler()) .setTag(String.format(Locale.US, "%d@%d@%s", animType, useSeparatedThread ? 0 : 1, jankContext.getHostPackageName())); InteractionJankMonitor.getInstance().begin(builder); diff --git a/core/java/android/view/inputmethod/TEST_MAPPING b/core/java/android/view/inputmethod/TEST_MAPPING index ad59463ea1f1b69ba0ac8321f3d0f017aeb13f32..989b686d62814f40edbcaac963614b2f73f9904c 100644 --- a/core/java/android/view/inputmethod/TEST_MAPPING +++ b/core/java/android/view/inputmethod/TEST_MAPPING @@ -1,21 +1,7 @@ { "presubmit": [ { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-filter": "android.autofillservice.cts.inline" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.AppModeFull" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsAutoFillServiceTestCases_cts_inline_ExcludeAppModeFull" } ] } diff --git a/core/java/android/view/textclassifier/TEST_MAPPING b/core/java/android/view/textclassifier/TEST_MAPPING index 050c65191cad527f014941171f42be633eccc4b8..bc7f3b0e2dc1e556cabd8b6a6d5b2bed729e49b9 100644 --- a/core/java/android/view/textclassifier/TEST_MAPPING +++ b/core/java/android/view/textclassifier/TEST_MAPPING @@ -4,20 +4,10 @@ "name": "FrameworksCoreTests_textclassifier" }, { - "name": "CtsTextClassifierTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTextClassifierTestCases" }, { - "name": "TextClassifierServiceTest", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TextClassifierServiceTest" } ] } diff --git a/core/java/android/webkit/TEST_MAPPING b/core/java/android/webkit/TEST_MAPPING index 07f438329e433ece47122da3ba4022eac3062775..38580595dc2d8e5b4ea30016fbe39c3495792d8d 100644 --- a/core/java/android/webkit/TEST_MAPPING +++ b/core/java/android/webkit/TEST_MAPPING @@ -1,28 +1,13 @@ { "presubmit": [ { - "name": "CtsWebkitTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsWebkitTestCases" }, { - "name": "CtsSdkSandboxWebkitTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsSdkSandboxWebkitTestCases" }, { - "name": "CtsHostsideWebViewTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsHostsideWebViewTests" }, { "name": "GtsWebViewTestCases", diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index c7900e4f0510584c3d6cda68bc415c728fccc2b8..668cd0152846a34b158663c8d8e507cef679a474 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -22,6 +22,7 @@ import android.content.pm.PackageInfo; import android.os.Build; import android.os.ChildZygoteProcess; import android.os.Process; +import android.os.UserHandle; import android.os.ZygoteProcess; import android.text.TextUtils; import android.util.Log; @@ -141,12 +142,14 @@ public class WebViewZygote { String abi = sPackage.applicationInfo.primaryCpuAbi; int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote( sPackage.applicationInfo, null); + final int[] sharedAppGid = { + UserHandle.getSharedAppGid(UserHandle.getAppId(sPackage.applicationInfo.uid)) }; sZygote = Process.ZYGOTE_PROCESS.startChildZygote( "com.android.internal.os.WebViewZygoteInit", "webview_zygote", Process.WEBVIEW_ZYGOTE_UID, Process.WEBVIEW_ZYGOTE_UID, - null, // gids + sharedAppGid, // Access to shared app GID for ART profiles runtimeFlags, "webview_zygote", // seInfo abi, // abi diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e1154ca0701cd229dfc86a1d794e4a82a506030b..06820cd4c2ce8c7c31acb21e1597064e4598e0e7 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1003,6 +1003,55 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_EMPTY_VIEW_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + out.write(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + appResources.getResourceName(mViewId)); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetEmptyViewAction.VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.VIEW_ID); + int emptyViewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID); + return new SetEmptyView(viewId, emptyViewId); + }; + } } private static class SetPendingIntentTemplate extends Action { @@ -1243,6 +1292,68 @@ public class RemoteViews implements Parcelable, Filter { mItems.visitUris(visitor); } + + @Override + public boolean canWriteToProto() { + // Skip actions that do not contain items (intent only actions) + return mItems != null; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + if (mItems == null) return; + final long token = out.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + out.write(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + appResources.getResourceName(mViewId)); + final long itemsToken = out.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + mItems.writeToProto(context, out, /* attached= */ true); + out.end(itemsToken); + out.end(token); + } + } + + private PendingResources createSetRemoteCollectionItemListAdapterActionFromProto( + ProtoInputStream in) throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID: + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + in.readString( + RemoteViewsProto + .SetRemoteCollectionItemListAdapterAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS: + final long itemsToken = in.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS, + RemoteCollectionItems.createFromProto(in)); + in.end(itemsToken); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID); + return new SetRemoteCollectionItemListAdapterAction(viewId, + ((PendingResources) values.get( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS)) + .create(context, resources, rootData, depth)); + }; } /** @@ -2036,6 +2147,68 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_DRAWABLE_TINT_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + out.write(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, mColorFilter); + out.write(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.modeToInt(mFilterMode)); + out.write(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, mTargetBackground); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetDrawableTintAction.VIEW_ID: + values.put(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + in.readString(RemoteViewsProto.SetDrawableTintAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND: + values.put(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, + in.readBoolean( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER: + values.put(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, + in.readInt(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.FILTER_MODE: + values.put(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.intToMode(in.readInt( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE))); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetDrawableTintAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetDrawableTintAction.VIEW_ID); + return new SetDrawableTint(viewId, (boolean) values.get( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, false), + (int) values.get(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 0), + (PorterDuff.Mode) values.get( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE)); + }; + } } /** @@ -2047,7 +2220,7 @@ public class RemoteViews implements Parcelable, Filter { * target {@link View#getBackground()}. *

*/ - private class SetRippleDrawableColor extends Action { + private static class SetRippleDrawableColor extends Action { ColorStateList mColorStateList; SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) { @@ -2082,6 +2255,58 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RIPPLE_DRAWABLE_COLOR_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + out.write(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + appResources.getResourceName(mViewId)); + writeColorStateListToProto(out, mColorStateList, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST, + createColorStateListFromProto(in, + RemoteViewsProto + .SetRippleDrawableColorAction.COLOR_STATE_LIST)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID); + return new SetRippleDrawableColor(viewId, (ColorStateList) values.get( + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST)); + }; + } } /** @@ -2987,6 +3212,82 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return RESOURCE_REFLECTION_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + out.write(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, mMethodName); + out.write(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, mType); + out.write(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, mResourceType); + if (mResId != 0) { + out.write(RemoteViewsProto.ResourceReflectionAction.RES_ID, + appResources.getResourceName(mResId)); + } + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.ResourceReflectionAction.VIEW_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.METHOD_NAME: + values.put(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + in.readString( + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RES_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.RES_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.RES_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.VIEW_ID); + + int resId = (values.indexOfKey(RemoteViewsProto.ResourceReflectionAction.RES_ID) + >= 0) ? getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.RES_ID) : 0; + return new ResourceReflectionAction(viewId, + (String) values.get(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + 0), resId); + }; + } } private static final class AttributeReflectionAction extends BaseReflectionAction { @@ -4593,6 +4894,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_INT_TAG_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + out.write(RemoteViewsProto.SetIntTagAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetIntTagAction.KEY, + appResources.getResourceName(mKey)); // rebase + out.write(RemoteViewsProto.SetIntTagAction.TAG, mTag); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetIntTagAction.VIEW_ID: + values.put(RemoteViewsProto.SetIntTagAction.VIEW_ID, + in.readString(RemoteViewsProto.SetIntTagAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetIntTagAction.KEY: + values.put(RemoteViewsProto.SetIntTagAction.KEY, + in.readString(RemoteViewsProto.SetIntTagAction.KEY)); + break; + case (int) RemoteViewsProto.SetIntTagAction.TAG: + values.put(RemoteViewsProto.SetIntTagAction.TAG, + in.readInt(RemoteViewsProto.SetIntTagAction.TAG)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetIntTagAction.VIEW_ID, + RemoteViewsProto.SetIntTagAction.KEY}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.VIEW_ID); + int keyId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.KEY); + return new SetIntTagAction(viewId, keyId, + (int) values.get(RemoteViewsProto.SetIntTagAction.TAG, 0)); + }; + } } private static class SetCompoundButtonCheckedAction extends Action { @@ -4643,6 +4999,56 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_COMPOUND_BUTTON_CHECKED_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start( + RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, mChecked); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, + in.readBoolean( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID); + return new SetCompoundButtonCheckedAction(viewId, (boolean) values.get( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, false)); + }; + } } private static class SetRadioGroupCheckedAction extends Action { @@ -4707,6 +5113,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RADIO_GROUP_CHECKED; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + if (mCheckedId != -1) { + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + appResources.getResourceName(mCheckedId)); + } + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + in.readString(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + in.readString( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID); + + int checkedId = (values.indexOfKey( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) >= 0) + ? getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) : -1; + return new SetRadioGroupCheckedAction(viewId, checkedId); + }; + } } private static class SetViewOutlinePreferredRadiusAction extends Action { @@ -8450,6 +8911,7 @@ public class RemoteViews implements Parcelable, Filter { public static PendingResources createFromProto(ProtoInputStream in) throws Exception { final LongSparseArray values = new LongSparseArray<>(); + values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList()); values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS, new ArrayList>()); @@ -9207,6 +9669,22 @@ public class RemoteViews implements Parcelable, Filter { return ReflectionAction.createFromProto(in); case (int) RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION: return RemoveFromParentAction.createFromProto(in); + case (int) RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION: + return ResourceReflectionAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION: + return SetCompoundButtonCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION: + return SetDrawableTint.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION: + return SetEmptyView.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_INT_TAG_ACTION: + return SetIntTagAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION: + return SetRadioGroupCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION: + return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in); + case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION: + return SetRippleDrawableColor.createFromProto(in); default: throw new RuntimeException("Unhandled field while reading Action proto!\n" + ProtoUtils.currentFieldToString(in)); diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING index bc71bee33d066ede15a6487d0427266545592841..624fa864aea6224f47354f8ffb2b8abbd38279ec 100644 --- a/core/java/android/widget/TEST_MAPPING +++ b/core/java/android/widget/TEST_MAPPING @@ -10,52 +10,17 @@ "file_patterns": ["Toast\\.java"] }, { - "name": "CtsWindowManagerDeviceWindow", - "options": [ - { - "include-filter": "android.server.wm.window.ToastWindowTest" - } - ], + "name": "CtsWindowManagerDeviceWindow_window_toastwindowtest", "file_patterns": ["Toast\\.java"] }, { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-filter": "android.autofillservice.cts.dropdown.LoginActivityTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.AppModeFull" - } - ] + "name": "CtsAutoFillServiceTestCases_dropdown_loginactivitytest" }, { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-filter": "android.autofillservice.cts.dropdown.CheckoutActivityTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.AppModeFull" - } - ] + "name": "CtsAutoFillServiceTestCases_dropdown_checkoutactivitytest" }, { - "name": "CtsTextTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsTextTestCases_text" } ] } diff --git a/core/java/android/widget/inline/TEST_MAPPING b/core/java/android/widget/inline/TEST_MAPPING index 82c6f61c3486cdf8c45f9e980cc84c120e395b99..eb412f110def91ba1e529285ef0d89a03f655be8 100644 --- a/core/java/android/widget/inline/TEST_MAPPING +++ b/core/java/android/widget/inline/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit-large": [ { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-filter": "android.autofillservice.cts.inline" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsAutoFillServiceTestCases_cts_inline" } ] } diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index 3cfde870de1826e4c147e58373eecf57cec56ace..8bb4c526b20ded5e06c65494250f50e404802f3d 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -187,6 +187,7 @@ public final class TransitionFilter implements Parcelable { /** If non-null, requires the change to specifically have or not-have a custom animation. */ public Boolean mCustomAnimation = null; + public IBinder mTaskFragmentToken = null; public Requirement() { } @@ -204,12 +205,19 @@ public final class TransitionFilter implements Parcelable { // 0: null, 1: false, 2: true final int customAnimRaw = in.readInt(); mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2); + mTaskFragmentToken = in.readStrongBinder(); } /** Go through changes and find if at-least one change matches this filter */ boolean matches(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); + + if (mTaskFragmentToken != null + && !mTaskFragmentToken.equals(change.getTaskFragmentToken())) { + continue; + } + if (mMustBeIndependent && !TransitionInfo.isIndependent(change, info)) { // Only look at independent animating windows. continue; @@ -313,6 +321,7 @@ public final class TransitionFilter implements Parcelable { dest.writeStrongBinder(mLaunchCookie); int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1); dest.writeInt(customAnimRaw); + dest.writeStrongBinder(mTaskFragmentToken); } @NonNull @@ -357,6 +366,9 @@ public final class TransitionFilter implements Parcelable { if (mCustomAnimation != null) { out.append(" customAnim=").append(mCustomAnimation.booleanValue()); } + if (mTaskFragmentToken != null) { + out.append(" taskFragmentToken=").append(mTaskFragmentToken); + } out.append("}"); return out.toString(); } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index ec79f94a6dd33b7f7ef0896bfd0301e97d118638..14505f5271953010ae309c6b1389e6238763395e 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -49,6 +49,7 @@ import android.content.ComponentName; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; @@ -681,6 +682,7 @@ public final class TransitionInfo implements Parcelable { private float mSnapshotLuma; private ComponentName mActivityComponent = null; private AnimationOptions mAnimationOptions = null; + private IBinder mTaskFragmentToken = null; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; @@ -712,6 +714,7 @@ public final class TransitionInfo implements Parcelable { mSnapshotLuma = in.readFloat(); mActivityComponent = in.readTypedObject(ComponentName.CREATOR); mAnimationOptions = in.readTypedObject(AnimationOptions.CREATOR); + mTaskFragmentToken = in.readStrongBinder(); } private Change localRemoteCopy() { @@ -737,6 +740,7 @@ public final class TransitionInfo implements Parcelable { out.mSnapshotLuma = mSnapshotLuma; out.mActivityComponent = mActivityComponent; out.mAnimationOptions = mAnimationOptions; + out.mTaskFragmentToken = mTaskFragmentToken; return out; } @@ -854,6 +858,14 @@ public final class TransitionInfo implements Parcelable { mAnimationOptions = options; } + /** + * Sets the client-defined TaskFragment token. Only set this if the window is a + * client-organized TaskFragment. + */ + public void setTaskFragmentToken(@Nullable IBinder token) { + mTaskFragmentToken = token; + } + /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { @@ -1009,6 +1021,15 @@ public final class TransitionInfo implements Parcelable { return mAnimationOptions; } + /** + * Returns the client-defined TaskFragment token. {@code null} if this window is not a + * client-organized TaskFragment. + */ + @Nullable + public IBinder getTaskFragmentToken() { + return mTaskFragmentToken; + } + /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -1035,6 +1056,7 @@ public final class TransitionInfo implements Parcelable { dest.writeFloat(mSnapshotLuma); dest.writeTypedObject(mActivityComponent, flags); dest.writeTypedObject(mAnimationOptions, flags); + dest.writeStrongBinder(mTaskFragmentToken); } @NonNull @@ -1110,6 +1132,9 @@ public final class TransitionInfo implements Parcelable { if (mAnimationOptions != null) { sb.append(" opt=").append(mAnimationOptions); } + if (mTaskFragmentToken != null) { + sb.append(" taskFragmentToken=").append(mTaskFragmentToken); + } sb.append('}'); return sb.toString(); } diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java index 5c53d66e49fedea2b1a8da36dafefbbd10979e7d..944a106bf441cdf6519f0c9bce1aa236bf3f232f 100644 --- a/core/java/android/window/flags/DesktopModeFlags.java +++ b/core/java/android/window/flags/DesktopModeFlags.java @@ -17,7 +17,9 @@ package android.window.flags; import android.annotation.Nullable; -import android.content.Context; +import android.app.ActivityThread; +import android.app.Application; +import android.content.ContentResolver; import android.provider.Settings; import android.util.Log; @@ -39,9 +41,30 @@ import java.util.function.Supplier; */ public enum DesktopModeFlags { // All desktop mode related flags to be overridden by developer option toggle will be added here - DESKTOP_WINDOWING_MODE( + ENABLE_DESKTOP_WINDOWING_MODE( Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true), - DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false); + ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true), + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION( + Flags::enableCaptionCompatInsetForceConsumption, true), + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS( + Flags::enableCaptionCompatInsetForceConsumptionAlways, true), + ENABLE_CASCADING_WINDOWS(Flags::enableCascadingWindows, true), + ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY( + Flags::enableDesktopWindowingWallpaperActivity, true), + ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true), + ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), + ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true), + ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true), + ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true), + ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true), + DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true), + ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true), + ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true), + ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true), + ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true), + ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS( + Flags::enableDesktopWindowingTaskbarRunningApps, true), + ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false); private static final String TAG = "DesktopModeFlagsUtil"; // Function called to obtain aconfig flag value. @@ -62,14 +85,15 @@ public enum DesktopModeFlags { * Determines state of flag based on the actual flag and desktop mode developer option * overrides. */ - public boolean isEnabled(Context context) { + public boolean isTrue() { + Application application = ActivityThread.currentApplication(); if (!Flags.showDesktopWindowingDevOption() || !mShouldOverrideByDevOption - || context.getContentResolver() == null) { + || application == null) { return mFlagFunction.get(); } else { boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode(); - return switch (getToggleOverride(context)) { + return switch (getToggleOverride(application.getContentResolver())) { case OVERRIDE_UNSET -> mFlagFunction.get(); // When toggle override matches its default state, don't override flags. This // helps users reset their feature overrides. @@ -79,14 +103,14 @@ public enum DesktopModeFlags { } } - private ToggleOverride getToggleOverride(Context context) { + private ToggleOverride getToggleOverride(ContentResolver contentResolver) { // If cached, return it if (sCachedToggleOverride != null) { return sCachedToggleOverride; } // Otherwise, fetch and cache it - ToggleOverride override = getToggleOverrideFromSystem(context); + ToggleOverride override = getToggleOverrideFromSystem(contentResolver); sCachedToggleOverride = override; Log.d(TAG, "Toggle override initialized to: " + override); return override; @@ -95,9 +119,9 @@ public enum DesktopModeFlags { /** * Returns {@link ToggleOverride} from Settings.Global set by toggle. */ - private ToggleOverride getToggleOverrideFromSystem(Context context) { + private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) { int settingValue = Settings.Global.getInt( - context.getContentResolver(), + contentResolver, Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, ToggleOverride.OVERRIDE_UNSET.getSetting() ); @@ -105,12 +129,13 @@ public enum DesktopModeFlags { } /** Override state of desktop mode developer option toggle. */ - private enum ToggleOverride { + public enum ToggleOverride { OVERRIDE_UNSET, OVERRIDE_OFF, OVERRIDE_ON; - int getSetting() { + /** Returns the integer representation of this {@code ToggleOverride}. */ + public int getSetting() { return switch (this) { case OVERRIDE_ON -> 1; case OVERRIDE_OFF -> 0; @@ -118,7 +143,8 @@ public enum DesktopModeFlags { }; } - static ToggleOverride fromSetting(int setting, @Nullable ToggleOverride fallback) { + /** Returns the {@code ToggleOverride} corresponding to a given integer setting. */ + public static ToggleOverride fromSetting(int setting, @Nullable ToggleOverride fallback) { return switch (setting) { case 1 -> OVERRIDE_ON; case 0 -> OVERRIDE_OFF; diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 0f401d3e60b11dc6d10d68fdb1d4ef295022a561..cc5e583034a585f46375d147b6a06bbdedaf4749 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -86,10 +86,13 @@ flag { } flag { - name: "enable_additional_windows_above_status_bar" + name: "enable_handle_input_fix" namespace: "lse_desktop_experience" - description: "Allows for additional windows tied to WindowDecoration to be layered between status bar and notification shade." + description: "Enables using AdditionalSystemViewContainer to resolve handle input issues." bug: "316186265" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -113,6 +116,13 @@ flag { bug: "325240051" } +flag { + name: "enable_tile_resizing" + namespace: "lse_desktop_experience" + description: "Enables drawing a divider bar upon tiling tasks left and right in desktop mode for simultaneous resizing" + bug: "351769839" +} + flag { name: "respect_orientation_change_for_unresizeable" namespace: "lse_desktop_experience" @@ -249,6 +259,16 @@ flag { bug: "356843241" } +flag { + name: "enable_hold_to_drag_app_handle" + namespace: "lse_desktop_experience" + description: "Requires hold-to-drag the App Handle when using touchscreen input" + bug: "356409496" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "skip_compat_ui_education_in_desktop_mode" namespace: "lse_desktop_experience" @@ -272,3 +292,10 @@ flag { description: "Creates a shell transition when display focus switches." bug: "356109871" } + +flag { + name: "enter_desktop_by_default_on_freeform_displays" + namespace: "lse_desktop_experience" + description: "Allow entering desktop mode by default on freeform displays" + bug: "361419732" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 69b91fdfaa9851ba50b58cc94bf9358587ffb529..e1402f8224ebb5d82ac96ad9708faa1caae4c99a 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -245,15 +245,6 @@ flag { } } -flag { - name: "custom_animations_behind_translucent" - namespace: "windowing_frontend" - description: "A change can use its own layer parameters to animate behind a translucent activity" - bug: "327332488" - metadata { - purpose: PURPOSE_BUGFIX - } -} flag { name: "migrate_predictive_back_transition" namespace: "windowing_frontend" @@ -274,4 +265,4 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -} \ No newline at end of file +} diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 13648de5b28ecb3d4128b1ed5fdddd43a60740f5..9ae3fc1fa3f08d846c872964385e7fd542d28ccf 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -122,3 +122,10 @@ flag { description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in" bug: "358129114" } + +flag { + namespace: "windowing_sdk" + name: "wlinfo_oncreate" + description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()" + bug: "337820752" +} diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING index 35f0553d41d7f091e779566a0e3fe2a08f5e2fe3..092aa20ba7303f6d2f089b8fa5a4bb600d336b47 100644 --- a/core/java/com/android/internal/infra/TEST_MAPPING +++ b/core/java/com/android/internal/infra/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsRoleTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsRoleTestCases" }, { "name": "CtsPermissionTestCases_Platform" diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index c7e1fba66d7f8a995c6c5f4a57b7e6fb6350ce21..ef08e49ce6d9bd1985991d64708019719fef4fcd 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -330,9 +330,10 @@ public class InteractionJankMonitor { * @param cujType the specific {@link Cuj.CujType}. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType) { + public boolean begin(SurfaceControl surface, Context context, Handler handler, + @Cuj.CujType int cujType) { try { - return begin(Configuration.Builder.withSurface(cujType, context, surface)); + return begin(Configuration.Builder.withSurface(cujType, context, surface, handler)); } catch (IllegalArgumentException ex) { Log.d(TAG, "Build configuration failed!", ex); return false; @@ -348,11 +349,12 @@ public class InteractionJankMonitor { * @param tag a tag containing extra information about the interaction. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(SurfaceControl surface, Context context, @Cuj.CujType int cujType, + public boolean begin(SurfaceControl surface, Context context, Handler handler, + @Cuj.CujType int cujType, String tag) { try { final Configuration.Builder builder = - Configuration.Builder.withSurface(cujType, context, surface); + Configuration.Builder.withSurface(cujType, context, surface, handler); if (!TextUtils.isEmpty(tag)) { builder.setTag(tag); } @@ -689,20 +691,23 @@ public class InteractionJankMonitor { private SurfaceControl mAttrSurfaceControl; private final @Cuj.CujType int mAttrCujType; private boolean mAttrDeferMonitor = true; + private Handler mHandler = null; /** * Creates a builder which instruments only surface. * @param cuj The enum defined in {@link Cuj.CujType}. * @param context context * @param surfaceControl surface control + * @param uiThreadHandler UI thread for that surface * @return builder */ public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context, - @NonNull SurfaceControl surfaceControl) { + @NonNull SurfaceControl surfaceControl, @NonNull Handler uiThreadHandler) { return new Builder(cuj) .setContext(context) .setSurfaceControl(surfaceControl) - .setSurfaceOnly(true); + .setSurfaceOnly(true) + .setHandler(uiThreadHandler); } /** @@ -721,6 +726,18 @@ public class InteractionJankMonitor { mAttrCujType = cuj; } + /** + * Specifies the UI thread handler. If not provided, the View's one will be used. + * If only a surface is provided without handler, the app main thread will be used. + * + * @param uiThreadHandler handler associated to the cuj UI thread + * @return builder + */ + public Builder setHandler(Handler uiThreadHandler) { + mHandler = uiThreadHandler; + return this; + } + /** * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false. * @param view an attached view @@ -798,13 +815,13 @@ public class InteractionJankMonitor { return new Configuration( mAttrCujType, mAttrView, mAttrTag, mAttrTimeout, mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl, - mAttrDeferMonitor); + mAttrDeferMonitor, mHandler); } } private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout, boolean surfaceOnly, Context context, SurfaceControl surfaceControl, - boolean deferMonitor) { + boolean deferMonitor, Handler handler) { mCujType = cuj; mTag = tag; mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag); @@ -816,8 +833,16 @@ public class InteractionJankMonitor { : (view != null ? view.getContext().getApplicationContext() : null); mSurfaceControl = surfaceControl; mDeferMonitor = deferMonitor; + if (handler != null) { + mHandler = handler; + } else if (mSurfaceOnly) { + Log.w(TAG, "No UIThread provided for " + mSessionName + + " (surface only). Defaulting to app main thread."); + mHandler = mContext.getMainThreadHandler(); + } else { + mHandler = mView.getHandler(); + } validate(); - mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler(); } @VisibleForTesting @@ -858,6 +883,12 @@ public class InteractionJankMonitor { shouldThrow = true; msg.append("Must pass in a valid surface control if only instrument surface; "); } + if (mHandler == null) { + shouldThrow = true; + msg.append( + "Must pass a UI thread handler when only a surface control is " + + "provided."); + } } else { if (!hasValidView()) { shouldThrow = true; diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index 258f402cf83168dde3d467f783a3a6ce5ebb569c..4400ed11772126331531887e509c7a374fd164db 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -49,12 +49,7 @@ ], "postsubmit": [ { - "name": "PowerStatsTests", - "options": [ - { - "include-filter": "com.android.server.power.stats.BstatsCpuTimesValidationTest" - } - ], + "name": "PowerStatsTests_stats_bstatscputimesvalidationtest", "file_patterns": [ "Kernel[^/]*\\.java" ] diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 4708be8108c26bb89cd220bdfaaa63126aa0ed09..6faea17f24b2701127ad31390dadb93e1915830f 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -38,6 +38,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATIO import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.flags.Flags.customizableWindowHeaders; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL; @@ -114,7 +116,6 @@ import com.android.internal.view.menu.MenuHelper; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; -import com.android.window.flags.Flags; import java.util.List; import java.util.concurrent.Executor; @@ -1217,14 +1218,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final boolean hideCaptionBar = fullscreen || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0; - final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption() - && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0 + final boolean consumingCaptionBar = + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue() + && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0 && hideCaptionBar); final boolean isOpaqueCaptionBar = customizableWindowHeaders() && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0; final boolean consumingOpaqueCaptionBar = - Flags.enableCaptionCompatInsetForceConsumptionAlways() + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue() && mLastForceConsumingOpaqueCaptionBar && isOpaqueCaptionBar; diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index 067e5e8813a721254dfa54d4de8c07f39e37e574..b23515aa51f3368609337c1e52d83c2f07d43e5b 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -37,6 +37,8 @@ public class ScreenDecorationsUtils { * * Note that if the context is not an UI context(not associated with Display), it will use * default display. + * + * If the associated display is not internal, will return 0. */ public static float getWindowCornerRadius(Context context) { final Resources resources = context.getResources(); @@ -44,7 +46,13 @@ public class ScreenDecorationsUtils { return 0f; } // Use Context#getDisplayNoVerify() in case the context is not an UI context. - final String displayUniqueId = context.getDisplayNoVerify().getUniqueId(); + final Display display = context.getDisplayNoVerify(); + // The radius is only valid for internal displays, since the corner radius of external or + // virtual displays is not known when window corners are configured or are not supported. + if (display.getType() != Display.TYPE_INTERNAL) { + return 0f; + } + final String displayUniqueId = display.getUniqueId(); // Radius that should be used in case top or bottom aren't defined. float defaultRadius = RoundedCorners.getRoundedCornerRadius(resources, displayUniqueId) - RoundedCorners.getRoundedCornerRadiusAdjustment(resources, displayUniqueId); diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index e440dc9053fd4fede238f6ce139d1cb10e5c58e8..fbc058cc0330b54e65b6ca62bd61d29aea2ae245 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -210,6 +210,8 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto DataSourceParams .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) .build(); + // NOTE: Registering that datasource is an async operation, so there may be no data traced + // for some messages logged right after the construction of this class. mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; this.mViewerConfigReader = viewerConfigReader; @@ -223,17 +225,17 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto "ServiceManager returned a null ProtoLog Configuration Service"); try { - var args = new ProtoLogConfigurationService.RegisterClientArgs(); + var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs(); if (viewerConfigFilePath != null) { args.setViewerConfigFile(viewerConfigFilePath); } final var groupArgs = Stream.of(groups) - .map(group -> new ProtoLogConfigurationService.RegisterClientArgs + .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(group.name(), group.isLogToLogcat())) - .toArray( - ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new); + .toArray(ProtoLogConfigurationServiceImpl + .RegisterClientArgs.GroupConfig[]::new); args.setGroups(groupArgs); mProtoLogConfigurationService.registerClient(this, args); diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java index 7031d694f09c48e22f8bd5168a82dd61536e2321..d65aaae7deaa7930755e36b978a2fd473965b45a 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java @@ -16,434 +16,32 @@ package com.android.internal.protolog; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; - import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemService; -import android.content.Context; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; -import android.tracing.perfetto.DataSourceParams; -import android.tracing.perfetto.InitArguments; -import android.tracing.perfetto.Producer; -import android.util.Log; -import android.util.proto.ProtoInputStream; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/** - * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing - * system. Currently this service has the following roles: - * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. - * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog - * clients. This is for two reasons: firstly, because client processes might be frozen so might - * not response to the request to dump their viewer config when the trace is stopped; secondly, - * multiple processes might be running the same code with the same viewer config, this centralized - * service ensures we don't dump the same viewer config multiple times across processes. - *

- * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to - * this service on initialization. - *

- * This service is intended to run on the system server, such that it never gets frozen. - */ -@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) -public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub { - private static final String LOG_TAG = "ProtoLogConfigurationService"; - - private final ProtoLogDataSource mDataSource; - - /** - * Keeps track of how many of each viewer config file is currently registered. - * Use to keep track of which viewer config files are actively being used in tracing and might - * need to be dumped on flush. - */ - private final Map mConfigFileCounts = new HashMap<>(); - /** - * Keeps track of the viewer config file of each client if available. - */ - private final Map mClientConfigFiles = new HashMap<>(); - - /** - * Keeps track of all the protolog groups that have been registered by clients and are still - * being actively traced. - */ - private final Set mRegisteredGroups = new HashSet<>(); - /** - * Keeps track of all the clients that are actively tracing a given protolog group. - */ - private final Map> mGroupToClients = new HashMap<>(); - - /** - * Keeps track of whether or not a given group should be logged to logcat. - * True when logging to logcat, false otherwise. - */ - private final Map mLogGroupToLogcatStatus = new TreeMap<>(); - - /** - * Keeps track of all the tracing instance ids that are actively running for ProtoLog. - */ - private final Set mRunningInstances = new HashSet<>(); - - private final ViewerConfigFileTracer mViewerConfigFileTracer; - - public ProtoLogConfigurationService() { - this(ProtoLogDataSource::new, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { - this(dataSourceBuilder, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) { - this(ProtoLogDataSource::new, tracer); - } - - @VisibleForTesting - public ProtoLogConfigurationService( - @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, - @NonNull ViewerConfigFileTracer tracer) { - mDataSource = dataSourceBuilder.build( - this::onTracingInstanceStart, - this::onTracingInstanceFlush, - this::onTracingInstanceStop - ); - - // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be - // receive the lifecycle callbacks of the datasource and write the viewer configs if and - // when required to the datasource. - Producer.init(InitArguments.DEFAULTS); - final var params = new DataSourceParams.Builder() - .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) - .build(); - mDataSource.register(params); - - mViewerConfigFileTracer = tracer; - } - - public static class RegisterClientArgs extends IRegisterClientArgs.Stub { - /** - * The viewer config file to be registered for this client ProtoLog process. - */ - @Nullable - private String mViewerConfigFile = null; - /** - * The list of all groups that this client protolog process supports and might trace. - */ - @NonNull - private String[] mGroups = new String[0]; - /** - * The default logcat status of the ProtoLog client. True is logging to logcat, false - * otherwise. The indices should match the indices in {@link mGroups}. - */ - @NonNull - private boolean[] mLogcatStatus = new boolean[0]; - - public record GroupConfig(@NonNull String group, boolean logToLogcat) {} - - /** - * Specify groups to register with this client that will be used for protologging in this - * process. - * @param groups to register with this client. - * @return self - */ - public RegisterClientArgs setGroups(GroupConfig... groups) { - mGroups = new String[groups.length]; - mLogcatStatus = new boolean[groups.length]; - - for (int i = 0; i < groups.length; i++) { - mGroups[i] = groups[i].group; - mLogcatStatus[i] = groups[i].logToLogcat; - } - - return this; - } - - /** - * Set the viewer config file that the logs in this process are using. - * @param viewerConfigFile The file path of the viewer config. - * @return self - */ - public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { - mViewerConfigFile = viewerConfigFile; - - return this; - } - - @Override - @NonNull - public String[] getGroups() { - return mGroups; - } - - @Override - @NonNull - public boolean[] getGroupsDefaultLogcatStatus() { - return mLogcatStatus; - } - - @Nullable - @Override - public String getViewerConfigFile() { - return mViewerConfigFile; - } - } - - @FunctionalInterface - public interface ViewerConfigFileTracer { - /** - * Write the viewer config data to the trace buffer. - * - * @param dataSource The target datasource to write the viewer config to. - * @param viewerConfigFilePath The path of the viewer config file which contains the data we - * want to write to the trace buffer. - * @throws FileNotFoundException if the viewerConfigFilePath is invalid. - */ - void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); - } - - @Override - public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) - throws RemoteException { - client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); - - final String viewerConfigFile = args.getViewerConfigFile(); - if (viewerConfigFile != null) { - registerViewerConfigFile(client, viewerConfigFile); - } - - registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); - } - - @Override - public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, - @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, - @NonNull ResultReceiver resultReceiver) throws RemoteException { - new ProtoLogCommandHandler(this) - .exec(this, in, out, err, args, callback, resultReceiver); - } +public interface ProtoLogConfigurationService extends IProtoLogConfigurationService { /** * Get the list of groups clients have registered to the protolog service. * @return The list of ProtoLog groups registered with this service. */ @NonNull - public String[] getGroups() { - return mRegisteredGroups.toArray(new String[0]); - } + String[] getGroups(); + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + boolean isLoggingToLogcat(@NonNull String group); /** * Enable logging target groups to logcat. * @param groups we want to enable logging them to logcat for. */ - public void enableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(true, groups); - } + void enableProtoLogToLogcat(@NonNull String... groups); /** * Disable logging target groups to logcat. * @param groups we want to disable from being logged to logcat. */ - public void disableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(false, groups); - } - - /** - * Check if a group is logging to logcat - * @param group The group we want to check for - * @return True iff we are logging this group to logcat. - */ - public boolean isLoggingToLogcat(@NonNull String group) { - final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); - - if (isLoggingToLogcat == null) { - throw new RuntimeException( - "Trying to get logcat logging status of non-registered group " + group); - } - - return isLoggingToLogcat; - } - - private void registerViewerConfigFile( - @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { - final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); - mConfigFileCounts.put(viewerConfigFile, count + 1); - mClientConfigFiles.put(client, viewerConfigFile); - } - - private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, - @NonNull boolean[] logcatStatuses) throws RemoteException { - if (groups.length != logcatStatuses.length) { - throw new RuntimeException( - "Expected groups and logcatStatuses to have the same length, " - + "but groups has length " + groups.length - + " and logcatStatuses has length " + logcatStatuses.length); - } - - for (int i = 0; i < groups.length; i++) { - String group = groups[i]; - boolean logcatStatus = logcatStatuses[i]; - - mRegisteredGroups.add(group); - - mGroupToClients.putIfAbsent(group, new HashSet<>()); - mGroupToClients.get(group).add(client); - - if (!mLogGroupToLogcatStatus.containsKey(group)) { - mLogGroupToLogcatStatus.put(group, logcatStatus); - } - - boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); - if (requestedLogToLogcat != logcatStatus) { - client.toggleLogcat(requestedLogToLogcat, new String[] { group }); - } - } - } - - private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { - final var clientToGroups = new HashMap>(); - - for (String group : groups) { - final var clients = mGroupToClients.get(group); - - if (clients == null) { - // No clients associated to this group - Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group - + " with no registered clients."); - continue; - } - - for (IProtoLogClient client : clients) { - clientToGroups.putIfAbsent(client, new HashSet<>()); - clientToGroups.get(client).add(group); - } - } - - for (IProtoLogClient client : clientToGroups.keySet()) { - try { - client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); - } catch (RemoteException e) { - throw new RuntimeException( - "Failed to toggle logcat status for groups on client", e); - } - } - - for (String group : groups) { - mLogGroupToLogcatStatus.put(group, enabled); - } - } - - private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.add(instanceIdx); - } - - private void onTracingInstanceFlush() { - for (String fileName : mConfigFileCounts.keySet()) { - mViewerConfigFileTracer.trace(mDataSource, fileName); - } - } - - private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.remove(instanceIdx); - } - - private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, - @NonNull String viewerConfigFilePath) { - Utils.dumpViewerConfig(dataSource, () -> { - try { - return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); - } catch (FileNotFoundException e) { - throw new RuntimeException( - "Failed to load viewer config file " + viewerConfigFilePath, e); - } - }); - } - - private void onClientBinderDeath(@NonNull IProtoLogClient client) { - // Dump the tracing config now if no other client is going to dump the same config file. - String configFile = mClientConfigFiles.get(client); - if (configFile != null) { - final var newCount = mConfigFileCounts.get(configFile) - 1; - mConfigFileCounts.put(configFile, newCount); - boolean lastProcessWithViewerConfig = newCount == 0; - if (lastProcessWithViewerConfig) { - mViewerConfigFileTracer.trace(mDataSource, configFile); - } - } - } - - private static void writeViewerConfigGroup( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inGroupToken = pis.start(GROUPS); - final long outGroupToken = os.start(GROUPS); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) ID -> { - int id = pis.readInt(ID); - os.write(ID, id); - } - case (int) NAME -> { - String name = pis.readString(NAME); - os.write(NAME, name); - } - case (int) TAG -> { - String tag = pis.readString(TAG); - os.write(TAG, tag); - } - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inGroupToken); - os.end(outGroupToken); - } - - private static void writeViewerConfigMessage( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inMessageToken = pis.start(MESSAGES); - final long outMessagesToken = os.start(MESSAGES); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MESSAGE_ID -> os.write(MESSAGE_ID, - pis.readLong(MESSAGE_ID)); - case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); - case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); - case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); - case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inMessageToken); - os.end(outMessagesToken); - } + void disableProtoLogToLogcat(@NonNull String... groups); } diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e382ac1513e075e5b1a17d05b58921ed209a2c3b --- /dev/null +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.protolog; + +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.util.Log; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing + * system. Currently this service has the following roles: + * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. + * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog + * clients. This is for two reasons: firstly, because client processes might be frozen so might + * not response to the request to dump their viewer config when the trace is stopped; secondly, + * multiple processes might be running the same code with the same viewer config, this centralized + * service ensures we don't dump the same viewer config multiple times across processes. + *

+ * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to + * this service on initialization. + *

+ * This service is intended to run on the system server, such that it never gets frozen. + */ +@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) +public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationService.Stub + implements ProtoLogConfigurationService { + private static final String LOG_TAG = "ProtoLogConfigurationService"; + + private final ProtoLogDataSource mDataSource; + + /** + * Keeps track of how many of each viewer config file is currently registered. + * Use to keep track of which viewer config files are actively being used in tracing and might + * need to be dumped on flush. + */ + private final Map mConfigFileCounts = new HashMap<>(); + /** + * Keeps track of the viewer config file of each client if available. + */ + private final Map mClientConfigFiles = new HashMap<>(); + + /** + * Keeps track of all the protolog groups that have been registered by clients and are still + * being actively traced. + */ + private final Set mRegisteredGroups = new HashSet<>(); + /** + * Keeps track of all the clients that are actively tracing a given protolog group. + */ + private final Map> mGroupToClients = new HashMap<>(); + + /** + * Keeps track of whether or not a given group should be logged to logcat. + * True when logging to logcat, false otherwise. + */ + private final Map mLogGroupToLogcatStatus = new TreeMap<>(); + + /** + * Keeps track of all the tracing instance ids that are actively running for ProtoLog. + */ + private final Set mRunningInstances = new HashSet<>(); + + private final ViewerConfigFileTracer mViewerConfigFileTracer; + + public ProtoLogConfigurationServiceImpl() { + this(ProtoLogDataSource::new, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { + this(dataSourceBuilder, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ViewerConfigFileTracer tracer) { + this(ProtoLogDataSource::new, tracer); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl( + @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, + @NonNull ViewerConfigFileTracer tracer) { + mDataSource = dataSourceBuilder.build( + this::onTracingInstanceStart, + this::onTracingInstanceFlush, + this::onTracingInstanceStop + ); + + // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be + // receive the lifecycle callbacks of the datasource and write the viewer configs if and + // when required to the datasource. + Producer.init(InitArguments.DEFAULTS); + final var params = new DataSourceParams.Builder() + .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) + .build(); + mDataSource.register(params); + + mViewerConfigFileTracer = tracer; + } + + public static class RegisterClientArgs extends IRegisterClientArgs.Stub { + /** + * The viewer config file to be registered for this client ProtoLog process. + */ + @Nullable + private String mViewerConfigFile = null; + /** + * The list of all groups that this client protolog process supports and might trace. + */ + @NonNull + private String[] mGroups = new String[0]; + /** + * The default logcat status of the ProtoLog client. True is logging to logcat, false + * otherwise. The indices should match the indices in {@link mGroups}. + */ + @NonNull + private boolean[] mLogcatStatus = new boolean[0]; + + public record GroupConfig(@NonNull String group, boolean logToLogcat) {} + + /** + * Specify groups to register with this client that will be used for protologging in this + * process. + * @param groups to register with this client. + * @return self + */ + public RegisterClientArgs setGroups(GroupConfig... groups) { + mGroups = new String[groups.length]; + mLogcatStatus = new boolean[groups.length]; + + for (int i = 0; i < groups.length; i++) { + mGroups[i] = groups[i].group; + mLogcatStatus[i] = groups[i].logToLogcat; + } + + return this; + } + + /** + * Set the viewer config file that the logs in this process are using. + * @param viewerConfigFile The file path of the viewer config. + * @return self + */ + public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { + mViewerConfigFile = viewerConfigFile; + + return this; + } + + @Override + @NonNull + public String[] getGroups() { + return mGroups; + } + + @Override + @NonNull + public boolean[] getGroupsDefaultLogcatStatus() { + return mLogcatStatus; + } + + @Nullable + @Override + public String getViewerConfigFile() { + return mViewerConfigFile; + } + } + + @FunctionalInterface + public interface ViewerConfigFileTracer { + /** + * Write the viewer config data to the trace buffer. + * + * @param dataSource The target datasource to write the viewer config to. + * @param viewerConfigFilePath The path of the viewer config file which contains the data we + * want to write to the trace buffer. + * @throws FileNotFoundException if the viewerConfigFilePath is invalid. + */ + void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); + } + + @Override + public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) + throws RemoteException { + client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); + + final String viewerConfigFile = args.getViewerConfigFile(); + if (viewerConfigFile != null) { + registerViewerConfigFile(client, viewerConfigFile); + } + + registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); + } + + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new ProtoLogCommandHandler(this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + /** + * Get the list of groups clients have registered to the protolog service. + * @return The list of ProtoLog groups registered with this service. + */ + @Override + @NonNull + public String[] getGroups() { + return mRegisteredGroups.toArray(new String[0]); + } + + /** + * Enable logging target groups to logcat. + * @param groups we want to enable logging them to logcat for. + */ + @Override + public void enableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(true, groups); + } + + /** + * Disable logging target groups to logcat. + * @param groups we want to disable from being logged to logcat. + */ + @Override + public void disableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(false, groups); + } + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + @Override + public boolean isLoggingToLogcat(@NonNull String group) { + final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); + + if (isLoggingToLogcat == null) { + throw new RuntimeException( + "Trying to get logcat logging status of non-registered group " + group); + } + + return isLoggingToLogcat; + } + + private void registerViewerConfigFile( + @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { + final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); + mConfigFileCounts.put(viewerConfigFile, count + 1); + mClientConfigFiles.put(client, viewerConfigFile); + } + + private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, + @NonNull boolean[] logcatStatuses) throws RemoteException { + if (groups.length != logcatStatuses.length) { + throw new RuntimeException( + "Expected groups and logcatStatuses to have the same length, " + + "but groups has length " + groups.length + + " and logcatStatuses has length " + logcatStatuses.length); + } + + for (int i = 0; i < groups.length; i++) { + String group = groups[i]; + boolean logcatStatus = logcatStatuses[i]; + + mRegisteredGroups.add(group); + + mGroupToClients.putIfAbsent(group, new HashSet<>()); + mGroupToClients.get(group).add(client); + + if (!mLogGroupToLogcatStatus.containsKey(group)) { + mLogGroupToLogcatStatus.put(group, logcatStatus); + } + + boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); + if (requestedLogToLogcat != logcatStatus) { + client.toggleLogcat(requestedLogToLogcat, new String[] { group }); + } + } + } + + private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { + final var clientToGroups = new HashMap>(); + + for (String group : groups) { + final var clients = mGroupToClients.get(group); + + if (clients == null) { + // No clients associated to this group + Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group + + " with no registered clients."); + continue; + } + + for (IProtoLogClient client : clients) { + clientToGroups.putIfAbsent(client, new HashSet<>()); + clientToGroups.get(client).add(group); + } + } + + for (IProtoLogClient client : clientToGroups.keySet()) { + try { + client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); + } catch (RemoteException e) { + throw new RuntimeException( + "Failed to toggle logcat status for groups on client", e); + } + } + + for (String group : groups) { + mLogGroupToLogcatStatus.put(group, enabled); + } + } + + private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.add(instanceIdx); + } + + private void onTracingInstanceFlush() { + for (String fileName : mConfigFileCounts.keySet()) { + mViewerConfigFileTracer.trace(mDataSource, fileName); + } + } + + private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.remove(instanceIdx); + } + + private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, + @NonNull String viewerConfigFilePath) { + Utils.dumpViewerConfig(dataSource, () -> { + try { + return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "Failed to load viewer config file " + viewerConfigFilePath, e); + } + }); + } + + private void onClientBinderDeath(@NonNull IProtoLogClient client) { + // Dump the tracing config now if no other client is going to dump the same config file. + String configFile = mClientConfigFiles.get(client); + if (configFile != null) { + final var newCount = mConfigFileCounts.get(configFile) - 1; + mConfigFileCounts.put(configFile, newCount); + boolean lastProcessWithViewerConfig = newCount == 0; + if (lastProcessWithViewerConfig) { + mViewerConfigFileTracer.trace(mDataSource, configFile); + } + } + } + + private static void writeViewerConfigGroup( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inGroupToken = pis.start(GROUPS); + final long outGroupToken = os.start(GROUPS); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) ID -> { + int id = pis.readInt(ID); + os.write(ID, id); + } + case (int) NAME -> { + String name = pis.readString(NAME); + os.write(NAME, name); + } + case (int) TAG -> { + String tag = pis.readString(TAG); + os.write(TAG, tag); + } + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inGroupToken); + os.end(outGroupToken); + } + + private static void writeViewerConfigMessage( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inMessageToken = pis.start(MESSAGES); + final long outMessagesToken = os.start(MESSAGES); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) MESSAGE_ID -> os.write(MESSAGE_ID, + pis.readLong(MESSAGE_ID)); + case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); + case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); + case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); + case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inMessageToken); + os.end(outMessagesToken); + } +} diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index b73cacb7753983c0bbd840144719582e9ea46729..bdb33c4b151cf3e16d8652290038b37fe1f647e2 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -33,8 +33,6 @@ import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.TextView; -import com.android.text.flags.Flags; - /** * The item view for each item in the ListView-based MenuViews. */ @@ -283,10 +281,7 @@ public class ListMenuItemView extends LinearLayout private void insertIconView() { LayoutInflater inflater = getInflater(); - mIconView = (ImageView) inflater.inflate( - !Flags.fixMisalignedContextMenu() - ? com.android.internal.R.layout.list_menu_item_fixed_size_icon : - com.android.internal.R.layout.list_menu_item_icon, + mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon, this, false); addContentView(mIconView, 0); } diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java index c43a8c6ee7eed12a3a2c91d4ee7c88d2e94cd9fc..8e2536a423b8b7d30eb45653134d622545cc337c 100644 --- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java +++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java @@ -35,8 +35,6 @@ import android.widget.PopupWindow; import android.widget.PopupWindow.OnDismissListener; import android.widget.TextView; -import com.android.text.flags.Flags; - import java.util.Objects; /** @@ -122,8 +120,7 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On mMenu = menu; mOverflowOnly = overflowOnly; final LayoutInflater inflater = LayoutInflater.from(context); - mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, - Flags.fixMisalignedContextMenu() ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT); + mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT_MATERIAL); mPopupStyleAttr = popupStyleAttr; mPopupStyleRes = popupStyleRes; diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 90cb10aa62b2053152bb76f3ec90045d13e8f458..9a4ff8fc264fdf676634148f42c24055a39e58cc 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -290,7 +290,6 @@ cc_library_shared_for_libandroid_runtime { "libasync_safe", "libbinderthreadstateutils", "libdmabufinfo", - "libgif", "libgui_window_info_static", "libkernelconfigs", "libnativehelper_lazy", diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 70505a45fa1b917d1c7118ae7deaa8e2a6669573..b9c3bf73f11c4dbf77a7579710ec2cb20ce6a1a8 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -16,16 +16,16 @@ #define LOG_TAG "BLASTBufferQueue" -#include - #include #include -#include -#include - +#include #include #include #include +#include +#include +#include + #include "core_jni_helpers.h" namespace android { @@ -209,6 +209,12 @@ static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong reinterpret_cast(transaction)); } +static void nativeSetApplyToken(JNIEnv* env, jclass clazz, jlong ptr, jobject applyTokenObject) { + sp queue = reinterpret_cast(ptr); + sp token(ibinderForJavaObject(env, applyTokenObject)); + return queue->setApplyToken(std::move(token)); +} + static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off @@ -227,6 +233,7 @@ static const JNINativeMethod gMethods[] = { {"nativeSetTransactionHangCallback", "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V", (void*)nativeSetTransactionHangCallback}, + {"nativeSetApplyToken", "(JLandroid/os/IBinder;)V", (void*)nativeSetApplyToken}, // clang-format on }; diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp index a0228428e90e53b439765922c942e0b08e61b84a..b1221ee38db32642c71d993a8e13f2e102a734c0 100644 --- a/core/jni/android_hardware_UsbDeviceConnection.cpp +++ b/core/jni/android_hardware_UsbDeviceConnection.cpp @@ -190,18 +190,25 @@ android_hardware_UsbDeviceConnection_bulk_request(JNIEnv *env, jobject thiz, return -1; } - jbyte* bufferBytes = NULL; - if (buffer) { - bufferBytes = (jbyte*)env->GetPrimitiveArrayCritical(buffer, NULL); + bool is_dir_in = (endpoint & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN; + std::unique_ptr bufferBytes(new (std::nothrow) jbyte[length]); + if (!bufferBytes) { + jniThrowException(env, "java/lang/OutOfMemoryError", NULL); + return -1; } - jint result = usb_device_bulk_transfer(device, endpoint, bufferBytes + start, length, timeout); + if (!is_dir_in && buffer) { + env->GetByteArrayRegion(buffer, start, length, bufferBytes.get()); + } - if (bufferBytes) { - env->ReleasePrimitiveArrayCritical(buffer, bufferBytes, 0); + jint bytes_transferred = + usb_device_bulk_transfer(device, endpoint, bufferBytes.get(), length, timeout); + + if (bytes_transferred > 0 && is_dir_in) { + env->SetByteArrayRegion(buffer, start, bytes_transferred, bufferBytes.get()); } - return result; + return bytes_transferred; } static jobject diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index f2c70b5f41d40c5f82b193eef2a8b1ac67c00a1c..8003bb7d442b6df0537a05bc9e6cf750df14f484 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1747,9 +1747,9 @@ static const JNINativeMethod gBinderProxyMethods[] = { {"linkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, {"addFrozenStateChangeCallbackNative", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback}, + "(Landroid/os/IBinder$FrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback}, {"removeFrozenStateChangeCallbackNative", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback}, + "(Landroid/os/IBinder$FrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback}, {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, {"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension}, }; @@ -1774,7 +1774,7 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V"); gBinderProxyOffsets.mInvokeFrozenStateChangeCallback = GetStaticMethodIDOrDie(env, clazz, "invokeFrozenStateChangeCallback", - "(Landroid/os/IBinder$IFrozenStateChangeCallback;Landroid/os/" + "(Landroid/os/IBinder$FrozenStateChangeCallback;Landroid/os/" "IBinder;I)V"); gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h index 3b29e305e41006e2cacab7eda226c05b626b2d44..21b5b1308fcf9cb84380852eae8a2af46bd9187b 100644 --- a/core/jni/jni_wrappers.h +++ b/core/jni/jni_wrappers.h @@ -69,9 +69,47 @@ static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { return static_cast(res); } +// Inline variable that specifies the method binding format. +// The expected format is 'XX${method}XX', where ${method} represents the original method name. +// This variable is shared across all translation units. This is treated as a global variable as +// per C++ 17. +inline std::string jniMethodFormat; + +inline static void setJniMethodFormat(std::string value) { + jniMethodFormat = value; +} + +// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set. +// Has no effect otherwise +inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods, + int numMethods) { + if (jniMethodFormat.empty()) { + return gMethods; + } + // Make a copy of gMethods with reformatted method names. + JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods]; + LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods"); + + size_t methodNamePos = jniMethodFormat.find("${method}"); + LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos, + "Invalid jniMethodFormat: could not find '${method}' in pattern"); + + for (int i = 0; i < numMethods; i++) { + modifiedMethods[i] = gMethods[i]; + std::string modifiedName = jniMethodFormat; + modifiedName.replace(methodNamePos, 9, gMethods[i].name); + char* modifiedNameChars = new char[modifiedName.length() + 1]; + LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name"); + std::strcpy(modifiedNameChars, modifiedName.c_str()); + modifiedMethods[i].name = modifiedNameChars; + } + return modifiedMethods; +} + static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - int res = jniRegisterNativeMethods(env, className, gMethods, numMethods); + const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods); + int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); return res; } diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index cb7c226bfc6412a45578bbe89b02d0b204737584..606e829c41fa60596f6ed4d1c0164d036b42b22c 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -401,6 +401,7 @@ message SecureSettingsProto { optional SettingProto long_press_timeout = 35 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto key_press_timeout_ms = 96 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto key_press_delay_ms = 97 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto key_repeat_enabled = 102 [ (android.privacy).dest = DEST_AUTOMATIC ]; message ManagedProfile { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -735,5 +736,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 102; + // Next tag = 103; } diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index bb654f05b02df5945787708d112082b162d02e7d..d8365631c2707168b8fac4a6b3211fa46f4e26a0 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -62,6 +62,9 @@ message GraphicsStatsProto { // HWUI renders pipeline type: GL or Vulkan optional PipelineType pipeline = 8; + + // The UID of the app + optional int32 uid = 9; } message GraphicsStatsJankSummaryProto { diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto index 47c97b08666b1b0ad3e68c48d2d18aa20799f7f2..f477d32cd91501b345128c4d1e7435b29ca92e6f 100644 --- a/core/proto/android/widget/remoteviews.proto +++ b/core/proto/android/widget/remoteviews.proto @@ -299,6 +299,14 @@ message RemoteViewsProto { NightModeReflectionAction night_mode_reflection_action = 5; ReflectionAction reflection_action = 6; RemoveFromParentAction remove_from_parent_action = 7; + ResourceReflectionAction resource_reflection_action = 8; + SetCompoundButtonCheckedAction set_compound_button_checked_action = 9; + SetDrawableTintAction set_drawable_tint_action = 10; + SetEmptyViewAction set_empty_view_action = 11; + SetIntTagAction set_int_tag_action = 12; + SetRadioGroupCheckedAction set_radio_group_checked_action = 13; + SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14; + SetRippleDrawableColorAction set_ripple_drawable_color_action = 15; } } @@ -374,6 +382,52 @@ message RemoteViewsProto { message RemoveFromParentAction { optional string view_id = 1; } + + message ResourceReflectionAction { + optional string view_id = 1; + optional string method_name = 2; + optional int32 resource_type = 3; + optional string res_id = 4; + optional int32 parameter_type = 5; + } + + message SetCompoundButtonCheckedAction { + optional string view_id = 1; + optional bool checked = 2; + } + + message SetDrawableTintAction { + optional string view_id = 1; + optional bool target_background = 2; + optional int32 color_filter = 3; + optional int32 filter_mode = 4; + } + + message SetEmptyViewAction { + optional string view_id = 1; + optional string empty_view_id = 2; + } + + message SetIntTagAction { + optional string view_id = 1; + optional string key = 2; + optional int32 tag = 3; + } + + message SetRadioGroupCheckedAction { + optional string view_id = 1; + optional string checked_id = 2; + } + + message SetRemoteCollectionItemListAdapterAction { + optional string view_id = 1; + optional RemoteCollectionItems items = 2; + } + + message SetRippleDrawableColorAction { + optional string view_id = 1; + optional android.content.res.ColorStateListProto color_state_list = 2; + } } diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index e60e0b0079a903a48de3e86f39803cdb0379f013..db1c779d0087df56dd354d61055297aafd27bde6 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -122,7 +122,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" - android:maxLines="2" + android:layout_toStartOf="@id/button_open" + android:layout_marginEnd="8dp" android:background="@drawable/resolver_outlined_button_bg" style="?android:attr/borderlessButtonStyle" android:paddingHorizontal="16dp" @@ -136,7 +137,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:maxLines="2" android:paddingHorizontal="16dp" android:background="@drawable/resolver_button_bg" style="?android:attr/borderlessButtonStyle" diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index a1dea8236bd40d51bb37cd72bbd5410409faf25c..1cd21504193cce1b962db86de4f950f8c8807b86 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1746,8 +1746,7 @@ "’n App verberg die toestemmingversoek en jou antwoord kan dus nie geverifieer word nie." "Tik op \'n kenmerk om dit te begin gebruik:" "Kies kenmerke om saam met die toeganklikheidknoppie te gebruik" - - + "Kies kenmerke om saam met die volumesleutelskortpad te gebruik" "%s is afgeskakel" "Wysig kortpaaie" "Klaar" @@ -2011,6 +2010,7 @@ "Stel ’n skermslot" "Stel skermslot" "Stel ’n skermslot op dié toestel om jou privaat ruimte te gebruik" + "Stel ’n skermslot op dié toestel om privaat ruimte uit te vee" "Program is nie beskikbaar nie" "%1$s is nie op die oomblik beskikbaar nie." "%1$s is nie beskikbaar nie" diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index a7b6661a42e5307f49578edf1f8769e93575f84b..484afc3c344e9c28e1637712e83d31bef6537196 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -2010,6 +2010,7 @@ "ማያ ገጽ መቆለፊያን ያቀናብሩ" "ማያ ገጽ መቆለፊያውን ያቀናብሩ" "የግል ቦታዎን ለመጠቀም፣ በዚህ መሣሪያ ላይ ማያ ገጽ መቆለፊያን ያቀናብሩ" + "የግል ቦታን ለመሰረዝ በዚህ መሣሪያ ላይ ማያ ገፅ መቆለፊያ ያቀናብሩ" "መተግበሪያ አይገኝም" "%1$s አሁን አይገኝም።" "%1$s አይገኝም" diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 7f025760466bbd14b9b1897aa45a1a7d644b5f8e..75e93d1aa315a7bc6da2adfa8ea77d58e3b93662 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1750,8 +1750,7 @@ "تعذَّر التحقّق من ردّك بسبب حجب أحد التطبيقات طلب الحصول على الإذن." "انقر على ميزة لبدء استخدامها:" "اختيار الميزات التي تريد استخدامها مع زر أدوات تمكين الوصول" - - + "اختيار الميزات التي تريد استخدامها مع اختصار مفتاحَي مستوى الصوت" "تم إيقاف %s." "تعديل الاختصارات" "تم" @@ -2015,6 +2014,7 @@ "ضبط قفل شاشة" "ضبط قفل الشاشة" "لاستخدام مساحتك الخاصة، يجب ضبط قفل شاشة على هذا الجهاز" + "لحذف المساحة الخاصة، يجب ضبط قفل شاشة على هذا الجهاز" "التطبيق غير متاح" "تطبيق %1$s غير متاح الآن." "تطبيق %1$s غير متاح" diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index c5ad8bab39d0ac282185d45304b9e6d130527d03..e64c85e058d5cc9b8ec8862ffb3ab5e1d6e5f6f4 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -2010,6 +2010,7 @@ "এটা স্ক্ৰীন লক ছেট কৰক" "স্ক্ৰীন লক ছেট কৰা" "আপোনাৰ প্ৰাইভেট স্পে\'চ ব্যৱহাৰ কৰিবলৈ এই ডিভাইচটোত স্ক্ৰীন লক ছেট কৰক" + "প্ৰাইভেট স্পে’চ মচিবলৈ, এই ডিভাইচটোত এটা স্ক্ৰীন লক ছেট কৰক" "এপ্‌টো উপলব্ধ নহয়" "এই মুহূৰ্তত %1$s উপলব্ধ নহয়।" "%1$s উপলব্ধ নহয়" diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index e93a57ba76ccc7dc9ed2800688ed79317e75d599..25dc42904995f436c6666c8e4b982d5559cdb136 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1746,8 +1746,7 @@ "Bir tətbiq icazə sorğusunu gizlətdiyi üçün cavabı yoxlamaq mümkün deyil." "Funksiyanı istifadə etmək üçün onun üzərinə toxunun:" "Xüsusi imkanlar düyməsinin köməyilə işə salınacaq funksiyaları seçin" - - + "Səs səviyyəsi düyməsinin qısayolu ilə istifadə edəcəyiniz funksiyaları seçin" "%s deaktiv edilib" "Qısayolları redaktə edin" "Hazırdır" @@ -2011,6 +2010,7 @@ "Ekran kilidi ayarlayın" "Ekran kilidi ayarlayın" "Bu cihazda ekran kilidi ayarlamaqla şəxsi sahədən istifadə edin" + "Bu cihazda ekran kilidi ayarlamaqla şəxsi sahəni silin" "Tətbiq əlçatan deyil" "%1$s hazırda əlçatan deyil." "%1$s əlçatan deyil" diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 8f0f4b2259c8a27bd8c73eebe6201864e1bbfd13..7413703e2adab3ebd2322992528a713e8409fd33 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -2011,6 +2011,7 @@ "Podesite otključavanje ekrana" "Podesi otključavanje ekrana" "Da biste koristili privatni prostor, podesite otključavanje ekrana na ovom uređaju" + "Da biste izbrisali privatan prostor, podesite otključavanje ekrana na ovom uređaju" "Aplikacija nije dostupna" "Aplikacija %1$s trenutno nije dostupna." "%1$s – nije dostupno" diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 7a49bf348acfc4ea5298d71a628e300e3aa4e482..a5af4b9fbdfe0972f85106090ef1d88664fae806 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -2012,6 +2012,7 @@ "Наладзьце блакіроўку экрана" "Наладзіць блакіроўку экрана" "Каб выкарыстоўваць прыватную прастору, на прыладзе неабходна наладзіць блакіроўку экрана" + "Каб выдаліць прыватную прастору, на прыладзе неабходна наладзіць блакіроўку экрана" "Праграма недаступная" "Праграма \"%1$s\" цяпер недаступная." "Недаступна: %1$s" diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index b6fad9e9a848ef1f123f7ad4bab95c842a6c002a..93fdf3dc5207079dce8abdcfdb1cf5e830ccf6b2 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1746,8 +1746,7 @@ "Отговорът ви не може да бъде потвърден, тъй като приложение прикрива заявката за разрешение." "Докоснете дадена функция, за да започнете да я използвате:" "Избиране на функции, които да използвате с бутона за достъпност" - - + "Избиране на функции, които да използвате с прекия път чрез бутоните за силата на звука" "Изключихте %s" "Редактиране на преките пътища" "Готово" @@ -2011,6 +2010,7 @@ "Настройте заключване на екрана" "Заключване на екрана" "За да ползвате частното си пространство, настройте заключване на екрана" + "За да изтриете личното пространство, настройте заключване на екрана" "Приложението не е достъпно" "В момента няма достъп до %1$s." "%1$s не е налице" diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index c434fe91d11e726c24440c76aec679762eb8ad47..0871bfd244fe0ce9c9cd8a965eee10f930ebed37 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -992,7 +992,7 @@ "ভুল পিন কোড৷" "আনলক করতে, মেনু টিপুন তারপর ০ টিপুন৷" "জরুরী নম্বর" - "কোনো পরিষেবা নেই" + "কোনও পরিষেবা নেই" "স্ক্রীণ লক করা আছে৷" "আনলক করতে বা জরুরি কল করতে মেনু টিপুন৷" "আনলক করতে মেনু টিপুন৷" @@ -1746,8 +1746,7 @@ "কোনও অ্যাপ অনুমতির অনুরোধ আড়াল করছে তাই আপনার উত্তর যাচাই করা যাবে না।" "কোনও ফিচার ব্যবহার করা শুরু করতে, সেটিতে ট্যাপ করুন:" "অ্যাক্সেসিবিলিটি বোতামের সাহায্যে আপনি যেসব ফিচার ব্যবহার করতে চান সেগুলি বেছে নিন" - - + "ভলিউম কী শর্টকাটের সাহায্যে আপনি যেসব ফিচার ব্যবহার করতে চান সেগুলি বেছে নিন" "%s বন্ধ করে দেওয়া হয়েছে" "শর্টকাট এডিট করুন" "হয়ে গেছে" @@ -2011,6 +2010,7 @@ "\'স্ক্রিন লক\' সেট-আপ করুন" "\'স্ক্রিন লক\' ফিচার সেট করুন" "নিজের প্রাইভেট স্পেস ব্যবহার করতে এই ডিভাইসে \'স্ক্রিন লক\' সেট করুন" + "প্রাইভেট স্পেস মুছে দিতে, এই ডিভাইসে \'স্ক্রিন লক\' সেট করুন।" "অ্যাপ পাওয়া যাচ্ছে না" "এই মুহূর্তে %1$s অ্যাপ পাওয়া যাচ্ছে না।" "%1$s উপলভ্য নেই" diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 91c77018810d61dd026299f6c7664ce12b442d39..50f2221d8fdea835eb2341a66853ad87dbb39011 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -191,7 +191,7 @@ "Pokušali ste izbrisati previše sadržaja iz kategorije %s." "Pohrana tableta je puna. Izbrišite fajlove kako biste oslobodili prostor." "Prostor za gledanje je pun. Izbrišite neke fajlove da oslobodite prostor." - "Pohrana Android TV uređaja je puna. Izbrišite neke fajlove da oslobodite prostor." + "Pohrana na uređaju Android TV je puna. Izbrišite neke fajlove da oslobodite prostor." "Pohrana telefona je puna. Izbrišite fajlove kako biste oslobodili prostor." "{count,plural, =1{CA certifikat je instaliran}one{CA certifikati su instalirani}few{CA certifikati su instalirani}other{CA certifikati su instalirani}}" "Od nepoznate treće strane" @@ -1240,7 +1240,7 @@ "Koristi %1$s kao glavnu aplikaciju" "Snimanje slike" "Snimanje slike koristeći" - "Snimanje slike koristeći %1$s" + "Snimite sliku aplikacijom %1$s" "Snimanje slike" "Koristiti kao zadanu rezoluciju za ovu akciju." "Koristi drugu aplikaciju" @@ -1747,8 +1747,7 @@ "Aplikacija skriva zahtjev za odobrenje, pa se vaš odgovor ne može potvrditi." "Dodirnite funkciju da je počnete koristiti:" "Odaberite funkcije koje ćete koristiti s dugmetom Pristupačnost" - - + "Odabir funkcija za korištenje pomoću prečice tipki za jačinu zvuka" "Usluga %s je isključena" "Uredi prečice" "Gotovo" @@ -2012,6 +2011,7 @@ "Postavite zaključavanje ekrana" "Postavite zaključavanje ekrana" "Za upotrebu privatnog prostora postavite zaključavanje ekrana na uređaju" + "Da izbrišete privatni prostor, postavite zaključavanje ekrana na uređaju" "Aplikacija nije dostupna" "Aplikacija %1$s trenutno nije dostupna." "Nedostupno: %1$s" @@ -2065,7 +2065,7 @@ "Mape i navigacija" "Produktivnost" "Pristupačnost" - "Memorija uređaja" + "Pohrana na uređaju" "Otklanjanje grešaka putem USB-a" "sat" "minuta" diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index cbe9c392772600414101ab49077d465dbd62f989..5728aed8551a823c0a6409a27caf8ad2592caeab 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1747,8 +1747,7 @@ "Una aplicació està ocultant la sol·licitud de permís, de manera que la teva resposta no es pot verificar" "Toca una funció per començar a utilitzar-la:" "Tria les funcions que vols utilitzar amb el botó d\'accessibilitat" - - + "Tria les funcions que vols utilitzar amb la drecera de les tecles de volum" "%s s\'ha desactivat" "Edita les dreceres" "Fet" @@ -2012,6 +2011,7 @@ "Defineix un bloqueig de pantalla" "Defineix un bloqueig de pantalla" "Per utilitzar l\'espai privat, defineix un bloqueig de pantalla en aquest dispositiu" + "Per suprimir l\'espai privat, defineix un bloqueig de pantalla en aquest dispositiu." "L\'aplicació no està disponible" "Ara mateix, %1$s no està disponible." "%1$s no està disponible" diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 69e6483a03f4ab9018d431373da973cd2157b346..2fea7fba45dc0991b533b9e055fa5db8cf492f1f 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -2012,6 +2012,7 @@ "Nastavte si zámek obrazovky" "Nastavit zámek obrazovky" "Pokud chcete používat soukromý prostor, nastavte na tomto zařízení zámek obrazovky" + "Pokud chcete vymazat soukromý prostor, nastavte na tomto zařízení zámek obrazovky" "Aplikace není k dispozici" "Aplikace %1$s v tuto chvíli není k dispozici." "%1$s není k dispozici" diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index d66ebd90723720a429ad349da0222a7ac6f0b0e6..d0f1615e78e9b76876385a42719f80fc8c8d2679 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1746,8 +1746,7 @@ "En app skjuler anmodningen om tilladelse, så dit svar kan ikke verificeres." "Tryk på en funktion for at bruge den:" "Vælg, hvilke funktioner du vil bruge med knappen til hjælpefunktioner" - - + "Vælg de funktioner, du vil bruge med genvejen til lydstyrkeknapperne" "%s er blevet deaktiveret" "Rediger genveje" "Udfør" @@ -2011,6 +2010,7 @@ "Konfigurer en skærmlås" "Konfigurer skærmlås" "Konfigurer en skærmlås på enheden for at bruge dit private område" + "Konfigurer en skærmlås på enheden for at slette det private område" "Appen er ikke tilgængelig" "%1$s er ikke tilgængelig lige nu." "%1$s er ikke understøttet" diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 38797675867280015f5d21662ed73808c0d50940..da0e9bd2c83583820a7a91b0fab34148291b89b7 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1746,8 +1746,7 @@ "Die Berechtigungsanfrage wird durch eine andere App verdeckt. Daher kann deine Antwort nicht geprüft werden." "Zum Auswählen der gewünschten Funktion tippen:" "Funktionen auswählen, die du mit der Schaltfläche \"Bedienungshilfen\" verwenden möchtest" - - + "Funktionen für den Kurzbefehl für die Lautstärketasten auswählen" "%s wurde deaktiviert" "Kurzbefehle bearbeiten" "Fertig" @@ -2011,6 +2010,7 @@ "Displaysperre einrichten" "Displaysperre einrichten" "Richte zur Nutzung des vertraulichen Profils auf dem Gerät die Displaysperre ein" + "Um das vertrauliche Profil zu löschen, richte auf dem Gerät eine Displaysperre ein" "App ist nicht verfügbar" "%1$s ist derzeit nicht verfügbar." "%1$s nicht verfügbar" diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 28a5e55fc284acf4fdffc8bf9952bc7c6b072679..0556a0820dea62988138f44b5e2af5711aef3e70 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1746,8 +1746,7 @@ "Μια εφαρμογή αποκρύπτει το αίτημα άδειας, με αποτέλεσμα να μην είναι δυνατή η επαλήθευση της απάντησής σας." "Πατήστε μια λειτουργία για να ξεκινήσετε να τη χρησιμοποιείτε:" "Επιλέξτε τις λειτουργίες που θέλετε να χρησιμοποιείτε με το κουμπί προσβασιμότητας." - - + "Επιλέξτε τις λειτουργίες που θέλετε να χρησιμοποιείτε με τη συντόμευση κουμπιών έντασης ήχου" "Η υπηρεσία %s έχει απενεργοποιηθεί." "Επεξεργασία συντομεύσεων" "Τέλος" @@ -2011,6 +2010,7 @@ "Ρυθμίστε ένα κλείδωμα οθόνης" "Ρύθμιση κλειδώματος οθόνης" "Ορίστε κλείδωμα οθόνης" + "Για να διαγράψετε τον ιδιωτικό χώρο, ορίστε ένα κλείδωμα οθόνης σε αυτή τη συσκευή" "Η εφαρμογή δεν είναι διαθέσιμη" "Η εφαρμογή %1$s δεν είναι διαθέσιμη αυτήν τη στιγμή." "%1$s δεν διατίθεται" diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 70d86e7de732891382ff19190a91994e096e845f..086835c1cd5abc0359bc19ca306dc57795e80597 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1746,8 +1746,7 @@ "An app is obscuring the permission request so your response cannot be verified." "Tap a feature to start using it:" "Choose features to use with the Accessibility button" - - + "Choose features to use with the volume keys shortcut" "%s has been turned off" "Edit shortcuts" "Done" @@ -2011,6 +2010,7 @@ "Set a screen lock" "Set screen lock" "To use your private space, set a screen lock on this device" + "To delete private space, set a screen lock on this device" "App is not available" "%1$s is not available right now." "%1$s unavailable" diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index dad63334dc6cbfd85d049ad40ea64b8de24f9460..88a83b595520552a1f06b0a717c79b51adfb395e 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -2010,6 +2010,7 @@ "Set a screen lock" "Set screen lock" "To use your private space, set a screen lock on this device" + "To delete private space, set a screen lock on this device" "App is not available" "%1$s is not available right now." "%1$s unavailable" diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index d3f0c6410e24900bce4ef52009e24b5625ac792c..ef399b715631a475865cc6173d3537cdc30d3ef3 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1746,8 +1746,7 @@ "An app is obscuring the permission request so your response cannot be verified." "Tap a feature to start using it:" "Choose features to use with the Accessibility button" - - + "Choose features to use with the volume keys shortcut" "%s has been turned off" "Edit shortcuts" "Done" @@ -2011,6 +2010,7 @@ "Set a screen lock" "Set screen lock" "To use your private space, set a screen lock on this device" + "To delete private space, set a screen lock on this device" "App is not available" "%1$s is not available right now." "%1$s unavailable" diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 7c3be159a01fc15fa4fcb9aef7a193d9fa99e0a9..94ddc434926f188ae8c08ac3f81af71b0cdd095c 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1746,8 +1746,7 @@ "An app is obscuring the permission request so your response cannot be verified." "Tap a feature to start using it:" "Choose features to use with the Accessibility button" - - + "Choose features to use with the volume keys shortcut" "%s has been turned off" "Edit shortcuts" "Done" @@ -2011,6 +2010,7 @@ "Set a screen lock" "Set screen lock" "To use your private space, set a screen lock on this device" + "To delete private space, set a screen lock on this device" "App is not available" "%1$s is not available right now." "%1$s unavailable" diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 0bcbed786d446a3bfc4c83dbddad109d98f90c61..a0a891eefac2f5e21511a1e35691f5a8f2cd531b 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -2010,6 +2010,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎Set a screen lock‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎Set screen lock‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎To use your private space, set a screen lock on this device‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎To delete private space, set a screen lock on this device‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎App is not available‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ is not available right now.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ unavailable‎‏‎‎‏‎" diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 773edd3e5ba7a38d90d810fb61e03d29b752b3bf..cbb30fd707689fb0a0198483788c5448bfb42e3c 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1747,8 +1747,7 @@ "Una app está cubriendo la solicitud de permiso, por lo que no se puede verificar tu respuesta." "Presiona una función para comenzar a usarla:" "Selecciona las funciones a utilizar con el botón de accesibilidad" - - + "Selecciona las funciones que usarás con la combinación de teclas de volumen" "Se desactivó %s" "Editar accesos directos" "Listo" @@ -2012,6 +2011,7 @@ "Configurar bloqueo de pantalla" "Configurar bloqueo de pantalla" "Para usar tu espacio privado, configura un bloqueo de pantalla" + "Para borrar el espacio privado, configura un bloqueo de pantalla." "La app no está disponible" "%1$s no está disponible en este momento." "%1$s no disponible" diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index e50d5faea41b48c91b1a26346cf1bf0a73d6f67d..23024f097dec3fe15ed0607f225796791b39bc15 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -2011,6 +2011,7 @@ "Define un bloqueo de pantalla" "Establecer bloqueo de pantalla" "Para usar el espacio privado, define un bloqueo de pantalla" + "Para eliminar el espacio privado, define un método de desbloqueo de pantalla en este dispositivo" "La aplicación no está disponible" "En estos momentos, %1$s no está disponible." "%1$s no disponible" diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index f313fb29b92a200c94d915a3fef7efcba4aceac8..326b3db247c8811a917f8015afd0f20cd678f578 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1746,8 +1746,7 @@ "Rakendus varjab loataotlust, nii et teie vastust ei saa kinnitada." "Puudutage funktsiooni, et selle kasutamist alustada." "Valige funktsioonid, mida juurdepääsetavuse nupuga kasutada" - - + "Valige helitugevuse nuppude otsetee funktsioonid" "%s on välja lülitatud" "Muuda otseteid" "Valmis" @@ -2011,6 +2010,7 @@ "Seadistage ekraanilukk" "Seadistage ekraanilukk" "Seadistage oma privaatse ruumi jaoks seadmele ekraanilukk" + "Privaatse ruumi kustutamiseks määrake selles seadmes ekraanilukk" "Rakendus ei ole saadaval" "%1$s ei ole praegu saadaval." "%1$s ei ole saadaval" diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index b0775d0796a9239d4f78277966702eb57c4948f2..d092f46ab6c249ec3bf1737ed997a8a2c1b27c92 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -1746,8 +1746,7 @@ "Aplikazio bat baimen-eskaera oztopatzen ari da eta, ondorioz, ezin da egiaztatu erantzuna." "Eginbide bat erabiltzen hasteko, saka ezazu:" "Aukeratu zein eginbide erabili nahi duzun Erabilerraztasuna botoiarekin" - - + "Aukeratu zein eginbide erabili nahi duzun bolumen-botoien lasterbidearekin" "Desaktibatu da %s" "Editatu lasterbideak" "Eginda" @@ -2011,6 +2010,7 @@ "Ezarri pantailaren blokeoa" "Ezarri pantailaren blokeoa" "Eremu pribatua erabiltzeko, ezarri pantailaren blokeoa gailuan" + "Eremu pribatua ezabatzeko, ezarri pantaila blokeatzeko aukera bat gailuan" "Aplikazioa ez dago erabilgarri" "%1$s ez dago erabilgarri une honetan." "%1$s ez dago erabilgarri" diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 0c46ed93a60202834aca61403bd0ea6644fb2183..e37106d3b27c5d43e76c884cae03abbe27b39af6 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -2010,6 +2010,7 @@ "قفل صفحه تنظیم کنید" "تنظیم قفل صفحه" "برای استفاده از فضای خصوصی، قفل صفحه تنظیم کنید" + "برای حذف کردن فضای خصوصی، قفل صفحه در این دستگاه تنظیم کنید" "برنامه در دسترس نیست" "%1$s درحال‌حاضر در دسترس نیست." "%1$s دردسترس نیست" diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index dec5b64b9592588413e47acfe8169f2bc4ca5f23..672e5080596758d30195db0cebe70a084f19e86b 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1746,8 +1746,7 @@ "Sovellus peittää lupapyynnön, joten vastaustasi ei voi vahvistaa." "Aloita ominaisuuden käyttö napauttamalla sitä:" "Valitse ominaisuudet, joita käytetään esteettömyyspainikkeella" - - + "Valitse ominaisuudet, joita käytetään äänenvoimakkuuspikanäppäimillä" "%s on laitettu pois päältä" "Muokkaa pikakuvakkeita" "Valmis" @@ -2011,6 +2010,7 @@ "Näytön lukituksen asettaminen" "Aseta näytön lukitus" "Edellyttää näytön lukitusta" + "Jos haluat poistaa yksityisen tilan, aseta laitteelle näytön lukitus" "Sovellus ei ole käytettävissä" "%1$s ei ole nyt käytettävissä." "%1$s ei käytettävissä" diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 7cb9e06c287a142f3dd9af31cc33c45479db1f0b..bc11e547bb821eabed0c6004388e32a372c1f428 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1747,8 +1747,7 @@ "Une appli masque la demande d\'autorisation de sorte que votre réponse ne peut pas être vérifiée." "Toucher une fonctionnalité pour commencer à l\'utiliser :" "Choisir les fonctionnalités à utiliser à l\'aide du bouton d\'accessibilité" - - + "Choisir les fonctionnalités à utiliser avec le raccourci des touches de volume" "%s a été désactivé" "Modifier les raccourcis" "OK" @@ -2012,6 +2011,7 @@ "Config. Verrouillage d\'écran" "Config. Verrouillage d\'écran" "Configurez verrouillage de l\'écran pour utiliser Espace privé" + "Réglez le verrouillage de l\'écran pour supprimer l\'espace privé" "L\'appli n\'est pas accessible" "%1$s n\'est pas accessible pour le moment." "%1$s non accessible" diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index ca3998c6f43c61f650ca12ff5b245f29dc017780..03cf4634bad2520d7e2135c5a164d3e86573e803 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1747,8 +1747,7 @@ "Une application masque la demande d\'autorisation. Votre réponse ne peut donc pas être vérifiée." "Appuyez sur une fonctionnalité pour commencer à l\'utiliser :" "Choisir les fonctionnalités à utiliser avec le bouton Accessibilité" - - + "Choisir les fonctionnalités à utiliser avec le raccourci des boutons de volume" "Le service %s a été désactivé" "Modifier les raccourcis" "OK" @@ -2012,6 +2011,7 @@ "Activer verrouillage écran" "Activer verrouillage écran" "Pour utiliser votre espace privé, activez le verrouillage de l\'écran sur cet appareil" + "Pour supprimer un espace privé, définissez un verrouillage de l\'écran sur cet appareil." "Application non disponible" "%1$s n\'est pas disponible pour le moment." "%1$s indisponible" diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index e07218cd955547e943cc434485ed63df3931fda9..430d64918470629f020c92b62c4d217dcdd3ea56 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1746,8 +1746,7 @@ "Hai unha aplicación que está ocultando a solicitude de permiso, polo que non se pode verificar a túa resposta." "Tocar unha función para comezar a utilizala:" "Escoller as funcións que queres utilizar co botón Accesibilidade" - - + "Escolle as funcións que queres utilizar co atallo das teclas de volume" "%s: desactivouse" "Editar atallos" "Feito" @@ -1938,7 +1937,7 @@ "Noite da semana" "Fin de semana" "Evento" - "Durmindo" + "Mentres durmo" "Xestionada por %1$s" "Activada" "Desactivada" @@ -2011,6 +2010,7 @@ "Define un bloqueo de pantalla" "Define un bloqueo de pantalla" "Para usar o espazo privado, define un bloqueo de pantalla" + "Para eliminar o espazo privado, define un bloqueo de pantalla neste dispositivo" "A aplicación non está dispoñible" "A aplicación %1$s non está dispoñible neste momento." "%1$s non está dispoñible" diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 2ac70ab79c3bb9e3aaca07f1028089215febf814..14b512eb6d310d88f3694034c525b09ede1d0cad 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -2010,6 +2010,7 @@ "સ્ક્રીન લૉક સેટ કરો" "સ્ક્રીન લૉક સેટ કરો" "તમારી ખાનગી સ્પેસનો ઉપયોગ કરવા, આ ડિવાઇસ પર સ્ક્રીન લૉક સેટ કરો" + "ખાનગી સ્પેસનો ડિલીટ કરવા, આ ડિવાઇસ પર સ્ક્રીન લૉક સેટ કરો" "ઍપ ઉપલબ્ધ નથી" "%1$s હાલમાં ઉપલબ્ધ નથી." "%1$s ઉપલબ્ધ નથી" diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 5f1bb9a74d2787ffaffcffe512937d5a17d08b87..0386387fc134a5fbe5479216bdcd720c8c7899aa 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -2010,6 +2010,7 @@ "स्क्रीन लॉक सेट करें" "स्क्रीन लॉक सेट करें" "प्राइवेट स्पेस के लिए, इस डिवाइस पर स्क्रीन लॉक सेट करें" + "प्राइवेट स्पेस मिटाने के लिए, इस डिवाइस पर स्क्रीन लॉक सेट करें" "ऐप्लिकेशन उपलब्ध नहीं है" "%1$s इस समय उपलब्ध नहीं है." "%1$s उपलब्ध नहीं है" diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 2596de21fadd135857c714f1002a1297e2d1aaa5..54dc55de7991286398de2d85dd792b2459f77e93 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1747,8 +1747,7 @@ "Aplikacija prekriva upit za dopuštenje pa se vaš odgovor ne može potvrditi." "Dodirnite značajku da biste je počeli koristiti:" "Odabir značajki za upotrebu pomoću gumba za Pristupačnost" - - + "Odabir značajki za upotrebu pomoću prečaca tipki za glasnoću" "Usluga %s je isključena" "Uredite prečace" "Gotovo" @@ -2012,6 +2011,7 @@ "Postavite zaključavanje zaslona" "Postavi zaključavanje zaslona" "Da biste upotrebljavali privatni prostor, postavite zaključavanje zaslona na ovom uređaju" + "Za upotrebu privatnog prostora postavite zaključavanje zaslona." "Aplikacija nije dostupna" "Aplikacija %1$s trenutačno nije dostupna." "%1$s – nije dostupno" diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 09af5060364302763635a4de8eaddda058fa73b0..179de2009dc53b1156235e893a73a73cd961c109 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1746,8 +1746,7 @@ "Az egyik alkalmazás eltakarja az engedélykérelmet, így az Ön válasza nem ellenőrizhető." "Koppintson valamelyik funkcióra a használatához:" "Kiválaszthatja a Kisegítő lehetőségek gombbal használni kívánt funkciókat" - - + "Kiválaszthatja a hangerőszabályzó gombokkal használni kívánt funkciókat" "%s kikapcsolva" "Gyorsparancsszerkesztés" "Kész" @@ -2011,6 +2010,7 @@ "Állítson be képernyőzárat" "Képernyőzár beállítása" "A privát terület használatához állítson be képernyőzárat" + "A privát terület törléséhez állítson be képernyőzárat ezen az eszközön." "Az alkalmazás nem hozzáférhető" "A(z) %1$s jelenleg nem hozzáférhető." "A(z) %1$s nem áll rendelkezése" diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index a28f413b9572df35a76cf75128dc5590cc903aa8..312a105285e174ca63353062881cd3bfe722f685 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1746,8 +1746,7 @@ "Հավելվածը թաքցնում է թույլտվության հայտը, ուստի ձեր պատասխանը հնարավոր չէ ստուգել։" "Ընտրեք՝ որ գործառույթն օգտագործել" "Ընտրեք գործառույթները, որոնք կբացվեն «Հատուկ գործառույթներ» կոճակի միջոցով" - - + "Ընտրեք գործառույթները, որոնք պետք է բացվեն ձայնի կարգավորման կոճակների միջոցով" "%s ծառայությունն անջատված է" "Փոփոխել դյուրանցումները" "Պատրաստ է" @@ -1938,7 +1937,7 @@ "Աշխատանքային օր" "Շաբաթ-կիրակի" "Միջոցառում" - "Քնի ժամանակ" + "Քնի ժամ" "Կառավարվում է %1$s հավելվածի կողմից" "Միացված է" "Անջատված է" @@ -2011,6 +2010,7 @@ "Կարգավորեք էկրանի կողպումը" "Կարգավորել էկրանի կողպումը" "Մասնավոր տարածքն օգտագործելու համար այս սարքում կարգավորեք էկրանի կողպումը" + "Մասնավոր տարածքը ջնջելու համար սահմանեք էկրանի կողպում այս սարքում" "Հավելվածը հասանելի չէ" "%1$s հավելվածն այս պահին հասանելի չէ։" "%1$s՝ անհասանելի է" diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 76c89eaa2ddf4f1903fe0103af202810661b0c17..e830d6d10e0e3d8eab2feda69d1afb66261db42d 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1746,8 +1746,7 @@ "Aplikasi menghalangi permintaan izin sehingga respons Anda tidak dapat diverifikasi." "Ketuk fitur untuk mulai menggunakannya:" "Pilih fitur yang akan digunakan dengan tombol aksesibilitas" - - + "Pilih fitur yang akan digunakan dengan pintasan tombol volume" "%s telah dinonaktifkan" "Edit pintasan" "Selesai" @@ -2011,6 +2010,7 @@ "Setel kunci layar" "Setel kunci layar" "Untuk menggunakan ruang privasi, setel kunci layar di perangkat ini" + "Untuk menghapus ruang privasi, setel kunci layar di perangkat ini" "Aplikasi tidak tersedia" "%1$s tidak tersedia saat ini." "%1$s tidak tersedia" diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 057b830d4fb70783d0bb98b2d2c2d738115475c9..307fb529b903d879201a4df1cb7cf93629de4384 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -2010,6 +2010,7 @@ "Stilltu skjálás" "Stilla skjálás" "Stilltu skjálás í tækinu til að nota leynirými" + "Stilltu skjálás í tækinu til að eyða leynirými" "Forrit er ekki tiltækt" "%1$s er ekki tiltækt núna." "%1$s ekki í boði" diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 29533d50eac8be2d202068ce972469610ff2ac62..08ea1f1adee459aedc77184917430c01c72331b6 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1747,8 +1747,7 @@ "Un\'app nasconde la tua richiesta di autorizzazione, per cui non abbiamo potuto verificare la tua risposta." "Tocca una funzionalità per iniziare a usarla:" "Scegli le funzionalità da usare con il pulsante Accessibilità" - - + "Scegli le funzionalità da usare con la scorciatoia tasti del volume" "Il servizio %s è stato disattivato" "Modifica scorciatoie" "Fine" @@ -2012,6 +2011,7 @@ "Imposta un blocco schermo" "Imposta il blocco schermo" "Per utilizzare il tuo spazio privato, imposta un blocco schermo sul dispositivo" + "Per eliminare lo spazio privato, imposta un blocco schermo sul dispositivo" "L\'app non è disponibile" "L\'app %1$s non è al momento disponibile." "%1$s non disponibile" diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 52b6173745b51577b351d22a1ecc833779f48c15..1c1918cac58b732ff45b47309b193be38fe02576 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1407,7 +1407,7 @@ "המכשיר זיהה התקן אודיו אנלוגי" "ההתקן שחיברת לא תואם לטלפון הזה. יש להקיש לקבלת מידע נוסף." "‏ניפוי באגים ב-USB מחובר" - "‏לכיבוי של ניפוי הבאגים ב-USB, יש להקיש" + "‏יש להקיש כדי לכבות את ניפוי הבאגים ב-USB" "‏יש ללחוץ על ההתראה כדי להשבית ניפוי באגים ב-USB." "ניפוי הבאגים האלחוטי מחובר" "יש להקיש כדי להשבית ניפוי באגים אלחוטי" @@ -1747,8 +1747,7 @@ "אפליקציה מסתירה את בקשת ההרשאה כך שלא ניתן לאמת את התשובה שלך." "יש להקיש על תכונה כדי להתחיל להשתמש בה:" "בחירת תכונה לשימוש עם לחצן הנגישות" - - + "בחירת תכונות לשימוש עם קיצור דרך באמצעות מקשי עוצמת הקול" "שירות %s כבוי" "עריכת קיצורי הדרך" "סיום" @@ -2012,6 +2011,7 @@ "הגדרת נעילת מסך" "הגדרה של נעילת מסך" "כדי להשתמש במרחב הפרטי יש להגדיר נעילת מסך במכשיר" + "כדי למחוק את המרחב הפרטי, צריך להגדיר נעילת מסך במכשיר הזה." "האפליקציה לא זמינה" "האפליקציה %1$s לא זמינה בשלב זה." "%1$s לא זמינה" diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 0705b36d9450d29dd9dc9bf5e62dd8ac0d2b84f0..36d956c3bdd05b6d7f0f473fb16546f484711193 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1936,8 +1936,8 @@ "ダウンタイム" "平日の夜" "週末" - "予定" - "睡眠中" + "予定モード" + "おやすみモード" "%1$s によって管理されています" "ON" "OFF" @@ -2010,6 +2010,7 @@ "画面ロックの設定" "画面ロックを設定" "プライベート スペースには画面ロックの設定が必要です" + "プライベート スペースを削除するには、このデバイスに画面ロックを設定してください" "アプリの利用不可" "現在 %1$s はご利用になれません。" "%1$sは利用できません" diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index bac23337e0d183bebdb6da693695407505b2e36b..9a88e545b0c6a8b963ebf0bb11bc1eda917cac67 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -2010,6 +2010,7 @@ "ეკრანის დაბლოკვის დაყენება" "ეკრანის დაბლოკვის დაყენება" "კერძო სივრცის გამოსაყენებლად დააყენეთ ამ მოწყობილობაზე ეკრანის დაბლოკვა" + "კერძო სივრცის წასაშლელად დააყენეთ ეკრანის დაბლოკვა ამ მოწყობილობაზე" "აპი მიუწვდომელია" "%1$s ამჟამად მიუწვდომელია." "%1$s მიუწვდომელია" diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index d9ee240ef48a65edfb21583a03586a6dff928b17..0997b4b972de1a72dd78a5ce056a2441f8a09031 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1746,8 +1746,7 @@ "Қолданба рұқсат сұрауын жасырып тұрғандықтан, жауабыңыз расталмайды." "Функцияны пайдалана бастау үшін түртіңіз:" "\"Арнайы мүмкіндіктер\" түймесімен қолданылатын функцияларды таңдаңыз" - - + "Дыбыс деңгейі пернелері тіркесімімен қолданылатын функцияларды таңдаңыз" "%s қызметі өшірулі." "Жылдам пәрмендерді өзгерту" "Дайын" @@ -2011,6 +2010,7 @@ "Экран құлпын орнатыңыз" "Экран құлпын орнату" "Құпия кеңістігіңізді қолдану үшін осы құрылғыда экран құлпын орнатыңыз." + "Құпия кеңістікті жою үшін құрылғыға экран құлпын орнатыңыз." "Қолданба қолжетімді емес" "%1$s қазір қолжетімді емес." "%1$s қолжетімсіз" diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 1f931c6644e3d7c1d9dad7afc5665243cb2866c5..696bd79509ac6dd47886b4d98142546dc067c487 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1216,7 +1216,7 @@ "{rating,plural, =1{ផ្កាយមួយ​ក្នុងចំណោមផ្កាយ {max}}other{ផ្កាយ # ក្នុងចំណោមផ្កាយ {max}}}" "កំពុងដំណើរការ" "បញ្ចប់​សកម្មភាព​ដោយ​ប្រើ" - "បញ្ចប់​សកម្មភាព​ដោយ​ប្រើ​ %1$s" + "បញ្ចប់​សកម្មភាព​ដោយ​ប្រើ​ %%1$s" "បញ្ចប់សកម្មភាព" "បើក​ជា​មួយ" "បើក​ជាមួយ %1$s" @@ -2010,6 +2010,7 @@ "កំណត់​ការចាក់​សោអេក្រង់" "កំណត់​ការចាក់​សោ​អេក្រង់" "ដើម្បីប្រើលំហឯកជនរបស់អ្នក សូមកំណត់ការចាក់សោអេក្រង់នៅលើឧបករណ៍នេះ" + "ដើម្បីលុបលំហឯកជនរបស់អ្នក សូមកំណត់ការចាក់សោអេក្រង់នៅលើឧបករណ៍នេះ" "មិនអាច​ប្រើ​កម្មវិធី​នេះបានទេ" "មិនអាច​ប្រើ %1$s នៅពេល​នេះ​បានទេ​។" "មិនអាចប្រើ %1$s បានទេ" diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 941a1b2572ce341fc040fa8787b3192783bc6866..126d55eec3e2aca86b9c3d23e865ea187fc4454c 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -2010,6 +2010,7 @@ "ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸೆಟ್ ಮಾಡಿ" "ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸೆಟ್ ಮಾಡಿ" "ನಿಮ್ಮ ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್ ಅನ್ನು ಬಳಸಲು, ಈ ಸಾಧನದಲ್ಲಿ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸೆಟ್ ಮಾಡಿ" + "ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್ ಅನ್ನು ಅಳಿಸಲು ಈ ಸಾಧನದಲ್ಲಿ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸೆಟ್ ಮಾಡಿ" "ಆ್ಯಪ್ ಲಭ್ಯವಿಲ್ಲ" "%1$s ಇದೀಗ ಲಭ್ಯವಿಲ್ಲ." "%1$s ಲಭ್ಯವಿಲ್ಲ" diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 0e47e59c35f958407c813e17179d2533cd8d0a74..5f0e1f9760e06345bfe389efce6247a79677e46f 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1746,8 +1746,7 @@ "앱에서 권한 요청을 가려서 응답을 확인할 수 없습니다." "기능을 사용하려면 탭하세요" "접근성 버튼으로 사용할 기능 선택" - - + "볼륨 키 바로가기로 사용할 기능 선택" "%s이(가) 사용 중지됨" "단축키 수정" "완료" @@ -2011,6 +2010,7 @@ "화면 잠금 설정" "화면 잠금 설정" "비공개 스페이스를 사용하려면 이 기기에 화면 잠금을 설정하세요" + "비공개 스페이스를 삭제하려면 이 기기에 화면 잠금을 설정하세요" "앱을 사용할 수 없습니다" "현재 %1$s 앱을 사용할 수 없습니다." "%1$s 사용할 수 없음" diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 43fd6a78cc73b8f1c0d8ab7141d73ddc74fe38cd..514b9cfb380f797e1ae09b8f4b49bb8fd0838792 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -2010,6 +2010,7 @@ "Экран кулпусун коюп алыңыз" "Экран кулпусун коюу" "Жеке мейкиндикти колдонуу үчүн бул түзмөктүн экранын кулпулаңыз" + "Жеке мейкиндикти өчүрүү үчүн бул түзмөктө экран кулпусун коюңуз" "Колдонмо учурда жеткиликсиз" "%1$s учурда жеткиликсиз" "%1$s жеткиликсиз" diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index dc15e9afc0cf9a214df349f7a1cda240706d2621..fb677cf1e44feccd6a1fa61e7355339c1561674b 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -2010,6 +2010,7 @@ "ຕັ້ງການລັອກໜ້າຈໍ" "ຕັ້ງການລັອກໜ້າຈໍ" "ເພື່ອໃຊ້ພື້ນທີ່ສ່ວນບຸກຄົນ, ໃຫ້ຕັ້ງລັອກໜ້າຈໍຢູ່ອຸປະກອນນີ້" + "ເພື່ອລຶບພື້ນທີ່ສ່ວນບຸກຄົນ, ໃຫ້ຕັ້ງການລັອກໜ້າຈໍຢູ່ອຸປະກອນນີ້" "ແອັບບໍ່ສາມາດໃຊ້ໄດ້" "%1$s ບໍ່ສາມາດໃຊ້ໄດ້ໃນຕອນນີ້." "ບໍ່ສາມາດໃຊ້ %1$s ໄດ້" diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 8eace89c0a6b6fc9501ae0f0387e76242dd89654..e617a4cfbfc623e4978e1cfe7a602646ecd0fd30 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1748,8 +1748,7 @@ "Programa užstoja leidimo užklausą, todėl negalima patvirtinti jūsų atsakymo." "Norėdami naudoti funkciją, palieskite ją:" "Funkcijų, kurioms bus naudojamas pritaikomumo mygtukas, pasirinkimas" - - + "Funkcijų, kurioms bus naudojami garsumo spartieji klavišai, pasirinkimas" "Paslauga „%s“ išjungta" "Redaguoti sparčiuosius klavišus" "Atlikta" @@ -2013,6 +2012,7 @@ "Ekrano užrako nustatymas" "Nustatykite ekrano užraktą" "Jei norite naudoti privačią erdvę, nustatykite ekrano užraktą šiame įrenginyje" + "Jei norite ištrinti privačią erdvę, nustatykite ekrano užraktą šiame įrenginyje" "Programa nepasiekiama." "Programa „%1$s“ šiuo metu nepasiekiama." "„%1$s“ nepasiekiama" diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 01fb9da42198feac46007713e1329929e4ad1687..b04dd4521a3179dd019685fc170bcbfad105877e 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1747,8 +1747,7 @@ "Kāda lietotne padara atļaujas pieprasījumu nesaprotamu, tāpēc nevar verificēt jūsu atbildi." "Pieskarieties funkcijai, lai sāktu to izmantot" "Izvēlieties funkcijas, ko izmantot ar pieejamības pogu" - - + "Izvēlieties funkcijas, kuras piešķirt skaļuma pogu saīsnei" "Pakalpojums %s ir izslēgts." "Rediģēt īsinājumtaustiņus" "Gatavs" @@ -2012,6 +2011,7 @@ "Iestatiet ekrāna bloķēšanu" "Iestatīt ekrāna bloķēšanu" "Lai izmantotu privāto telpu, iestatiet ekrāna bloķēšanu." + "Lai dzēstu privāto telpu, iestatiet ekrāna bloķēšanu šajā ierīcē." "Lietotne nav pieejama" "Lietotne %1$s pašlaik nav pieejama." "%1$s nav pieejams" diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index dbc4b757c4a2ee126253b010e9edeab6ac2ccc3b..b0d6351f5667cb78894c77667f5b95983a425686 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1746,8 +1746,7 @@ "Апликација го прикрива барањето за дозвола, па вашиот одговор не може да се потврди." "Допрете на функција за да почнете да ја користите:" "Изберете ги функциите што ќе ги користите со копчето за пристапност" - - + "Изберете ги функциите што ќе ги користите со кратенката за копчињата за јачина на звук" "%s е исклучена" "Изменете ги кратенките" "Готово" @@ -2011,6 +2010,7 @@ "Поставете заклучување екран" "Поставете заклучување екран" "За да користите „Приватен простор“, поставете заклучување екран на уредов" + "За да го избришете „Приватниот простор“, поставете заклучување екран на уредов" "Апликацијата не е достапна" "%1$s не е достапна во моментов." "%1$s е недостапна" diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 6d60696f40e99fd75f99eefb2fdb26bef9bbc113..cc545e29ac4f233b48a1c123b92ee727589305df 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -2010,6 +2010,7 @@ "സ്‌ക്രീൻ ലോക്ക് സജ്ജീകരിക്കൂ" "സ്‌ക്രീൻ ലോക്ക് സജ്ജീകരിക്കൂ" "സ്വകാര്യ സ്പേസിന്, ഇതിൽ സ്ക്രീൻ ലോക്ക് സജ്ജീകരിക്കൂ" + "സ്വകാര്യ സ്പേസ് ഇല്ലാതാക്കാൻ, ഈ ഉപകരണത്തിൽ സ്ക്രീൻ ലോക്ക് സജ്ജീകരിക്കൂ" "ആപ്പ് ലഭ്യമല്ല" "%1$s ഇപ്പോൾ ലഭ്യമല്ല." "%1$s ലഭ്യമല്ല" diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index bf2f78c9c9dbadc20f18f5c290ea486fe59c25cc..87ce94ba6939d77660e2089a8dcc748be1691cee 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1746,8 +1746,7 @@ "Апп зөвшөөрлийн хүсэлтийг хааж байгаа тул таны хариултыг баталгаажуулах боломжгүй." "Үүнийг ашиглаж эхлэхийн тулд онцлог дээр товшино уу:" "Хандалтын товчлуурын тусламжтай ашиглах онцлогуудыг сонгоно уу" - - + "Дууны түвшний товчийн товчлолоор ашиглах онцлогуудыг сонгоно уу" "%s-г унтраалаа" "Товчлолуудыг засах" "Болсон" @@ -2011,6 +2010,7 @@ "Дэлгэцийн түгжээ тохируулах" "Дэлгэцийн түгжээ тохируулах" "Хаалттай орон зайгаа ашиглах бол уг төхөөрөмжид дэлгэцийн түгжээ тохируулна уу" + "Хаалттай орон зайг устгахын тулд энэ төхөөрөмж дээр дэлгэцийн түгжээ тохируулна уу" "Апп боломжгүй байна" "%1$s яг одоо боломжгүй байна." "%1$s боломжгүй байна" diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 30403cf0984f1c8b042a1667f3d06c4958261331..03cabc31f573b1255d8ca4b6f98dd5756891fcaa 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -2010,6 +2010,7 @@ "स्क्रीन लॉक सेट करा" "स्क्रीन लॉक सेट करा" "तुमची खाजगी स्पेस वापरण्यास, या डिव्हाइसवर स्क्रीन लॉक सेट करा" + "खाजगी स्पेस हटवण्यासाठी, या डिव्हाइसवर स्क्रीन लॉक सेट करा" "ॲप उपलब्ध नाही" "%1$s आता उपलब्ध नाही." "%1$s उपलब्ध नाही" diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index e17c62bb168e614c3fa6c9be79a16dfcea356caf..83a1d56716cd95361307e61b29edb26c7a64250e 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -2010,6 +2010,7 @@ "Tetapkan kunci skrin" "Tetapkan kunci skrin" "Tetapkan kunci skrin pada peranti untuk menggunakan ruang privasi" + "Untuk memadamkan ruang peribadi, tetapkan kunci skrin pada peranti ini" "Apl tidak tersedia" "%1$s tidak tersedia sekarang." "%1$s tidak tersedia" diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 85fb0e9e27d747c90a4d98fa9196036887f62a43..e4e75c1e3d2f52987f4f3134c7ad1777faaec6d1 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -2010,6 +2010,7 @@ "ဖန်သားပြင်လော့ခ် သတ်မှတ်ပါ" "ဖန်သားပြင်လော့ခ် သတ်မှတ်ရန်" "သင့်သီးသန့်နေရာသုံးရန် ဤစက်၌ ဖန်သားပြင်လော့ခ် သတ်မှတ်ပါ" + "သီးသန့်နေရာကို ဖျက်ရန်အတွက် ဤစက်တွင် ဖန်သားပြင်လော့ခ် သတ်မှတ်ပါ" "အက်ပ်ကို မရနိုင်ပါ" "%1$s ကို ယခု မရနိုင်ပါ။" "%1$s မရနိုင်ပါ" diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 8feb6e91557740b7ff1f76281e612d4f028450be..6d24bdfc39b397323dcff3abcfce84a7f082de0a 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1746,8 +1746,7 @@ "En app dekker forespørselen om tillatelse, så svaret ditt kan ikke bekreftes." "Trykk på en funksjon for å begynne å bruke den:" "Velg funksjonene du vil bruke med Tilgjengelighet-knappen" - - + "Velg funksjonene du vil bruke med volumtastsnarveien" "%s er slått av" "Endre snarveier" "Ferdig" @@ -2011,6 +2010,7 @@ "Angi en skjermlås" "Angi skjermlås" "For å bruke det private området, angi en skjermlås på enheten" + "Før du kan slette det private området, må du angi en skjermlås på denne enheten" "Appen er ikke tilgjengelig" "%1$s er ikke tilgjengelig for øyeblikket." "%1$s er utilgjengelig" diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 36e3254135eb4d21f3166cd77537ecb4f2036030..133429b2fcf736500758bab8ca5c451f93eafd35 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1216,10 +1216,10 @@ "{rating,plural, =1{{max} मा एक तारा}other{{max} मा # तारा}}" "जारी छ" "प्रयोग गरेर कारबाही पुरा गर्नुहोस्" - "निम्न एपको प्रयोग गरी कारबाही पुरा गर्नुहोस्: %1$s" + "%1$s प्रयोग गरी यो कार्य पूरा गर्नुहोस्" "पूर्ण कारबाही" "निम्नबाट खोल्नुहोस्" - "निम्न एपमा खोल्नुहोस्: %1$s" + "%1$s मार्फत खोल्नुहोस्" "खोल्नुहोस्" "निम्नमार्फत %1$s का लिंकहरू खोल्नुहोस्" "निम्नमार्फत लिंकहरू खोल्नुहोस्" @@ -1227,7 +1227,7 @@ "%2$s मार्फत %1$s का लिंकहरू खोल्नुहोस्" "पहुँच दिनुहोस्" "सँग सम्पादन गर्नुहोस्" - "%1$s सँग सम्पादन गर्नुहोस्" + "%1$s प्रयोग गरी सम्पादन गर्नुहोस्" "सम्पादन गर्नुहोस्" "सेयर गर्नुहोस्" "%1$s सँग सेयर गर्नुहोस्" @@ -1937,7 +1937,7 @@ "हरेक हप्तादिनको राति" "शनिवार" "कार्यक्रम" - "निदाएका बेला" + "शयन" "%1$s ले व्यवस्थापन गरेको" "अन छ" "अफ छ" @@ -2010,6 +2010,7 @@ "स्क्रिन लक सेटअप गर्नुहोस्" "स्क्रिन लक सेटअप गर्नुहोस्" "निजी स्पेस प्रयोग गर्न यो डिभाइसमा स्क्रिन लक सेटअप गर्नुहोस्" + "निजी स्पेस मेटाउन यो डिभाइसमा स्क्रिन लक सेटअप गर्नुहोस्" "एप उपलब्ध छैन" "%1$s अहिले उपलब्ध छैन।" "%1$s उपलब्ध छैन" diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 18773f6c147394a0ed3f7d12db6a5c267b44d7fa..d2ec4ba725a3bf8e505bf79f10d10367b4e86b94 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -2010,6 +2010,7 @@ "Schermvergrendeling instellen" "Schermvergrendeling instellen" "Als je je privégedeelte wilt gebruiken, stel je een schermvergrendeling op dit apparaat in" + "Als je het privégedeelte wilt verwijderen, stel je een schermvergrendeling op dit apparaat in" "App is niet beschikbaar" "%1$s is momenteel niet beschikbaar." "%1$s niet beschikbaar" diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 45b6e9ac8e75eeb8ab33a058301b97cb0daec2c5..85a6aecda0bd31d1f882d52ba6b2a4d8d2b6817e 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1746,8 +1746,7 @@ "ଏକ ଆପ ଅନୁମତି ଅନୁରୋଧକୁ ଅସ୍ପଷ୍ଟ କରୁଛି ତେଣୁ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରାଯାଇପାରିବ ନାହିଁ।" "ଏକ ଫିଚର୍ ବ୍ୟବହାର କରିବା ଆରମ୍ଭ କରିବାକୁ ଏହାକୁ ଟାପ୍ କରନ୍ତୁ:" "ଆକ୍ସେସିବିଲିଟୀ ବଟନ୍ ସହିତ ବ୍ୟବହାର କରିବାକୁ ଫିଚରଗୁଡ଼ିକ ବାଛନ୍ତୁ" - - + "ଭଲ୍ୟୁମ କୀ ସର୍ଟକଟ ସହିତ ବ୍ୟବହାର କରିବାକୁ ଫିଚରଗୁଡ଼ିକ ବାଛନ୍ତୁ" "%s ବନ୍ଦ ହୋଇଯାଇଛି" "ସର୍ଟକଟଗୁଡ଼ିକୁ ଏଡିଟ କରନ୍ତୁ" "ହୋଇଗଲା" @@ -2011,6 +2010,7 @@ "ଏକ ସ୍କ୍ରିନ ଲକ ସେଟ କରନ୍ତୁ" "ସ୍କ୍ରିନ ଲକ ସେଟ କରନ୍ତୁ" "ଆପଣଙ୍କ ପ୍ରାଇଭେଟ ସ୍ପେସ ବ୍ୟବହାର କରିବାକୁ ଏହି ଡିଭାଇସରେ ଏକ ସ୍କ୍ରିନ ଲକ ସେଟ କରନ୍ତୁ" + "ପ୍ରାଇଭେଟ ସ୍ପେସକୁ ଡିଲିଟ କରିବା ପାଇଁ ଏହି ଡିଭାଇସରେ ଏକ ସ୍କ୍ରିନ ଲକ ସେଟ କରନ୍ତୁ" "ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ" "%1$s ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ।" "%1$s ଉପଲବ୍ଧ ନାହିଁ" diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index c6d0b9fa1d9ba9203c361f7d4c3276024b962bde..51d71ff28260f1737b88b9418fd2a9f01d1e7167 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1746,8 +1746,7 @@ "ਕੋਈ ਐਪ ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਬੇਨਤੀ ਨੂੰ ਅਸਪਸ਼ਟ ਕਰ ਰਹੀ ਹੈ, ਇਸ ਲਈ ਤੁਹਾਡੇ ਜਵਾਬ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।" "ਕਿਸੇ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਵਰਤਣਾ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਉਸ \'ਤੇ ਟੈਪ ਕਰੋ:" "ਪਹੁੰਚਯੋਗਤਾ ਬਟਨ ਨਾਲ ਵਰਤਣ ਲਈ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚੁਣੋ" - - + "ਅਵਾਜ਼ ਕੁੰਜੀਆਂ ਦੇ ਸ਼ਾਰਟਕੱਟ ਨਾਲ ਵਰਤਣ ਲਈ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚੁਣੋ" "%s ਨੂੰ ਬੰਦ ਕਰ ਦਿੱਤਾ ਗਿਆ ਹੈ" "ਸ਼ਾਰਟਕੱਟਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ" "ਹੋ ਗਿਆ" @@ -2011,6 +2010,7 @@ "ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ" "ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ" "ਆਪਣੀ ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਰਤਣ ਲਈ, ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ" + "ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਨੂੰ ਮਿਟਾਉਣ ਲਈ, ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਕਰੋ" "ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" "%1$s ਐਪ ਇਸ ਵੇਲੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।" "%1$s ਉਪਲਬਧ ਨਹੀਂ ਹੈ" diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 0ae3bbf5c496c8e6b23137e0ab3c0f03140522ea..49c30c7710440af09fdc4db47fe5267e37ed697e 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1748,8 +1748,7 @@ "Aplikacja zasłania prośbę o uprawnienia, więc nie można zweryfikować Twojej odpowiedzi." "Wybierz funkcję, aby zacząć z niej korzystać:" "Wybierz funkcje, których chcesz używać z przyciskiem ułatwień dostępu" - - + "Wybierz funkcje, do których chcesz używać skrótu z przyciskami głośności" "Usługa %s została wyłączona" "Edytuj skróty" "OK" @@ -2013,6 +2012,7 @@ "Ustaw blokadę ekranu" "Ustaw blokadę ekranu" "Aby korzystać z przestrzeni prywatnej, ustaw na tym urządzeniu blokadę ekranu" + "Aby usunąć przestrzeń prywatną, ustaw na tym urządzeniu blokadę ekranu" "Aplikacja jest niedostępna" "Aplikacja %1$s jest obecnie niedostępna." "%1$s – brak dostępu" diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index e025d19879d737adaeaba891fb838dac40976852..ff07066e186b5d76055e5d31ae0ca97a7a4b4562 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1747,8 +1747,7 @@ "Um app está ocultando a solicitação de permissão e impedindo a verificação da sua resposta." "Toque em um recurso para começar a usá-lo:" "Escolha recursos para usar com o botão de acessibilidade" - - + "Escolha recursos para usar com o atalho das teclas de volume" "O %s foi desativado" "Editar atalhos" "Concluído" @@ -2012,6 +2011,7 @@ "Defina um bloqueio de tela" "Definir bloqueio de tela" "Para usar o espaço privado, defina um bloqueio de tela neste dispositivo" + "Para excluir o espaço privado, defina um bloqueio de tela neste dispositivo" "O app não está disponível" "O app %1$s não está disponível no momento." "%1$s indisponível" diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 28c130c42fe4b7989eed0b60e83aa94e889dd7a0..12245b9a65e349f8f73e44691f4200f423674e41 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1938,7 +1938,7 @@ "Dias da semana à noite" "Fim de semana" "Evento" - "A dormir" + "Dormir" "Gerido por %1$s" "Ativada" "Desativada" @@ -2011,6 +2011,7 @@ "Defina um bloqueio de ecrã" "Definir bloqueio de ecrã" "Defina um bloqueio para usar o espaço privado" + "Para eliminar o espaço privado, defina um bloqueio de ecrã neste dispositivo" "A app não está disponível" "De momento, a app %1$s não está disponível." "%1$s indisponível" diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index e025d19879d737adaeaba891fb838dac40976852..ff07066e186b5d76055e5d31ae0ca97a7a4b4562 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1747,8 +1747,7 @@ "Um app está ocultando a solicitação de permissão e impedindo a verificação da sua resposta." "Toque em um recurso para começar a usá-lo:" "Escolha recursos para usar com o botão de acessibilidade" - - + "Escolha recursos para usar com o atalho das teclas de volume" "O %s foi desativado" "Editar atalhos" "Concluído" @@ -2012,6 +2011,7 @@ "Defina um bloqueio de tela" "Definir bloqueio de tela" "Para usar o espaço privado, defina um bloqueio de tela neste dispositivo" + "Para excluir o espaço privado, defina um bloqueio de tela neste dispositivo" "O app não está disponível" "O app %1$s não está disponível no momento." "%1$s indisponível" diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 7e5e7c0d900aedaca6f56a5ef5505be93fcb99c1..85f859240b7f9471edb7134e59893b90840c910f 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1747,8 +1747,7 @@ "O aplicație blochează solicitarea de permisiune, așa că răspunsul nu se poate verifica." "Atinge o funcție ca să începi să o folosești:" "Alege funcțiile pe care să le folosești cu butonul de accesibilitate" - - + "Alege funcțiile pentru comanda rapidă a butoanelor de volum" "%s a fost dezactivat" "Editează comenzile rapide" "Gata" @@ -2012,6 +2011,7 @@ "Setează o blocare a ecranului" "Setează blocarea ecranului" "Ca să folosești spațiul privat, setează blocarea ecranului pe acest dispozitiv" + "Ca să ștergi spațiul privat, setează o blocare a ecranului pe acest dispozitiv" "Aplicația nu este disponibilă" "%1$s nu este disponibilă momentan." "%1$s nu este disponibilă" diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 3c374ef5de2889698218b963743be343d53a33a6..65597649917c83a3438f72f4440bf323885a7e7f 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -2012,6 +2012,7 @@ "Настройте блокировку экрана" "Настроить блокировку экрана" "Чтобы использовать частное пространство, настройте блокировку экрана на этом устройстве." + "Чтобы удалить частное пространство, настройте блокировку экрана на этом устройстве." "Приложение недоступно" "Приложение \"%1$s\" сейчас недоступно." "Недоступно: %1$s" diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index af835d92bf713ca70f4dc4acb05b20a082cef79a..be4151280e3af514de84e9db81adc9475a493f23 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1057,7 +1057,7 @@ "%1$s කොටුව එකතු කරන ලදි" "රටාව සම්පූර්ණයි" "රටා ප්‍රදේශය." - "%1$s. %3$d න් %2$d විජටය." + "%%1$s. %%3$d න් %%2$d විජටය." "විජටය එක් කරන්න." "හිස්" "අගුළු අරින ප්‍රදේශය විදහා ඇත." @@ -1746,8 +1746,7 @@ "යෙදුමක් අවසර ඉල්ලීම අඳුරු කරන බැවින්, ඔබේ ප්‍රතිචාරය සත්‍යාපනය කළ නොහැක." "එය භාවිත කිරීම ආරම්භ කිරීමට විශේෂාංගයක් තට්ටු කරන්න:" "ප්‍රවේශ්‍යතා බොත්තම සමග භාවිත කිරීමට විශේෂාංග තෝරා ගන්න" - - + "හඬ පරිමා යතුරු කෙටිමග සමග භාවිත කිරීමට විශේෂාංග තෝරා ගන්න" "%s ක්‍රියාවිරහිත කර ඇත" "කෙටිමං සංස්කරණ කරන්න" "නිමයි" @@ -2011,6 +2010,7 @@ "තිර අගුලක් සකසන්න" "තිර අගුල සකසන්න" "ඔබේ රහසිගත අවකාශය භාවිතා කිරීමට, මෙම උපාංගයේ තිර අගුලක් සකසන්න" + "පෞද්ගලික අවකාශය මැකීමට, මෙම උපාංගයෙහි තිර අගුලක් සකසන්න" "යෙදුම ලබා ගත නොහැකිය" "%1$s මේ දැන් ලබා ගත නොහැකිය." "%1$s නොතිබේ" diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 7185bcdb0de6458c3c2f000723e0f4c9c29e29fc..c671f461b7de3d6a61f3a600e634fa43bd526ec4 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -2012,6 +2012,7 @@ "Nastavte zámku obrazovky" "Nastaviť zámku obrazovky" "Ak chcete používať súkromný priestor, nastavte v tomto zariadení zámku obrazovky" + "Ak chcete odstrániť súkromný priestor, nastavte v tomto zariadení zámku obrazovky" "Aplikácia nie je dostupná" "Aplikácia %1$s nie je teraz dostupná." "%1$s nie je k dispozícii" diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index e67bf9c910a33123007e52585c8ef5bcbd0722c4..ed8cf505d7eb43d5c88a423c6df3ae299bc5475c 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -2012,6 +2012,7 @@ "Nastavitev zaklepanja zaslona" "Nastavite zaklepanje zaslona" "Če želite uporabljati zasebni prostor, v tej napravi nastavite zaklepanje zaslona" + "Če želite izbrisati zasebni prostor, v tej napravi nastavite zaklepanje zaslona" "Aplikacija ni na voljo" "Aplikacija %1$s trenutno ni na voljo." %1$s« ni na voljo" diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index d8fe10944e2e0997217996e9a36f489956bec9bd..e24e310e5772eb2760425834382a2f266545553c 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1746,8 +1746,7 @@ "Një aplikacion po fsheh kërkesën për leje, prandaj përgjigja jote nuk mund të verifikohet." "Trokit te një veçori për të filluar ta përdorësh atë:" "Zgjidh veçoritë që do të përdorësh me butonin e qasshmërisë" - - + "Zgjidh veçoritë që do të përdorësh me shkurtoren e tasteve të volumit" "%s është çaktivizuar" "Redakto shkurtoret" "U krye" @@ -2011,6 +2010,7 @@ "Cakto një kyçje ekrani" "Cakto kyçjen e ekranit" "Për të përdorur hapësirën private, cakto një kyçje ekrani në këtë pajisje" + "Për të përdorur hapësirën private, cakto një kyçje ekrani në këtë pajisje" "Aplikacioni nuk ofrohet" "%1$s nuk ofrohet për momentin." "%1$s nuk ofrohet" diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index f5988c282e36821818dfdcc89bf8362685c0a817..915486e990e96849e8a1761bfc36d43dbd85429c 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -2011,6 +2011,7 @@ "Подесите откључавање екрана" "Подеси откључавање екрана" "Да бисте користили приватни простор, подесите откључавање екрана на овом уређају" + "Да бисте избрисали приватан простор, подесите откључавање екрана на овом уређају" "Апликација није доступна" "Апликација %1$s тренутно није доступна." "%1$s – није доступно" diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 9bde6afc80f857b6c69fb5a260f5147fc290eaf7..0c5b6acec0fd06b0e7c9769b02403adf39980fd5 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1746,8 +1746,7 @@ "En app döljer behörighetsbegäran så det går inte att verifiera svaret." "Tryck på funktioner som du vill aktivera:" "Välj vilka funktioner du vill använda med hjälp av tillgänglighetsknappen" - - + "Välj funktioner att använda med hjälp av volymknappskortkommandot" "%s har inaktiverats" "Redigera genvägar" "Klar" @@ -1938,7 +1937,7 @@ "Vardagskväll" "I helgen" "Händelse" - "När jag sover" + "Sover" "Hanteras av %1$s" "På" "Av" @@ -2011,6 +2010,7 @@ "Ställ in ett skärmlås" "Ställ in skärmlås" "Ställ in ett skärmlås för enheten om du vill använda ditt privata område." + "Ange ett skärmlås för enheten om du vill radera privat område" "Appen är inte tillgänglig" "%1$s är inte tillgängligt just nu." "%1$s är inte tillgänglig" diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index c286eeb48d2ce843bc272d20003fcd96427917e0..8d69834eaed932738c93233cb91882eb02eb5d02 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1746,8 +1746,7 @@ "Programu inazuia ombi la ruhusa kwa hivyo jibu lako haliwezi kuthibitishwa." "Gusa kipengele ili uanze kukitumia:" "Chagua vipengele vya kutumia na kitufe cha zana za ufikivu" - - + "Chagua vipengele vya kutumia kupitia njia ya mkato ya vitufe vya sauti" "Huduma ya %s imezimwa" "Kubadilisha njia za mkato" "Nimemaliza" @@ -2011,6 +2010,7 @@ "Weka mbinu ya kufunga skrini" "Weka mbinu ya kufunga skrini" "Ili utumie sehemu ya faragha, weka mbinu ya kufunga skrini kwenye kifaa hiki" + "Ili ufute sehemu ya faragha, weka mbinu ya kufunga skrini kwenye kifaa hiki" "Programu haipatikani" "%1$s haipatikani hivi sasa." "%1$s haipatikani" diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 626205e42483ed70fe6cfa3d33f1fd49e37ea582..dfc6a791e8769a1ecb4d9281995c089702411c02 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -2010,6 +2010,7 @@ "திரைப் பூட்டை அமையுங்கள்" "திரைப் பூட்டை அமை" "ரகசிய இடத்தைப் பயன்படுத்த, சாதனத்தில் திரைப் பூட்டை அமையுங்கள்" + "ரகசிய இடத்தை நீக்க, இந்தச் சாதனத்தில் திரைப் பூட்டை அமையுங்கள்" "இந்த ஆப்ஸ் இப்போது கிடைப்பதில்லை" "%1$s ஆப்ஸ் இப்போது கிடைப்பதில்லை." "%1$s இல்லை" diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index db19868b37dd1b6a180138bda9abea1f2d6e7a48..1f8a2f7dfcc996434cd709e5daa634a641d22081 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -2010,6 +2010,7 @@ "స్క్రీన్ లాక్‌ను సెట్ చేయండి" "స్క్రీన్ లాక్‌ను సెట్ చేయండి" "మీ ప్రైవేట్ స్పేస్‌ను ఉపయోగించడానికి, ఈ పరికరంలో స్క్రీన్ లాక్ సెట్ చేయండి" + "ప్రైవేట్ స్పేస్‌ను తొలగించడానికి, ఈ పరికరంలో స్క్రీన్ లాక్‌ను సెట్ చేయండి" "యాప్ అందుబాటులో లేదు" "%1$s ప్రస్తుతం అందుబాటులో లేదు." "%1$s అందుబాటులో లేదు" @@ -2152,7 +2153,7 @@ "సరే" "ఆఫ్ చేయండి" "మరింత తెలుసుకోండి" - "Android 12లో Android అనుకూల నోటిఫికేషన్‌లను, మెరుగైన నోటిఫికేషన్‌లు రీప్లేస్‌ చేశాయి. ఈ ఫీచర్, సూచించిన చర్యలను, రిప్లయిలను చూపించి, మీ నోటిఫికేషన్‌లను ఆర్గనైజ్ చేస్తుంది.\n\nకాంటాక్ట్ పేర్లు, మెసేజ్‌లు లాంటి వ్యక్తిగత సమాచారంతో పాటు నోటిఫికేషన్ కంటెంట్‌ను మెరుగైన నోటిఫికేషన్‌లు యాక్సెస్ చేస్తాయి. ఫోన్ కాల్స్‌కు సమాధానమివ్వడం, \'అంతరాయం కలిగించవద్దు\' ఆప్షన్‌ను కంట్రోల్ చేయడం వంటి నోటిఫికేషన్‌లను విస్మరించడం లేదా వాటికి ప్రతిస్పందించడం కూడా ఈ ఫీచర్ చేయగలదు." + "Android 12లో \'మెరుగైన నోటిఫికేషన్‌లు\', \'Android అనుకూల నోటిఫికేషన్‌ల\'ను రీప్లేస్ చేశాయి. చేయాల్సిన పనులను, రిప్లయిలను ఈ ఫీచర్ మీకు చూపిస్తుంది. అలాగే మీ నోటిఫికేషన్‌లను ఆర్గనైజ్ చేస్తుంది. \n\nనోటిఫికేషన్ కంటెంట్‌ను \'మెరుగైన నోటిఫికేషన్‌లు\' ఫీచర్ యాక్సెస్ చేయగలదు. కాంటాక్ట్ పేర్లు, మెసేజ్‌ల వంటి వ్యక్తిగత సమాచారం కూడా ఈ కంటెంట్‌లో ఉంటుంది. ఈ ఫీచర్, నోటిఫికేషన్‌లను విస్మరించగలదు (డిస్మిస్ చేయగలదు) లేదా వాటికి ప్రతిస్పందించగలదు (రెస్పాండ్ కాగలదు). ఫోన్ కాల్స్‌కు సమాధానమివ్వడం, \'అంతరాయం కలిగించవద్దు\' ఆప్షన్‌ను కంట్రోల్ చేయడం లాంటి పనులు కూడా ఇందులో ఉంటాయి." "రొటీన్ మోడ్ సమాచార నోటిఫికేషన్" "బ్యాటరీ సేవర్ ఆన్ చేయబడింది" "బ్యాటరీ జీవితకాలాన్ని పొడిగించడానికి బ్యాటరీ వినియోగాన్ని తగ్గించడం" diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index b34cf59a2bb8e22a08f88c0fbfd1fb1a87ad5fac..51bb90f71d2b54ad4d9f3de070bb90e2bbc2091e 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -2010,6 +2010,7 @@ "ตั้งล็อกหน้าจอ" "ตั้งล็อกหน้าจอ" "หากต้องการใช้พื้นที่ส่วนตัว ให้ตั้งการล็อกหน้าจอในอุปกรณ์นี้" + "หากต้องการลบพื้นที่ส่วนตัว ให้ตั้งการล็อกหน้าจอในอุปกรณ์นี้" "แอปไม่พร้อมใช้งาน" "%1$s ไม่พร้อมใช้งานในขณะนี้" "%1$s ไม่พร้อมใช้งาน" diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index b976f204e395be6d04b2abb9cd3706ccf5033066..a4d27527d48053856b441efcb969cc8dae913e2b 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -2010,6 +2010,7 @@ "Magtakda ng lock ng screen" "Itakda ang lock ng screen" "Para gamitin ang iyong pribadong space, magtakda ng lock ng screen sa device na ito." + "Para mag-delete ng pribadong space, magtakda ng lock ng screen sa device na ito" "Hindi available ang app" "Hindi available sa ngayon ang %1$s." "Hindi available ang %1$s" diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index a015a8f6cca3c3dffc4e0314561695a33269082c..f467f9264073f45f3a8f27b55396e7056bdf983a 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1746,8 +1746,7 @@ "Bir uygulama, izin isteğini gizlediğinden yanıtınız doğrulanamıyor." "Kullanmaya başlamak için bir özelliğe dokunun:" "Erişilebilirlik düğmesiyle kullanılacak özellikleri seçin" - - + "Ses seviyesi tuşları kısayoluyla kullanılacak özellikleri seçin" "%s kapatıldı" "Kısayolları düzenle" "Bitti" @@ -2011,6 +2010,7 @@ "Ekran kilidi ayarlayın" "Ekran kilidi ayarla" "Özel alanı kullanmak için cihazda ekran kilidi ayarlayın" + "Gizli alanı silmek için bu cihazda ekran kilidi ayarlayın." "Uygulama kullanılamıyor" "%1$s uygulaması şu anda kullanılamıyor." "%1$s kullanılamıyor" diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index e41d3ce1b925258324122c23522c23d39b78326d..a63f3fbf4e922282e1e05c301303ec24b0cfa2e1 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1748,8 +1748,7 @@ "Інший додаток перекриває запит на доступ, тому вашу відповідь не вдається підтвердити." "Натисніть функцію, щоб почати використовувати її:" "Виберіть функції для кнопки спеціальних можливостей" - - + "Виберіть функції для швидких дій клавішами гучності" "Сервіс %s вимкнено" "Змінити" "Готово" @@ -2013,6 +2012,7 @@ "Налаштуйте блокування екрана" "Налаштувати блокування екрана" "Для доступу до приватного простору налаштуйте блокування екрана" + "Щоб видалити приватний простір, налаштуйте блокування екрана на цьому пристрої" "Додаток недоступний" "Додаток %1$s зараз недоступний." "Недоступно: %1$s" diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index fcfff0faa88c3dee62513eae3efa451ef5fb3eb4..660068d969ece4a8c82de468b3d716cd34792698 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -2010,6 +2010,7 @@ "اسکرین لاک سیٹ کریں" "اسکرین لاک سیٹ کریں" "اپنی نجی اسپیس استعمال کرنے کیلئے، اس آلہ پر اسکرین لاک سیٹ کریں" + "پرائیویٹ اسپیس استعمال کرنے کیلئے، اس آلہ پر اسکرین لاک سیٹ کریں" "ایپ دستیاب نہیں ہے" "%1$s ابھی دستیاب نہیں ہے۔" "%1$s دستیاب نہیں ہے" diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index e9bc805e303a2f0e2b81a170b5d51d5c70466991..f62074e060d779859d289f9313a0626c8ab02b0a 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1746,8 +1746,7 @@ "Ilova ruxsat olish talabini berkitmoqda, shu sababdan javobingizni tasdiqlash imkonsiz." "Kerakli funksiyani tanlang" "Qulayliklar tugmasi bilan foydalanish uchun funksiyalarni tanlang" - - + "Tovush tugmasi bilan ishga tushiriladigan funksiyalarni tanlang" "%s faolsizlantirildi" "Tezkor tugmalarni tahrirlash" "OK" @@ -2011,6 +2010,7 @@ "Ekran qulfini sozlash" "Ekran qulfini sozlash" "Maxfiy makon ishlatish uchun bu qurilma ekran qulfini sozlang" + "Maxfiy makon ishlatish uchun bu qurilma ekran qulfini sozlang" "Ilova ishlamayapti" "Ayni vaqtda %1$s ilovasi ishlamayapti." "%1$s kanali ish faoliyatida emas" diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 510edab26fb427aa4266dc7e9c53a28640446dda..3b08a22ea956f745d2879b6477aac0599f4c2133 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1746,8 +1746,7 @@ "Một ứng dụng đang che khuất yêu cầu quyền này nên chúng tôi không thể xác minh phản hồi của bạn." "Nhấn vào một tính năng để bắt đầu sử dụng:" "Chọn các tính năng để dùng với nút hỗ trợ tiếp cận" - - + "Chọn các tính năng để dùng với lối tắt cho phím âm lượng" "%s đã bị tắt" "Chỉnh sửa phím tắt" "Xong" @@ -2011,6 +2010,7 @@ "Đặt phương thức khoá màn hình" "Đặt phương thức khoá màn hình" "Để dùng không gian riêng tư, hãy thiết lập một phương thức khoá màn hình trên thiết bị này" + "Để xoá không gian riêng tư, hãy đặt một phương thức khoá màn hình trên thiết bị này" "Ứng dụng này không dùng được" "%1$s hiện không dùng được." "Không hỗ trợ %1$s" diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index bcb54dcac5780427a08e25483126813a6cf14431..95753ebb093ba3e64953d53bc3df2adfa379920d 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1746,8 +1746,7 @@ "应用遮挡了权限请求,因此我们无法验证您的回复。" "点按相应功能即可开始使用:" "选择可通过“无障碍”按钮使用的功能" - - + "选择可通过音量键快捷方式使用的功能" "已关闭%s" "修改快捷方式" "完成" @@ -2011,6 +2010,7 @@ "设置一种屏锁方式" "设置屏锁方式" "若要使用私密空间,请在此设备上设置屏锁方式" + "若要删除私密空间,请在此设备上设置屏幕锁定" "应用无法使用" "%1$s目前无法使用。" "%1$s不可用" diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 875eec021f7bfdb0355e3fd1f9a7fd82d1ec8481..df04ec8e8387be4e10922d9d6f69e12410ca6bb9 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1746,8 +1746,7 @@ "有應用程式阻擋權限要求,因此系統無法驗證你的回應。" "輕按即可開始使用所需功能:" "選擇要配搭無障礙功能按鈕使用的功能" - - + "選擇要用音量鍵捷徑的功能" "%s 已關閉" "編輯捷徑" "完成" @@ -2011,6 +2010,7 @@ "設定螢幕鎖定" "設定螢幕鎖定" "如要使用私人空間,請在此裝置上設定螢幕鎖定功能" + "如要刪除私人空間,請在此裝置上設定螢幕鎖定功能" "無法使用應用程式" "目前無法使用「%1$s」。" "無法使用「%1$s」" diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index efa4c519ee195d06373dfc4b9a681d95f146dd31..f0351ceb5591eeab37118d35e9d98e5ea45eb573 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1746,8 +1746,7 @@ "應用程式遮擋了權限要求,因此系統無法驗證你的回覆。" "輕觸即可開始使用所需功能:" "選擇要搭配無障礙工具按鈕使用的功能" - - + "選擇要搭配音量鍵捷徑使用的功能" "「%s」已關閉" "編輯捷徑" "完成" @@ -2011,6 +2010,7 @@ "設定螢幕鎖定功能" "設定螢幕鎖定功能" "如要使用私人空間,請在這部裝置設定螢幕鎖定功能" + "如要刪除私人空間,請在這部裝置上設定螢幕鎖定功能" "應用程式無法使用" "「%1$s」目前無法使用。" "無法存取「%1$s」" diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 67a43611b2f3e5981d01bcfe9b4d5bb64be35d48..f051a456a9bb34da041e6c021ab9bc9a55b7a596 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1746,8 +1746,7 @@ "I-app ifihla isicelo semvume ngakho impendulo yakho ayikwazi ukuqinisekiswa." "Thepha isici ukuqala ukusisebenzisa:" "Khetha izici ongazisebenzisa nenkinobho yokufinyeleleka" - - + "Khetha izakhi ongazisebenzisa nesinqamuleli sokhiye bevolumu" "I-%s ivaliwe" "Hlela izinqamuleli" "Kwenziwe" @@ -2011,6 +2010,7 @@ "Setha ukukhiya isikrini" "Setha ukukhiya isikrini" "Ukuze usebenzise isikhala esigodliwe, setha ukukhiya kwesikrini kule divayisi." + "Ukuze usule Indawo Engasese, setha ukukhiya kwesikrini kule divayisi." "Uhlelo lokusebenza alutholakali" "I-%1$s ayitholakali khona manje." "okungatholakali %1$s" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f6267f6174b67ab47f7c91404952fd88433d938d..fe3d4f6b39bf3259ea9352994adc39c9502d253d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5834,6 +5834,13 @@ should also be non-empty.--> + + + + + + false @@ -7089,6 +7096,10 @@ 0 + + false + 102 @@ -7125,4 +7136,8 @@ false + + + + com\u002eandroid\u002esettings diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index dc99634ddabc1b7bf0e5ae1190f3979befd9ba2a..579dc91d2ca102f3fbe466060a6c1c05a21594c6 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1509,7 +1509,7 @@ please see styles_device_defaults.xml. - @@ -1526,6 +1526,12 @@ please see styles_device_defaults.xml. #009DC9 + + + - @@ -102,23 +102,23 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c69027c20802adf60cb1073b3fca589ad30f1b0 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml new file mode 100644 index 0000000000000000000000000000000000000000..41fe2250f0ad7341ab0a6e6b72ac162789ad8def --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml @@ -0,0 +1,32 @@ + + + + + + \ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..10e5267c02d321ddd6bc85a3f50885833eaaa6ce --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt @@ -0,0 +1,78 @@ +/* +* 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.settingslib.widget + +import android.content.Context +import android.os.Build +import android.os.SystemProperties + +object SettingsThemeHelper { + private const val IS_EXPRESSIVE_DESIGN_ENABLED = "is_expressive_design_enabled" + private var expressiveThemeState: ExpressiveThemeState = ExpressiveThemeState.UNKNOWN + + enum class ExpressiveThemeState { + UNKNOWN, + ENABLED, + DISABLED, + } + + @JvmStatic + fun isExpressiveTheme(context: Context): Boolean { + tryInit(context) + if (expressiveThemeState == ExpressiveThemeState.UNKNOWN) { + throw Exception( + "need to call com.android.settingslib.widget.SettingsThemeHelper.init(Context) first." + ) + } + + return expressiveThemeState == ExpressiveThemeState.ENABLED + } + + private fun tryInit(context: Context) { + if (expressiveThemeState != ExpressiveThemeState.UNKNOWN) { + return + } + + expressiveThemeState = + if ( + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) && + (SystemProperties.getBoolean(IS_EXPRESSIVE_DESIGN_ENABLED, false) || + getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false)) + ) { + ExpressiveThemeState.ENABLED + } else { + ExpressiveThemeState.DISABLED + } + } + + private fun getPropBoolean(context: Context, property: String, def: Boolean): Boolean { + return try { + val systemProperties = context.classLoader.loadClass("android.os.SystemProperties") + + val paramTypes = + arrayOf?>(String::class.java, Boolean::class.javaPrimitiveType) + val getBoolean = systemProperties.getMethod("getBoolean", *paramTypes) + + val params = arrayOf(property, def) + getBoolean.invoke(systemProperties, *params) as Boolean + } catch (iae: IllegalArgumentException) { + throw iae + } catch (exception: Exception) { + def + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt index e3f4860ee7646aee0b2d0672983e5e8c267c5d9f..185fd2974fb159281084cafa96043864f8172fdc 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.widget.banner import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ExperimentalLayoutApi @@ -55,6 +56,7 @@ import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled import com.android.settingslib.spa.widget.ui.SettingsBody import com.android.settingslib.spa.widget.ui.SettingsTitle @@ -62,15 +64,13 @@ import com.android.settingslib.spa.widget.ui.SettingsTitle fun SettingsBanner(content: @Composable ColumnScope.() -> Unit) { Card( shape = CornerExtraLarge, - colors = CardDefaults.cardColors( - containerColor = Color.Transparent, - ), - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = SettingsDimension.itemPaddingEnd, - vertical = SettingsDimension.itemPaddingAround, - ), + colors = CardDefaults.cardColors(containerColor = Color.Transparent), + modifier = + Modifier.fillMaxWidth() + .padding( + horizontal = SettingsDimension.itemPaddingEnd, + vertical = SettingsDimension.itemPaddingAround, + ), content = content, ) } @@ -81,40 +81,64 @@ fun SettingsBannerContent( content: @Composable ColumnScope.() -> Unit, ) { Card( - shape = CornerExtraSmall, - colors = CardDefaults.cardColors( - containerColor = containerColor.takeOrElse { MaterialTheme.colorScheme.surface }, - ), - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 1.dp), + shape = if (isSpaExpressiveEnabled) CornerExtraLarge else CornerExtraSmall, + colors = + CardDefaults.cardColors( + containerColor = containerColor.takeOrElse { MaterialTheme.colorScheme.surface } + ), + modifier = Modifier.fillMaxWidth().padding(vertical = 1.dp), content = content, ) } @Composable fun SettingsBanner(model: BannerModel) { - SettingsBanner { - SettingsBannerImpl(model) - } + SettingsBanner { SettingsBannerImpl(model) } } @Composable internal fun SettingsBannerImpl(model: BannerModel) { AnimatedVisibility(visible = model.isVisible()) { SettingsBannerContent(containerColor = model.containerColor) { - Column( - modifier = (model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier) - .padding( - horizontal = SettingsDimension.dialogItemPaddingHorizontal, - vertical = SettingsDimension.itemPaddingAround, - ), - verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround) - ) { - BannerHeader(model.imageVector, model.tintColor, model.onDismiss) - SettingsTitle(model.title) - SettingsBody(model.text) - Buttons(model.buttons, model.tintColor) + if (isSpaExpressiveEnabled) { + Column( + modifier = + (model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier) + .padding( + start = SettingsDimension.paddingLarge, + end = SettingsDimension.paddingLarge, + top = SettingsDimension.paddingLarge, + bottom = SettingsDimension.paddingSmall, + ) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + BannerIcon(model.imageVector, model.tintColor) + Column( + modifier = Modifier.padding(start = SettingsDimension.paddingLarge), + verticalArrangement = + Arrangement.spacedBy(SettingsDimension.itemPaddingAround), + ) { + BannerTitleHeader(model.title, model.onDismiss) + SettingsBody(model.text) + } + } + Buttons(model.buttons, model.tintColor) + } + } else { + Column( + modifier = + (model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier) + .padding( + horizontal = SettingsDimension.dialogItemPaddingHorizontal, + vertical = SettingsDimension.itemPaddingAround, + ), + verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround), + ) { + BannerHeader(model.imageVector, model.tintColor, model.onDismiss) + SettingsTitle(model.title) + SettingsBody(model.text) + Buttons(model.buttons, model.tintColor) + } } } } @@ -132,6 +156,15 @@ fun BannerHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() -> } } +@Composable +fun BannerTitleHeader(title: String, onDismiss: (() -> Unit)? = null) { + Row(Modifier.fillMaxWidth()) { + Box(modifier = Modifier.weight(1f)) { SettingsTitle(title) } + Spacer(modifier = Modifier.padding(SettingsDimension.paddingSmall)) + DismissButton(onDismiss) + } +} + @Composable private fun BannerIcon(imageVector: ImageVector?, color: Color) { if (imageVector != null) { @@ -147,19 +180,12 @@ private fun BannerIcon(imageVector: ImageVector?, color: Color) { @Composable private fun DismissButton(onDismiss: (() -> Unit)?) { if (onDismiss == null) return - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.secondaryContainer, - ) { - IconButton( - onClick = onDismiss, - modifier = Modifier.size(SettingsDimension.itemIconSize) - ) { + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.secondaryContainer) { + IconButton(onClick = onDismiss, modifier = Modifier.size(SettingsDimension.itemIconSize)) { Icon( imageVector = Icons.Outlined.Close, - contentDescription = stringResource( - androidx.compose.material3.R.string.m3c_snackbar_dismiss - ), + contentDescription = + stringResource(androidx.compose.material3.R.string.m3c_snackbar_dismiss), modifier = Modifier.padding(SettingsDimension.paddingSmall), ) } @@ -172,10 +198,11 @@ private fun Buttons(buttons: List, color: Color) { if (buttons.isNotEmpty()) { FlowRow( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy( - space = SettingsDimension.itemPaddingEnd, - alignment = Alignment.End, - ), + horizontalArrangement = + Arrangement.spacedBy( + space = SettingsDimension.itemPaddingEnd, + alignment = Alignment.End, + ), ) { for (button in buttons) { Button(button, color) @@ -205,9 +232,7 @@ private fun SettingsBannerPreview() { title = "Lorem ipsum", text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", imageVector = Icons.Outlined.WarningAmber, - buttons = listOf( - BannerButton(text = "Action") {}, - ) + buttons = listOf(BannerButton(text = "Action") {}), ) ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt index 022ddedd10629ab3ea5073dd898d939386dafe45..265864e1b3fd32c3973a3d0f3bef0681c4042cc0 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt @@ -17,11 +17,16 @@ package com.android.settingslib.spa.widget.dialog import android.content.res.Configuration +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -32,9 +37,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled data class AlertDialogButton( val text: String, @@ -85,27 +92,41 @@ private fun AlertDialogPresenter.SettingsAlertDialog( AlertDialog( onDismissRequest = ::close, modifier = Modifier.width(getDialogWidth()), - confirmButton = { confirmButton?.let { Button(it) } }, - dismissButton = dismissButton?.let { { Button(it) } }, - title = title?.let { { Text(it) } }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } + confirmButton = { + confirmButton?.let { if (isSpaExpressiveEnabled) ConfirmButton(it) else Button(it) } }, + dismissButton = + dismissButton?.let { + { if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) } + }, + title = title?.let { { CenterRow { Text(it) } } }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, properties = DialogProperties(usePlatformDefaultWidth = false), ) } +@Composable +internal fun CenterRow(content: @Composable (() -> Unit)) { + if (isSpaExpressiveEnabled) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + content() + } + } else { + content() + } +} + @Composable fun getDialogWidth(): Dp { val configuration = LocalConfiguration.current - return configuration.screenWidthDp.dp * when (configuration.orientation) { - Configuration.ORIENTATION_LANDSCAPE -> 0.65f - else -> 0.85f - } + return configuration.screenWidthDp.dp * + when (configuration.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> 0.65f + else -> 0.85f + } } @Composable @@ -120,3 +141,47 @@ private fun AlertDialogPresenter.Button(button: AlertDialogButton) { Text(button.text) } } + +@Composable +private fun AlertDialogPresenter.DismissButton(button: AlertDialogButton) { + OutlinedButton( + onClick = { + close() + button.onClick() + }, + enabled = button.enabled, + ) { + Text(button.text) + } +} + +@Composable +private fun AlertDialogPresenter.ConfirmButton(button: AlertDialogButton) { + Button( + onClick = { + close() + button.onClick() + }, + enabled = button.enabled, + ) { + Text(button.text) + } +} + +@Preview +@Composable +private fun AlertDialogPreview() { + val alertDialogPresenter = remember { + object : AlertDialogPresenter { + override fun open() {} + + override fun close() {} + } + } + alertDialogPresenter.SettingsAlertDialog( + confirmButton = AlertDialogButton("Ok"), + dismissButton = AlertDialogButton("Cancel"), + title = "Title", + text = { Text("Text") }, + ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt index 030522d73b265d3543ea1aedc0dcef3c457b7af3..58a83fa725326a7026a9901b2f779a7249f25cb5 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt @@ -40,10 +40,7 @@ fun SettingsAlertDialogWithIcon( dismissButton: AlertDialogButton?, title: String?, icon: @Composable (() -> Unit)? = { - Icon( - Icons.Default.WarningAmber, - contentDescription = null - ) + Icon(Icons.Default.WarningAmber, contentDescription = null) }, text: @Composable (() -> Unit)?, ) { @@ -52,43 +49,22 @@ fun SettingsAlertDialogWithIcon( icon = icon, modifier = Modifier.width(getDialogWidth()), confirmButton = { - confirmButton?.let { - Button( - onClick = { - it.onClick() - }, - ) { - Text(it.text) - } - } + confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } } }, - dismissButton = dismissButton?.let { - { - OutlinedButton( - onClick = { - it.onClick() - }, - ) { - Text(it.text) + dismissButton = + dismissButton?.let { { OutlinedButton(onClick = { it.onClick() }) { Text(it.text) } } }, + title = + title?.let { + { + CenterRow { + Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + } } - } - }, - title = title?.let { - { - Text( - it, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - } - }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } - }, + }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, properties = DialogProperties(usePlatformDefaultWidth = false), ) -} \ No newline at end of file +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt index bef0bca1c5a44c58613033f04f04ecd3b24fa18c..9f2210d852a97a0305aa8a696f3639bf5007d68d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt @@ -58,10 +58,7 @@ fun SettingsAlertDialogContent( dismissButton: AlertDialogButton?, title: String?, icon: @Composable (() -> Unit)? = { - Icon( - Icons.Default.WarningAmber, - contentDescription = null - ) + Icon(Icons.Default.WarningAmber, contentDescription = null) }, text: @Composable (() -> Unit)?, ) { @@ -69,42 +66,22 @@ fun SettingsAlertDialogContent( buttons = { AlertDialogFlowRow( mainAxisSpacing = ButtonsMainAxisSpacing, - crossAxisSpacing = ButtonsCrossAxisSpacing + crossAxisSpacing = ButtonsCrossAxisSpacing, ) { - dismissButton?.let { - OutlinedButton(onClick = it.onClick) { - Text(it.text) - } - } - confirmButton?.let { - Button( - onClick = { - it.onClick() - }, - ) { - Text(it.text) - } - } + dismissButton?.let { OutlinedButton(onClick = it.onClick) { Text(it.text) } } + confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } } } }, icon = icon, modifier = Modifier.width(getDialogWidth()), - title = title?.let { - { - Text( - it, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - } - }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } - }, + title = + title?.let { + { Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) } + }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, ) } @@ -121,18 +98,12 @@ internal fun SettingsAlertDialogContent( shape = SettingsShape.CornerExtraLarge, color = MaterialTheme.colorScheme.surfaceContainerHigh, ) { - Column( - modifier = Modifier.padding(DialogPadding) - ) { + Column(modifier = Modifier.padding(DialogPadding)) { icon?.let { CompositionLocalProvider( - LocalContentColor provides AlertDialogDefaults.iconContentColor, + LocalContentColor provides AlertDialogDefaults.iconContentColor ) { - Box( - Modifier - .padding(IconPadding) - .align(Alignment.CenterHorizontally) - ) { + Box(Modifier.padding(IconPadding).align(Alignment.CenterHorizontally)) { icon() } } @@ -144,8 +115,7 @@ internal fun SettingsAlertDialogContent( ) { Box( // Align the title to the center when an icon is present. - Modifier - .padding(TitlePadding) + Modifier.padding(TitlePadding) .align( if (icon == null) { Alignment.Start @@ -161,11 +131,10 @@ internal fun SettingsAlertDialogContent( text?.let { ProvideContentColorTextStyle( contentColor = AlertDialogDefaults.textContentColor, - textStyle = MaterialTheme.typography.bodyMedium + textStyle = MaterialTheme.typography.bodyMedium, ) { Box( - Modifier - .weight(weight = 1f, fill = false) + Modifier.weight(weight = 1f, fill = false) .padding(TextPadding) .align(Alignment.Start) ) { @@ -177,7 +146,7 @@ internal fun SettingsAlertDialogContent( ProvideContentColorTextStyle( contentColor = MaterialTheme.colorScheme.primary, textStyle = MaterialTheme.typography.labelLarge, - content = buttons + content = buttons, ) } } @@ -188,7 +157,7 @@ internal fun SettingsAlertDialogContent( internal fun AlertDialogFlowRow( mainAxisSpacing: Dp, crossAxisSpacing: Dp, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { Layout(content) { measurables, constraints -> val sequences = mutableListOf>() @@ -204,8 +173,9 @@ internal fun AlertDialogFlowRow( // Return whether the placeable can be added to the current sequence. fun canAddToCurrentSequence(placeable: Placeable) = - currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() + - placeable.width <= constraints.maxWidth + currentSequence.isEmpty() || + currentMainAxisSize + mainAxisSpacing.roundToPx() + placeable.width <= + constraints.maxWidth // Store current sequence information and start a new sequence. fun startNewSequence() { @@ -213,8 +183,7 @@ internal fun AlertDialogFlowRow( crossAxisSpace += crossAxisSpacing.roundToPx() } // Ensures that confirming actions appear above dismissive actions. - @Suppress("ListIterator") - sequences.add(0, currentSequence.toList()) + @Suppress("ListIterator") sequences.add(0, currentSequence.toList()) crossAxisSizes += currentCrossAxisSize crossAxisPositions += crossAxisSpace @@ -254,23 +223,23 @@ internal fun AlertDialogFlowRow( layout(layoutWidth, layoutHeight) { sequences.fastForEachIndexed { i, placeables -> - val childrenMainAxisSizes = IntArray(placeables.size) { j -> - placeables[j].width + - if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0 - } + val childrenMainAxisSizes = + IntArray(placeables.size) { j -> + placeables[j].width + + if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0 + } val arrangement = Arrangement.End val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 } with(arrangement) { arrange( - mainAxisLayoutSize, childrenMainAxisSizes, - layoutDirection, mainAxisPositions + mainAxisLayoutSize, + childrenMainAxisSizes, + layoutDirection, + mainAxisPositions, ) } placeables.fastForEachIndexed { j, placeable -> - placeable.place( - x = mainAxisPositions[j], - y = crossAxisPositions[i] - ) + placeable.place(x = mainAxisPositions[j], y = crossAxisPositions[i]) } } } @@ -289,8 +258,8 @@ private val ButtonsCrossAxisSpacing = 12.dp /** * ProvideContentColorTextStyle * - * A convenience method to provide values to both LocalContentColor and LocalTextStyle in - * one call. This is less expensive than nesting calls to CompositionLocalProvider. + * A convenience method to provide values to both LocalContentColor and LocalTextStyle in one call. + * This is less expensive than nesting calls to CompositionLocalProvider. * * Text styles will be merged with the current value of LocalTextStyle. */ @@ -298,12 +267,12 @@ private val ButtonsCrossAxisSpacing = 12.dp private fun ProvideContentColorTextStyle( contentColor: Color, textStyle: TextStyle, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val mergedStyle = LocalTextStyle.current.merge(textStyle) CompositionLocalProvider( LocalContentColor provides contentColor, LocalTextStyle provides mergedStyle, - content = content + content = content, ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt index 9bbc16d56811879bb3c69344a4a4b3a6cd127154..94d2c210daaba8c2f2370235b5e793a6445807c1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -62,7 +62,7 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) { modifier = if (isSpaExpressiveEnabled) Modifier .size(SettingsDimension.actionIconWidth, SettingsDimension.actionIconHeight) .clip(SettingsShape.CornerExtraLarge) - .background(MaterialTheme.colorScheme.onSurfaceVariant) + .background(MaterialTheme.colorScheme.surfaceContainerHigh) .padding(SettingsDimension.actionIconPadding) else Modifier ) } diff --git a/packages/SettingsLib/res/drawable/ic_media_microphone.xml b/packages/SettingsLib/res/drawable/ic_media_microphone.xml new file mode 100644 index 0000000000000000000000000000000000000000..209dea515802cac518efbc26b4cccebb9e128431 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_media_microphone.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 1963b15c21b32a7ea93dad7093992aa5c95d8404..de728e27beac45f57aa19d489c452c1a8c88ae40 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -582,8 +582,11 @@ "Sopas" "Hierdie foon" "Hierdie tablet" + + + "Mikrofoon (intern)" "Dokluidspreker" "Eksterne toestel" "Gekoppelde toestel" @@ -684,7 +687,14 @@ "Gedeaktiveer" "Geaktiveer" "Jou toestel moet herselflaai om hierdie verandering toe te pas. Herselflaai nou of kanselleer." - "Bedrade oorfone" + + + + + + + "Mikrofoonsok" + "USB-mikrofoon" "Aan" "Af" "Diensverskaffernetwerk verander tans" diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 002d3bc17755b40743126826e71370ef8186fc30..0c9ed5cef34f99379eb2afe6a6f84cff14590201 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -582,8 +582,11 @@ "ልክ አሁን" "ይህ ስልክ" "ይህ ጡባዊ" + + + "ማይክሮፎን (ውስጣዊ)" "የመትከያ ድምፅ ማውጫ" "የውጭ መሣሪያ" "የተገናኘ መሣሪያ" @@ -684,7 +687,14 @@ "ተሰናክሏል" "ነቅቷል" "የእርስዎን መሣሪያ ይህ ለው ለማመልከት እንደገና መነሣት አለበት። አሁን እንደገና ያስነሡ ወይም ይተዉት።" - "ባለገመድ ጆሮ ማዳመጫ" + + + + + + + "የማይክሮፎን መሰኪያ" + "USB ማይክሮፎን" "አብራ" "አጥፋ" "የአገልግሎት አቅራቢ አውታረ መረብን በመቀየር ላይ" diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index a459a8688960f5ce7decf392d8672f4c41d571a6..46e4c76f4dfad049c37847887c303b8fac6d94b7 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -582,8 +582,11 @@ "للتو" "هذا الهاتف" "هذا الجهاز اللوحي" + + + "ميكروفون (داخلي)" "مكبّر صوت بقاعدة إرساء" "جهاز خارجي" "جهاز متّصل" @@ -684,7 +687,14 @@ "غير مفعّل" "مفعّل" "يجب إعادة تشغيل جهازك ليتم تطبيق هذا التغيير. يمكنك إعادة التشغيل الآن أو إلغاء التغيير." - "سمّاعة سلكية" + + + + + + + "مقبس الميكروفون" + "‏ميكروفون بمنفذ USB" "مفعّلة" "إيقاف" "جارٍ تغيير شبكة مشغِّل شبكة الجوّال." diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index 6f6dce75747d8b8d33780470fb90cf35929e10cf..82cd91c8d1b8091daa338be14087c1ec001c85ab 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -582,8 +582,11 @@ "এই মাত্ৰ" "এই ফ’নটো" "এই টেবলেটটো" + + + "মাইক্ৰ’ফ’ন (অভ্যন্তৰীণ)" "ড’ক স্পীকাৰ" "বাহ্যিক ডিভাইচ" "সংযোগ হৈ থকা ডিভাইচ" @@ -684,7 +687,14 @@ "অক্ষম কৰা আছে" "সক্ষম কৰা আছে" "এই সলনিটো কার্যকৰী হ’বলৈ আপোনাৰ ডিভাইচটো ৰিবুট কৰিবই লাগিব। এতিয়াই ৰিবুট কৰক অথবা বাতিল কৰক।" - "তাঁৰযুক্ত হেডফ\'ন" + + + + + + + "মাইকৰ জেক" + "ইউএছবি মাইক" "অন" "অফ" "বাহক নেটৱৰ্কৰ পৰিৱৰ্তন" diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 8cc67cb70500015c1a3972aeba05b20fd5118526..4715109b8f45b5daecdd282e0208f293a13c49a7 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -582,8 +582,11 @@ "İndicə" "Bu telefon" "Bu planşet" + + + "Mikrofon (daxili)" "Dok dinamiki" "Xarici cihaz" "Qoşulmuş cihaz" @@ -684,7 +687,14 @@ "Deaktiv" "Aktiv" "Bu dəyişikliyin tətbiq edilməsi üçün cihaz yenidən başladılmalıdır. İndi yenidən başladın və ya ləğv edin." - "Naqilli qulaqlıq" + + + + + + + "Mikrofon yuvası" + "USB mikrofon" "Aktiv" "Deaktiv" "Operator şəbəkəsinin dəyişilməsi" diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 3688d0cbb35d736be61324878925e0f0acfc1d8a..5da1c69ef1c7d7edc9478d3320889b806cdbce4e 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -582,8 +582,11 @@ "Upravo" "Ovaj telefon" "Ovaj tablet" + + + "Mikrofon (interni)" "Zvučnik bazne stanice" "Spoljni uređaj" "Povezani uređaj" @@ -684,7 +687,14 @@ "Onemogućeno" "Omogućeno" "Morate da restartujete uređaj da bi se ova promena primenila. Restartujte ga odmah ili otkažite." - "Žičane slušalice" + + + + + + + "Utikač za mikrofon" + "USB mikrofon" "Uključeno" "Isključeno" "Promena mreže mobilnog operatera" diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index f4f5331cbea0b97e28a42c5cb43349a0b5373f44..2adcc43a885bd1352a5187ae9c7f3c35224136b0 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -582,8 +582,11 @@ "Толькі што" "Гэты тэлефон" "Гэты планшэт" + + + "Мікрафон (унутраны)" "Дынамік док-станцыі" "Знешняя прылада" "Падключаная прылада" @@ -684,7 +687,14 @@ "Выключана" "Уключана" "Перазагрузіце прыладу, каб прымяніць гэта змяненне. Перазагрузіце ці скасуйце." - "Правадныя навушнікі" + + + + + + + "Раздым для мікрафона" + "Мікрафон USB" "Уключана" "Выключана" "Змяненне аператара сеткі" diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index dd16fc91db09fd6190765e3fa84f64b907ba5170..df5051f175a86b7e9e46c3f31c7fc9900dc8ae44 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -582,8 +582,11 @@ "Току-що" "Този телефон" "Този таблет" + + + "Микрофон (вътрешен)" "Високоговорител докинг станция" "Външно устройство" "Свързано устройство" @@ -684,7 +687,14 @@ "Деактивирано" "Активирано" "За да бъде приложена тази промяна, устройството ви трябва да бъде рестартирано. Рестартирайте сега или анулирайте." - "Слушалки с кабел" + + + + + + + "Жак за микрофон" + "Микрофон с USB" "Включване" "Изключване" "Промяна на мрежата на оператора" diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 6b377178bd50fce3de3b077592263c27c959255c..e782aa1b074d7518de87c8a7c7511b6d7c5f4d83 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -582,8 +582,11 @@ "এখনই" "এই ফোন" "এই ট্যাবলেট" + + + "মাইক্রোফোন (ইন্টার্নাল)" "ডক স্পিকার" "এক্সটার্নাল ডিভাইস" "কানেক্ট থাকা ডিভাইস" @@ -684,7 +687,14 @@ "বন্ধ করা আছে" "চালু করা আছে" "এই পরিবর্তনটি প্রয়োগ করার জন্য আপনার ডিভাইসটি অবশ্যই রিবুট করতে হবে। এখনই রিবুট করুন বা বাতিল করুন।" - "তার যুক্ত হেডফোন" + + + + + + + "মাইকের জ্যাক" + "USB মাইক" "চালু আছে" "বন্ধ আছে" "পরিষেবা প্রদানকারীর নেটওয়ার্ক পরিবর্তন করা হচ্ছে" diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index cb71f256a48ceb3cb7486d7271809f20dab55eb7..bddced568bb3f0926a506c134cec9619c597cfe7 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -582,8 +582,11 @@ "Upravo" "Ovaj telefon" "Ovaj tablet" + + + "Mikrofon (interni)" "Zvučnik priključne stanice" "Vanjski uređaj" "Povezani uređaj" @@ -684,7 +687,14 @@ "Onemogućeno" "Omogućeno" "Morate ponovo pokrenuti uređaj da se ova promjena primijeni. Ponovo pokrenite odmah ili otkažite." - "Žičane slušalice" + + + + + + + "Priključak za mikrofon" + "USB mikrofon" "Uključi" "Isključi" "Promjena mreže mobilnog operatera" diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 84eb51fedda8500f7c0b5a5db1e2786dedf6613c..89f8f5af1b4bdfc816255a198a40d33636d6ee90 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -582,8 +582,11 @@ "Ara mateix" "Aquest telèfon" "Aquesta tauleta" + + + "Micròfon (intern)" "Base d\'altaveu" "Dispositiu extern" "Dispositiu connectat" @@ -684,7 +687,14 @@ "Desactivat" "Activat" "Has de reiniciar el teu dispositiu perquè s\'apliquin els canvis. Reinicia\'l ara o cancel·la." - "Auriculars amb cable" + + + + + + + "Connector per al micròfon" + "Micròfon USB" "Activa" "Desactiva" "S\'està canviant la xarxa de l\'operador de telefonia mòbil" diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index a8764b6cd9e405a2d4dc665cd21951538532c8c7..e7750501784230121deb38bc0bd0369706809dfd 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -582,8 +582,11 @@ "Právě teď" "Tento telefon" "Tento tablet" + + + "Mikrofon (interní)" "Reproduktor doku" "Externí zařízení" "Připojené zařízení" @@ -684,7 +687,14 @@ "Vypnuto" "Zapnuto" "Aby se tato změna projevila, je třeba zařízení restartovat. Restartujte zařízení nebo zrušte akci." - "Kabelová sluchátka" + + + + + + + "Konektor mikrofonu" + "USB mikrofon" "Zapnout" "Vypnout" "Probíhá změna sítě operátora" diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 697802ead22711bf8a72c98130bb8c8562d9ad45..a85bc291afa29f28964c2436a11ebc616d63b453 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -582,8 +582,11 @@ "Lige nu" "Denne telefon" "Denne tablet" + + + "Mikrofon (indbygget)" "Dockhøjttaler" "Ekstern enhed" "Forbundet enhed" @@ -684,7 +687,14 @@ "Deaktiveret" "Aktiveret" "Din enhed skal genstartes for at anvende denne ændring. Genstart nu, eller annuller." - "Høretelefoner med ledning" + + + + + + + "Stik til mikrofon" + "USB-mikrofon" "Til" "Fra" "Skift af mobilnetværk" diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index cb745fca6148dfac8b4e55512ca6cc62717c690d..ac46bb606d25bd4cfdcfdf78c20f00fda67787a8 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -582,8 +582,12 @@ "Gerade eben" "Dieses Smartphone" "Dieses Tablet" + + + + "Dock-Lautsprecher" "Externes Gerät" "Verbundenes Gerät" @@ -684,7 +688,16 @@ "Deaktiviert" "Aktiviert" "Damit diese Änderung übernommen wird, musst du dein Gerät neu starten. Du kannst es jetzt neu starten oder den Vorgang abbrechen." - "Kabelgebundene Kopfhörer" + + + + + + + + + + "An" "Aus" "Mobilfunknetzwerk wird gewechselt" diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 89817dd59661c94cc818b79b59066cdbad14856d..ce045a9cb2757220e16ec759e61c900a6352763b 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -582,8 +582,11 @@ "Μόλις τώρα" "Αυτό το τηλέφωνο" "Αυτό το tablet" + + + "Μικρόφωνο (εσωτερικό)" "Ηχείο βάσης σύνδεσης" "Εξωτερική συσκευή" "Συνδεδεμένη συσκευή" @@ -684,7 +687,14 @@ "Ανενεργή" "Ενεργή" "Για να εφαρμοστεί αυτή η αλλαγή, θα πρέπει να επανεκκινήσετε τη συσκευή σας. Επανεκκίνηση τώρα ή ακύρωση." - "Ενσύρματα ακουστικά" + + + + + + + "Υποδοχή μικροφώνου" + "Μικρόφωνο USB" "Ενεργό" "Ανενεργό" "Αλλαγή δικτύου εταιρείας κινητής τηλεφωνίας" diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index b6e41c47474694a52d7cff3ab3660f2116b65c7d..961f0e7d16fdd0e2566825780852423923faa0bd 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -582,8 +582,11 @@ "Just now" "This phone" "This tablet" + + + "Microphone (internal)" "Dock speaker" "External device" "Connected device" @@ -684,7 +687,14 @@ "Disabled" "Enabled" "Your device must be rebooted for this change to apply. Reboot now or cancel." - "Wired headphones" + + + + + + + "Mic jack" + "USB mic" "On" "Off" "Operator network changing" diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index be5dfaa7434ed6697dd87e045e7c8e00a63709e8..6c2f45efdb89e12a29b10f32fa9bc89a79e4ac04 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -582,8 +582,11 @@ "Just now" "This phone" "This tablet" + + + "Microphone (internal)" "Dock speaker" "External Device" "Connected device" @@ -684,7 +687,14 @@ "Disabled" "Enabled" "Your device must be rebooted for this change to apply. Reboot now or cancel." - "Wired headphone" + + + + + + + "Mic jack" + "USB mic" "On" "Off" "Carrier network changing" diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index b6e41c47474694a52d7cff3ab3660f2116b65c7d..961f0e7d16fdd0e2566825780852423923faa0bd 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -582,8 +582,11 @@ "Just now" "This phone" "This tablet" + + + "Microphone (internal)" "Dock speaker" "External device" "Connected device" @@ -684,7 +687,14 @@ "Disabled" "Enabled" "Your device must be rebooted for this change to apply. Reboot now or cancel." - "Wired headphones" + + + + + + + "Mic jack" + "USB mic" "On" "Off" "Operator network changing" diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index b6e41c47474694a52d7cff3ab3660f2116b65c7d..961f0e7d16fdd0e2566825780852423923faa0bd 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -582,8 +582,11 @@ "Just now" "This phone" "This tablet" + + + "Microphone (internal)" "Dock speaker" "External device" "Connected device" @@ -684,7 +687,14 @@ "Disabled" "Enabled" "Your device must be rebooted for this change to apply. Reboot now or cancel." - "Wired headphones" + + + + + + + "Mic jack" + "USB mic" "On" "Off" "Operator network changing" diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index 92af2847754733d3409564725212a5b9e7364145..a9ed4a4c1be06b9ca9f2c79334c73830113cf1fc 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -582,8 +582,11 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎Just now‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎This phone‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‎This tablet‎‏‎‎‏‎" + + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎Microphone (internal)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‎Dock speaker‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‎External Device‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎Connected device‎‏‎‎‏‎" @@ -684,7 +687,14 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎Disabled‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‎Enabled‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎Your device must be rebooted for this change to apply. Reboot now or cancel.‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎Wired headphone‎‏‎‎‏‎" + + + + + + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎Mic jack‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎USB mic‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎On‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎Off‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎Carrier network changing‎‏‎‎‏‎" diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index ff69f6495eda07bc6978f21d32c147dc4263b25a..8fcb49ce9a07b41ba3b71f09dadd44bf09d69fbb 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -564,8 +564,7 @@ "Alarmas y recordatorios" "Permite que esta app establezca alarmas y programe acciones para horarios específicos. De esta manera, la app puede ejecutarse en segundo plano, lo que podría aumentar el consumo de batería.\n\nSi se desactiva este permiso, no funcionarán las alarmas ni los eventos basados en el tiempo existentes que programe esta app." "programar, alarma, recordatorio, reloj" - - + "No interrumpir" "No interrumpir" "Activar" "Activar No interrumpir" @@ -583,8 +582,12 @@ "Recién" "Este teléfono" "Esta tablet" + + + + "Bocina de la estación de carga" "Dispositivo externo" "Dispositivo conectado" @@ -685,7 +688,16 @@ "Inhabilitado" "Habilitado" "Debes reiniciar el dispositivo para que se aplique el cambio. Reinícialo ahora o cancela la acción." - "Auriculares con cable" + + + + + + + + + + "Activar" "Desactivar" "Cambio de proveedor de red" diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index e2d35891d149bd375fca7df71349996fb897ce1d..716713a6fb6a4e7acb8a89957128be81e4a9b139 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -564,8 +564,7 @@ "Alarmas y recordatorios" "Permite que esta aplicación programe alarmas y otras acciones que se llevan a cabo a una hora determinada. Esto hace que la aplicación pueda seguir activa en segundo plano, lo que puede usar más batería.\n\nSi este permiso está desactivado, no funcionarán las alarmas ni los eventos que se activan a una hora determinada que programe esta aplicación." "programar, alarma, recordatorio, reloj" - - + "No molestar" "No molestar" "Activar" "Activar el modo No molestar" @@ -583,8 +582,11 @@ "justo ahora" "Este teléfono" "Esta tablet" + + + "Micrófono (interno)" "Altavoz de la base" "Dispositivo externo" "Dispositivo conectado" @@ -685,7 +687,14 @@ "Inhabilitado" "Habilitado" "Es necesario reiniciar tu dispositivo para que se apliquen los cambios. Reinicia ahora o cancela la acción." - "Auriculares con cable" + + + + + + + "Conector jack para micrófono" + "Micrófono USB" "Activado" "Desactivado" "Cambiando la red del operador" diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index 8f7c4a722f1b16f8291f6ca90db58d425ce3bd02..f1975fa4c402d1d5c8200f9e2b8571065d1789bc 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -582,8 +582,11 @@ "Äsja" "See telefon" "See tahvelarvuti" + + + "Mikrofon (sisemine)" "Doki kõlar" "Väline seade" "Ühendatud seade" @@ -684,7 +687,14 @@ "Keelatud" "Lubatud" "Selle muudatuse rakendamiseks tuleb seade taaskäivitada. Taaskäivitage kohe või tühistage." - "Juhtmega kõrvaklapid" + + + + + + + "Mikrofoni pistikupesa" + "USB-mikrofon" "Sees" "Väljas" "Operaatori võrku muudetakse" diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 72855477ad25acb9ea2e21ae0aa5e604c36bbef3..fc4157c5e560b35bbe12225653a6a432e4db7457 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -564,7 +564,7 @@ "Alarmak eta abisuak" "Eman alarmak ezartzeko eta denbora-muga duten ekintzak programatzeko baimena aplikazioari. Hala, aplikazioak atzeko planoan funtzionatuko du, eta litekeena da bateria gehiago kontsumitzea.\n\nBaimen hori ematen ez baduzu, ez dute funtzionatuko aplikazio honen bidez programatutako alarmek eta denbora-muga duten ekintzek." "programazioa, alarma, abisua, erlojua" - "Ez molestatzeko modua" + "Ez molestatzeko" "Ez molestatzeko modua" "Aktibatu" "Aktibatu ez molestatzeko modua" @@ -582,8 +582,11 @@ "Oraintxe" "Telefono hau" "Tableta hau" + + + "Mikrofonoa (barnekoa)" "Oinarri bozgorailuduna" "Kanpoko gailua" "Konektatutako gailua" @@ -684,7 +687,14 @@ "Desgaituta" "Gaituta" "Aldaketa aplikatzeko, berrabiarazi egin behar da gailua. Berrabiaraz ezazu orain, edo utzi bertan behera." - "Entzungailu kableduna" + + + + + + + "Mikrofonoaren konektorea" + "USB bidezko mikrofonoa" "Aktibatu" "Desaktibatu" "Operadorearen sarea aldatzen" diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index ff0ad1df79b65edc11e4d76d8f9714245640b427..db9a744f5021a2092dd94eeddc909b2328f9d915 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -582,8 +582,11 @@ "هم‌اکنون" "این تلفن" "این رایانه لوحی" + + + "میکروفون (داخلی)" "بلندگوی پایه اتصال" "دستگاه خارجی" "دستگاه متصل" @@ -684,7 +687,14 @@ "غیرفعال" "فعال" "برای اعمال این تغییر، دستگاه باید بازراه‌اندازی شود. یا اکنون بازراه‌اندازی کنید یا لغو کنید." - "هدفون سیمی" + + + + + + + "فیش میکروفون" + "‏میکروفون USB" "روشن" "خاموش" "تغییر شبکه شرکت مخابراتی" diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 800f327f2a39b9ee587a15b02b1ced1b827720e4..b92616bc8f1f99df46e38f9a6b2692f70a75a6fa 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -582,8 +582,11 @@ "Äsken" "Tämä puhelin" "Tämä tabletti" + + + "Mikrofoni (sisäinen)" "Telinekaiutin" "Ulkoinen laite" "Yhdistetty laite" @@ -684,7 +687,14 @@ "Ei käytössä" "Käytössä" "Laitteesi on käynnistettävä uudelleen, jotta muutos tulee voimaan. Käynnistä uudelleen nyt tai peru." - "Langalliset kuulokkeet" + + + + + + + "Mikrofoniliitäntä" + "USB-mikrofoni" "Päällä" "Ei käytössä" "Operaattorin verkko muuttuu" diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index f36b5f00beb3af7ae45498d4835310e9ec01ebce..bf9adbae062212151288514bd765bc273e515d13 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -582,8 +582,11 @@ "À l\'instant" "Ce téléphone" "Cette tablette" + + + "Microphone (interne)" "Haut-parleur du socle" "Appareil externe" "Appareil connecté" @@ -684,7 +687,14 @@ "Désactivé" "Activé" "Votre appareil doit être redémarré pour que ce changement prenne effet. Redémarrez-le maintenant ou annulez la modification." - "Écouteurs filaires" + + + + + + + "Prise du microphone" + "Microphone USB" "Activé" "Désactivé" "Changer de réseau de fournisseur de services" diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 432770857815d4edf5d47c8f23a1c5e2e134360b..1f09856f341572ac44ad6fcc62cb3d8d8f69bc2f 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -564,8 +564,7 @@ "Alarmes et rappels" "Autoriser cette appli à définir des alarmes et à programmer des actions à certaines heures. Elle s\'exécutera alors en arrière-plan, ce qui peut solliciter davantage la batterie.\n\nSi l\'autorisation est désactivée, les alarmes existantes et les événements programmés par l\'appli ne fonctionneront pas." "définir, alarme, rappel, horloge" - - + "Ne pas déranger" "Ne pas déranger" "Activer" "Activer le mode Ne pas déranger" @@ -583,8 +582,12 @@ "À l\'instant" "Ce téléphone" "Cette tablette" + + + + "Haut-parleur station d\'accueil" "Appareil externe" "Appareil connecté" @@ -685,7 +688,16 @@ "Désactivé" "Activé" "Vous devez redémarrer l\'appareil pour que cette modification soit appliquée. Redémarrez maintenant ou annulez l\'opération." - "Casque filaire" + + + + + + + + + + "Allumé" "Éteint" "Modification du réseau de l\'opérateur" diff --git a/packages/SettingsLib/res/values-gl/arrays.xml b/packages/SettingsLib/res/values-gl/arrays.xml index e6d098f746cff0aab9170012b3516f7c24bbf3e7..8f7a8b6a79c84efb764a65bae657223e77ed98bc 100644 --- a/packages/SettingsLib/res/values-gl/arrays.xml +++ b/packages/SettingsLib/res/values-gl/arrays.xml @@ -26,10 +26,10 @@ "Conectando..." "Autenticando…" "Obtendo enderezo IP..." - "Conectada" + "Conectado" "Suspendida" "Desconectando..." - "Desconectada" + "Desconectado" "Incorrecta" "Bloqueada" "Evitando conexión deficiente temporalmente" diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index bac62f22f1fdb36d6dfa0fe1682c29ef1b327705..98a1401f35680142a3b8da25137d433db0d1082f 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -510,7 +510,7 @@ "Cargando sen fíos" "Cargando" "Non se está cargando" - "Conectado, pero non cargando" + "Conectado, pero sen cargar" "Cargada" "Carga completa" "Carga en pausa" @@ -564,7 +564,7 @@ "Alarmas e recordatorios" "Permite que esta aplicación defina alarmas e planifique accións que dependan da hora. Con este permiso, a aplicación pode executarse en segundo plano, o que pode provocar un maior consumo de batería.\n\nSe este permiso está desactivado, non funcionarán as alarmas que xa se definisen nin os eventos que dependan da hora planificados por esta aplicación." "planificar, alarma, recordatorio, reloxo" - "Modo Non molestar" + "Non molestar" "Non molestar" "Activar" "Activar modo Non molestar" @@ -582,8 +582,11 @@ "Agora mesmo" "Este teléfono" "Esta tableta" + + + "Micrófono (interno)" "Altofalante da base" "Dispositivo externo" "Dispositivo conectado" @@ -684,7 +687,14 @@ "Desactivado" "Activado" "É necesario reiniciar o teu dispositivo para aplicar este cambio. Reiníciao agora ou cancela o cambio." - "Auriculares con cable" + + + + + + + "Conector do micrófono" + "Micrófono USB" "Activada" "Desactivada" "Cambio de rede do operador" diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 9af8e48d818a24cd59ee213c0f25c01b8d8d3e98..0455dffdb6c5e47a0f6d8719dec4045e3bee61d8 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -582,8 +582,11 @@ "હમણાં જ" "આ ફોન" "આ ટૅબ્લેટ" + + + "માઇક્રોફોન (આંતરિક)" "ડૉક સ્પીકર" "બહારનું ડિવાઇસ" "કનેક્ટ કરેલું ડિવાઇસ" @@ -684,7 +687,14 @@ "બંધ છે" "ચાલુ છે" "આ ફેરફારને લાગુ કરવા માટે તમારા ડિવાઇસને રીબૂટ કરવાની જરૂર છે. હમણાં જ રીબૂટ કરો કે રદ કરો." - "વાયરવાળો હૅડફોન" + + + + + + + "માઇક જૅક" + "USB માઇક" "ચાલુ" "બંધ" "કૅરીઅર નેટવર્કમાં ફેરફાર થઈ રહ્યો છે" diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 785ef59df6d6acefa98fe02f4f570cd1a2ffe42f..6f0fde34150258455dcb7e965eee9cf31319643f 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -582,8 +582,11 @@ "अभी-अभी" "यह फ़ोन" "यह टैबलेट" + + + "माइक्रोफ़ोन (इंटरनल)" "डॉक स्पीकर" "बाहरी डिवाइस" "कनेक्ट किया गया डिवाइस" @@ -684,7 +687,14 @@ "बंद है" "चालू है" "बदली गई सेटिंग को लागू करने के लिए, डिवाइस को रीस्टार्ट करना होगा. अपने डिवाइस को रीस्टार्ट करें या रद्द करें." - "वायर वाला हेडफ़ोन" + + + + + + + "माइक्रोफ़ोन जैक" + "यूएसबी माइक्रोफ़ोन" "चालू है" "बंद है" "मोबाइल और इंटरनेट सेवा देने वाली कंपनी का नेटवर्क बदल रहा है" diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 107c5617998b94c57425dac4849084831e5f998c..2045fe77e372782c230f76d75d3751bdcdccd9fe 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -582,8 +582,11 @@ "Upravo sad" "Ovaj telefon" "Ovaj tablet" + + + "Mikrofon (ugrađeni)" "Zvučnik priključne stanice" "Vanjski uređaj" "Povezani uređaj" @@ -684,7 +687,14 @@ "Onemogućeno" "Omogućeno" "Uređaj se mora ponovno pokrenuti da bi se ta promjena primijenila. Ponovo pokrenite uređaj odmah ili odustanite." - "Žičane slušalice" + + + + + + + "Utičnica za mikrofon" + "USB mikrofon" "Uključeno" "Isključeno" "Promjena mreže mobilnog operatera" diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index dff22d9d3517bc97238ab3e928987eb5329571d6..62f4777c2337e68663f8c650de9ea1572fc3a33c 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -582,8 +582,11 @@ "Az imént" "Ez a telefon" "Ez a táblagép" + + + "Mikrofon (belső)" "Dokkhangszóró" "Külső eszköz" "Csatlakoztatott eszköz" @@ -684,7 +687,14 @@ "Letiltva" "Engedélyezve" "Az eszközt újra kell indítani, hogy a módosítás megtörténjen. Indítsa újra most, vagy vesse el a módosítást." - "Vezetékes fejhallgató" + + + + + + + "Mikrofon jack csatlakozója" + "USB-mikrofon" "Be" "Ki" "Szolgáltatói hálózat váltása" diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 60e5adfee7f1e4ec30d27802b669f7d0a74b3ff0..ba4e2043827f7d201b81724dd7c4f638aa76cbe5 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -582,8 +582,11 @@ "Հենց նոր" "Այս հեռախոսը" "Այս պլանշետը" + + + "Խոսափող (ներքին)" "Դոկ-կայանով բարձրախոս" "Արտաքին սարք" "Միացված սարք" @@ -684,7 +687,14 @@ "Անջատված է" "Միացված է" "Սարքն անհրաժեշտ է վերագործարկել, որպեսզի փոփոխությունը կիրառվի։ Վերագործարկեք հիմա կամ չեղարկեք փոփոխությունը։" - "Լարով ականջակալ" + + + + + + + "Խոսափողի հարակցիչ" + "USB խոսափող" "Միացնել" "Անջատել" "Օպերատորի ցանցի փոփոխություն" diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index f0b76cef02c6bf59dacf58ef09c463b8564bedcf..89d82f002fa9746355ff9e0616280119ac6a7a28 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -582,8 +582,11 @@ "Baru saja" "Ponsel ini" "Tablet ini" + + + "Mikrofon (internal)" "Speaker dok" "Perangkat Eksternal" "Perangkat yang terhubung" @@ -684,7 +687,14 @@ "Nonaktif" "Aktif" "Perangkat Anda harus di-reboot agar perubahan ini diterapkan. Reboot sekarang atau batalkan." - "Headphone berkabel" + + + + + + + "Colokan mikrofon" + "Mikrofon USB" "Aktif" "Nonaktif" "Jaringan operator berubah" diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 61813a392bc8c2fa1f36323d8341af3cf8eda330..b5d1831ad27a80cf7880f354d0e1d7e65734cd9e 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -582,8 +582,11 @@ "Rétt í þessu" "Þessi sími" "Þessi spjaldtölva" + + + "Hljóðnemi (innbyggður)" "Hátalaradokka" "Ytra tæki" "Tengt tæki" @@ -684,7 +687,14 @@ "Slökkt" "Virkt" "Endurræsa þarf tækið til að þessi breyting taki gildi. Endurræstu núna eða hættu við." - "Heyrnartól með snúru" + + + + + + + "Hljóðnematengi" + "USB-hljóðnemi" "Kveikt" "Slökkt" "Skiptir um farsímakerfi" diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 10394f1f597a364538f719c3d68772bec247fde7..60c6391c8c1dbcf61689153e00e7643e83d4c194 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -564,8 +564,7 @@ "Sveglie e promemoria" "Consenti a questa app di impostare sveglie e programmare azioni per orari specifici. L\'app potrà essere eseguita in background, comportando un consumo maggiore della batteria.\n\nSe questa autorizzazione viene disattivata, le sveglie esistenti e gli eventi basati sull\'orario programmati da questa app non funzioneranno." "programmare, sveglia, promemoria, orologio" - - + "Non disturbare" "Non disturbare" "Attiva" "Attiva Non disturbare" @@ -583,8 +582,12 @@ "Adesso" "Questo smartphone" "Questo tablet" + + + + "Base con altoparlante" "Dispositivo esterno" "Dispositivo connesso" @@ -685,7 +688,16 @@ "Non attivo" "Attivo" "Per applicare questa modifica, devi riavviare il dispositivo. Riavvia ora o annulla." - "Cuffie con cavo" + + + + + + + + + + "On" "Off" "Cambio della rete dell\'operatore" diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index c0f1e8782a740a09f85ec70cd573fd2f41b68367..986fcb978ae4b062b514650c2e05034882a3c3d3 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -582,8 +582,11 @@ "הרגע" "הטלפון הזה" "הטאבלט הזה" + + + "מיקרופון (פנימי)" "רמקול של אביזר העגינה" "מכשיר חיצוני" "המכשיר המחובר" @@ -684,7 +687,14 @@ "מושבת" "מופעל" "צריך להפעיל מחדש את המכשיר כדי להחיל את השינוי. יש להפעיל מחדש עכשיו או לבטל." - "אוזניות חוטיות" + + + + + + + "שקע למיקרופון" + "‏מיקרופון USB" "פועלת" "מצב כבוי" "רשת ספק משתנה" diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 71aae44b000b2913d3fbad6838e1e90424475163..bc8dee8282351b7fc1bc7d380b7e8e5bc94f99a3 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -582,8 +582,11 @@ "たった今" "このデバイス" "このタブレット" + + + "マイク(内蔵)" "ホルダー スピーカー" "外部デバイス" "接続済みのデバイス" @@ -684,7 +687,14 @@ "無効" "有効" "この変更を適用するには、デバイスの再起動が必要です。今すぐ再起動するか、キャンセルしてください。" - "有線ヘッドフォン" + + + + + + + "マイク差込口" + "USB マイク" "ON" "OFF" "携帯通信会社のネットワークを変更します" diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 7801417586dfbbef74d577037a135aa02cac4989..a6151f3d60039377deabe9ddb3291f7dec132953 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -582,8 +582,11 @@ "ახლახან" "ეს ტელეფონი" "ამ ტაბლეტზე" + + + "მიკროფონი (შიდა)" "სამაგრის დინამიკი" "გარე მოწყობილობა" "დაკავშირებული მოწყობილობა" @@ -684,7 +687,14 @@ "გათიშული" "ჩართული" "ამ ცვლილების ასამოქმედებლად თქვენი მოწყობილობა უნდა გადაიტვირთოს. გადატვირთეთ ახლავე ან გააუქმეთ." - "სადენიანი ყურსასმენი" + + + + + + + "მიკროფონის ჯეკი" + "USB მიკროფონი" "ჩართვა" "გამორთვა" "ოპერატორის ქსელის შეცვლა" diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 2ae8250189fd3b80ae3aab61815ca775388618a2..6789f409fc2e806221a6543ece9e4e6f976f9f8a 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -582,8 +582,11 @@ "Дәл қазір" "Осы телефон" "Осы планшет" + + + "Микрофон (ішкі)" "Динамигі бар қондыру станциясы" "Сыртқы құрылғы" "Жалғанған құрылғы" @@ -684,7 +687,14 @@ "Өшірулі" "Қосулы" "Бұл өзгеріс күшіне енуі үшін, құрылғыны қайта жүктеу керек. Қазір қайта жүктеңіз не бас тартыңыз." - "Сымды құлақаспап" + + + + + + + "Микрофон ұяшығы" + "USB микрофоны" "Қосу" "Өшіру" "Оператор желісін өзгерту" diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 85801c9cd6068872e950a4b2d43a4fbc2061e035..933dee798cfcd00f04bcc9170860ae01eeac528e 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -582,8 +582,11 @@ "អម្បាញ់មិញ" "ទូរសព្ទនេះ" "ថេប្លេតនេះ" + + + "មីក្រូហ្វូន (ខាងក្នុង)" "ឧបាល័រជើងទម្រ" "ឧបករណ៍ខាងក្រៅ" "​ឧបករណ៍ដែលបាន​ភ្ជាប់" @@ -684,7 +687,14 @@ "បានបិទ" "បានបើក" "ត្រូវតែ​ចាប់ផ្ដើម​ឧបករណ៍​របស់អ្នក​ឡើងវិញ ដើម្បីឱ្យ​ការផ្លាស់ប្ដូរ​នេះ​មានប្រសិទ្ធភាព។ ចាប់ផ្ដើមឡើងវិញ​ឥឡូវនេះ ឬ​បោះបង់​។" - "កាស​មានខ្សែ" + + + + + + + "ឌុយ​មីក្រូហ្វូន" + "មីក្រូហ្វូន USB" "បើក" "បិទ" "បណ្តាញ​ក្រុមហ៊ុនសេវាទូរសព្ទ​កំពុងផ្លាស់ប្តូរ" diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 6dc3ae9d2ea13d38d7c50902ff2878ea542a4824..29417d600dc3eeddbe1b250a8f18a504bdea2462 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -582,8 +582,11 @@ "ಇದೀಗ" "ಈ ಫೋನ್" "ಈ ಟ್ಯಾಬ್ಲೆಟ್" + + + "ಮೈಕ್ರೊಫೋನ್‌ (ಆಂತರಿಕ)" "ಡಾಕ್ ಸ್ಪೀಕರ್" "ಬಾಹ್ಯ ಸಾಧನ" "ಕನೆಕ್ಟ್ ಮಾಡಿರುವ ಸಾಧನ" @@ -684,7 +687,14 @@ "ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" "ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" "ಈ ಬದಲಾವಣೆ ಅನ್ವಯವಾಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ರೀಬೂಟ್ ಮಾಡಬೇಕು. ಇದೀಗ ರೀಬೂಟ್ ಮಾಡಿ ಅಥವಾ ರದ್ದುಗೊಳಿಸಿ." - "ವೈಯರ್ ಹೊಂದಿರುವ ಹೆಡ್‌ಫೋನ್" + + + + + + + "ಮೈಕ್‌ ಜ್ಯಾಕ್‌" + "USB ಮೈಕ್‌" "ಆನ್ ಆಗಿದೆ" "ಆಫ್" "ವಾಹಕ ನೆಟ್‌ವರ್ಕ್ ಬದಲಾಯಿಸುವಿಕೆ" diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 14e8f4f1561bd6b26a8a559fed9d8e910d07b4b9..4b0cfb4802d75cb6cc3392fe5b8f5df48a6f4eee 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -582,8 +582,11 @@ "조금 전" "이 휴대전화" "이 태블릿" + + + "마이크(내부)" "도크 스피커" "외부 기기" "연결된 기기" @@ -684,7 +687,14 @@ "사용 중지됨" "사용 설정됨" "변경사항을 적용하려면 기기를 재부팅해야 합니다. 지금 재부팅하거나 취소하세요." - "유선 헤드폰" + + + + + + + "마이크 잭" + "USB 마이크" "사용" "사용 안 함" "이동통신사 네트워크 변경" diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 9ff62cf0f96aa823f59bde752ce24f9f8f9da828..17b55c4ef97aba63d34cdebe1004477982630c8b 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -582,8 +582,11 @@ "Жаңы эле" "Ушул телефон" "Ушул планшет" + + + "Микрофон (ички)" "Док бекеттин динамиги" "Тышкы түзмөк" "Туташкан түзмөк" @@ -684,7 +687,14 @@ "Өчүк" "Күйүк" "Бул өзгөрүү күчүнө кириши үчүн, түзмөктү өчүрүп күйгүзүңүз. Азыр же кийинчерээк өчүрүп күйгүзсөңүз болот." - "Зымдуу гарнитура" + + + + + + + "Микрофондун оюкчасы" + "USB микрофон" "Күйгүзүү" "Өчүрүү" "Байланыш оператору өзгөртүлүүдө" diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 5d5a4c6188a545b57e9325d787b91b1ecaf6cf55..e6e8ef89fb7406f3abafc93b781214696d8ef3f8 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -582,8 +582,11 @@ "ຕອນນີ້" "ໂທລະສັບນີ້" "ແທັບເລັດນີ້" + + + "ໄມໂຄຣໂຟນ (ພາຍໃນ)" "ແທ່ນວາງລຳໂພງ" "ອຸປະກອນພາຍນອກ" "ອຸປະກອນທີ່ເຊື່ອມຕໍ່" @@ -684,7 +687,14 @@ "ປິດການນຳໃຊ້ແລ້ວ" "ເປີດການນຳໃຊ້ແລ້ວ" "ທ່ານຕ້ອງປິດເປີດອຸປະກອນຄືນໃໝ່ເພື່ອນຳໃຊ້ການປ່ຽນແປງນີ້. ປິດເປີດໃໝ່ດຽວນີ້ ຫຼື ຍົກເລີກ." - "ຫູຟັງແບບມີສາຍ" + + + + + + + "ຊ່ອງສຽງໄມ" + "ໄມ USB" "ເປີດ" "ປິດ" "ການປ່ຽນເຄືອຂ່າຍຜູ້ໃຫ້ບໍລິການ" diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 2b0b71d368e5baabc1b2ac74e6ea1d64dd5f1a26..5945e2a38a7c2999ba9a87ad2c1f311f65a41603 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -582,8 +582,11 @@ "Ką tik" "Šis telefonas" "Šis planšetinis kompiuteris" + + + "Mikrofonas (vidinis)" "Doko garsiakalbis" "Išorinis įrenginys" "Prijungtas įrenginys" @@ -684,7 +687,14 @@ "Išjungta" "Įgalinta" "Kad pakeitimas būtų pritaikytas, įrenginį reikia paleisti iš naujo. Dabar paleiskite iš naujo arba atšaukite." - "Laidinės ausinės" + + + + + + + "Mikrofono jungtis" + "USB mikrofonas" "Įjungta" "Išjungta" "Keičiamas operatoriaus tinklas" diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index a8a4ea063cb7d5dd838f98fc209ebdccfc7861b8..34f6c4c2c1205dcace14fdd1262cec1c16b64fcb 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -582,8 +582,11 @@ "Tikko" "Šis tālrunis" "Šis planšetdators" + + + "Mikrofons (iebūvētais)" "Doka skaļrunis" "Ārēja ierīce" "Pievienotā ierīce" @@ -684,7 +687,14 @@ "Atspējots" "Iespējots" "Lai šīs izmaiņas tiktu piemērotas, nepieciešama ierīces atkārtota palaišana. Atkārtoti palaidiet to tūlīt vai atceliet izmaiņas." - "Vadu austiņas" + + + + + + + "Mikrofona ligzda" + "USB mikrofons" "Ieslēgts" "Izslēgts" "Mobilo sakaru operatora tīkla mainīšana" diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 923ea5cc046aa5240c30130035e5cb017b418dfd..a8eb6072a85349cca379de71a4da93ed0676ad22 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -582,8 +582,11 @@ "Пред малку" "Овој телефон" "Овој таблет" + + + "Микрофон (внатрешен)" "Док со звучник" "Надворешен уред" "Поврзан уред" @@ -684,7 +687,14 @@ "Оневозможено" "Овозможено" "За да се примени променава, уредот мора да се рестартира. Рестартирајте сега или откажете." - "Жичени слушалки" + + + + + + + "Приклучок за микрофон" + "USB-микрофон" "Вклучено" "Исклучено" "Променување на мрежата на операторот" diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index 05579fec0a100e3054d16f3b2bc960934249c5e4..753aa3297179b8cb2a1e33465e2ffbbe50aa4639 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -582,8 +582,11 @@ "ഇപ്പോൾ" "ഈ ഫോൺ" "ഈ ടാബ്‌ലെറ്റ്" + + + "മൈക്രോഫോൺ (ഇന്റേണൽ)" "ഡോക്ക് സ്‌പീക്കർ" "ബാഹ്യ ഉപകരണം" "കണക്‌റ്റ് ചെയ്‌ത ഉപകരണം" @@ -684,7 +687,14 @@ "പ്രവർത്തനരഹിതമാക്കി" "പ്രവർത്തനക്ഷമമാക്കി" "ഈ മാറ്റം ബാധകമാകുന്നതിന് നിങ്ങളുടെ ഉപകരണം റീബൂട്ട് ചെയ്യേണ്ടതുണ്ട്. ഇപ്പോൾ റീബൂട്ട് ചെയ്യുകയോ റദ്ദാക്കുകയോ ചെയ്യുക." - "വയേർഡ് ഹെഡ്ഫോൺ" + + + + + + + "മൈക്ക് ജാക്ക്" + "USB മൈക്ക്" "ഓണാണ്" "ഓഫാണ്" "കാരിയർ നെറ്റ്‌വർക്ക് മാറ്റൽ" diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 8957a2b174a10b384ab660aa024b9deaa17686a1..bc953d374a914c2956e09ec1b3275cad1fd87a9b 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -582,8 +582,11 @@ "Дөнгөж сая" "Энэ утас" "Энэ таблет" + + + "Микрофон (дотоод)" "Суурилуулагчийн чанга яригч" "Гадаад төхөөрөмж" "Холбогдсон төхөөрөмж" @@ -684,7 +687,14 @@ "Идэвхгүй болгосон" "Идэвхжүүлсэн" "Энэ өөрчлөлтийг хэрэгжүүлэхийн тулд таны төхөөрөмжийг дахин асаах ёстой. Одоо дахин асаах эсвэл цуцлана уу." - "Утастай чихэвч" + + + + + + + "Микрофоны чихэвчний оролт" + "USB микрофон" "Асаах" "Унтраах" "Оператор компанийн сүлжээг өөрчилж байна" diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 36b967e3131f1d233c6e210966a1cd2ece30c9ad..483936ae83514d5a1b8f41e353bd30c6aa1d57db 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -582,8 +582,11 @@ "आत्ताच" "हा फोन" "हा टॅबलेट" + + + "मायक्रोफोन (अंतर्गत)" "डॉक स्पीकर" "बाह्य डिव्हाइस" "कनेक्ट केलेले डिव्हाइस" @@ -684,7 +687,14 @@ "बंद केले आहे" "सुरू केले आहे" "हा बदल लागू करण्यासाठी तुमचे डिव्हाइस रीबूट करणे आवश्यक आहे. आता रीबूट करा किंवा रद्द करा." - "वायर असलेला हेडफोन" + + + + + + + "माइक जॅक" + "USB माइक" "सुरू करा" "बंद करा" "वाहक नेटवर्क बदलत आहे" diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 2db04be96e211a8a5f089eec2e6b2301f4e5f590..dbe745dc8c3cd28edbd76c4b85447b3a5ca32839 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -582,8 +582,11 @@ "Sebentar tadi" "Telefon ini" "Tablet ini" + + + "Mikrofon (dalaman)" "Pembesar suara dok" "Peranti Luar" "Peranti yang disambungkan" @@ -684,7 +687,14 @@ "Dilumpuhkan" "Didayakan" "Peranti anda mesti dibut semula supaya perubahan ini berlaku. But semula sekarang atau batalkan." - "Fon kepala berwayar" + + + + + + + "Bicu mikrofon" + "Mikrofon USB" "Hidup" "Mati" "Rangkaian pembawa berubah" diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 660fd146e8bd5c2080ee0b6450e18ff35b1b6e59..063fc9c1f268f6f063101307c90f4f53026edf68 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -582,8 +582,11 @@ "ယခုလေးတင်" "ဤဖုန်း" "ဤတက်ဘလက်" + + + "မိုက်ခရိုဖုန်း (စက်တွင်း)" "အထိုင် စပီကာ" "ပြင်ပစက်" "ချိတ်ဆက်ကိရိယာ" @@ -684,7 +687,14 @@ "ပိတ်ထားသည်" "ဖွင့်ထားသည်" "ဤအပြောင်းအလဲ ထည့်သွင်းရန် သင့်စက်ကို ပြန်လည်စတင်ရမည်။ ယခု ပြန်လည်စတင်ပါ သို့မဟုတ် ပယ်ဖျက်ပါ။" - "ကြိုးတပ်နားကြပ်" + + + + + + + "မိုက်ဂျက်ပင်" + "USB မိုက်" "ဖွင့်" "ပိတ်" "ဝန်ဆောင်မှုပေးသူ ကွန်ရက် ပြောင်းလဲနေသည်။" diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 3e5d141616b16078405f7427763c83f9e7cb408e..9036c8c80158cf04817c2343493db1143d2d6165 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -582,8 +582,11 @@ "Nå nettopp" "Denne telefonen" "Dette nettbrettet" + + + "Mikrofon (intern)" "Dokkhøyttaler" "Ekstern enhet" "Tilkoblet enhet" @@ -684,7 +687,14 @@ "Slått av" "Slått på" "Enheten din må startes på nytt for at denne endringen skal tre i kraft. Start på nytt nå eller avbryt." - "hodetelefoner med kabel" + + + + + + + "Mikrofonkontakt" + "USB-mikrofon" "På" "Av" "Bytting av operatørnettverk" diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index ad2888285c429b245ecd5ea592644523db5acbbf..09240f096703229549b7e887f46d7cec2810faba 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -582,8 +582,11 @@ "अहिले भर्खरै" "यो फोन" "यो ट्याब्लेट" + + + "माइक्रोफोन (आन्तरिक)" "डक स्पिकर" "बाह्य डिभाइस" "कनेक्ट गरिएको डिभाइस" @@ -684,7 +687,14 @@ "असक्षम पारिएको छ" "सक्षम पारिएको छ" "यो परिवर्तन लागू गर्न तपाईंको यन्त्र अनिवार्य रूपमा रिबुट गर्नु पर्छ। अहिले रिबुट गर्नुहोस् वा रद्द गर्नुहोस्।" - "तारसहितको हेडफोन" + + + + + + + "माइकको ज्याक" + "USB माइक" "अन छ" "अफ छ" "सेवा प्रदायकको नेटवर्क परिवर्तन गर्ने आइकन" diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 0fcc6fecf578af4c1598b66c234eadeb1af40fe0..0117acff9d486a1ad70b008dbdfa49256d79bdd4 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -577,13 +577,16 @@ "op %1$s" "Duur" "Altijd vragen" - "Totdat je uitzet" + "Totdat je ze uitzet" "(Geen naam)" "Zojuist" "Deze telefoon" "Deze tablet" + + + "Microfoon (intern)" "Dockspeaker" "Extern apparaat" "Verbonden apparaat" @@ -684,7 +687,14 @@ "Uit" "Aan" "Je apparaat moet opnieuw worden opgestart om deze wijziging toe te passen. Start nu opnieuw op of annuleer de wijziging." - "Bedrade koptelefoon" + + + + + + + "Microfoonaansluiting" + "USB-microfoon" "Aan" "Uit" "Netwerk van provider wordt gewijzigd" diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 694d034812c883e2f71e6b0a754554114afeee23..1b2e4217fa5fb511d11f4ead648ab8c9fadcd208 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -582,8 +582,11 @@ "ଏହିକ୍ଷଣି" "ଏହି ଫୋନ" "ଏହି ଟାବଲେଟ" + + + "ମାଇକ୍ରୋଫୋନ (ଇଣ୍ଟର୍ନଲ)" "ଡକ ସ୍ପିକର" "ଏକ୍ସଟର୍ନଲ ଡିଭାଇସ" "କନେକ୍ଟ କରାଯାଇଥିବା ଡିଭାଇସ" @@ -684,7 +687,14 @@ "ଅକ୍ଷମ କରାଯାଇଛି" "ସକ୍ଷମ କରାଯାଇଛି" "ଏହି ପରିବର୍ତ୍ତନ ଲାଗୁ କରିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିଶ୍ଚିତ ରୂପେ ରିବୁଟ୍ କରାଯିବା ଆବଶ୍ୟକ। ବର୍ତ୍ତମାନ ରିବୁଟ୍ କରନ୍ତୁ କିମ୍ବା ବାତିଲ କରନ୍ତୁ।" - "ତାରଯୁକ୍ତ ହେଡଫୋନ୍" + + + + + + + "ମାଇକ ଜେକ" + "USB ମାଇକ" "ଚାଲୁ ଅଛି" "ବନ୍ଦ ଅଛି" "କେରିଅର୍‍ ନେଟ୍‌ୱର୍କ ବଦଳୁଛି" diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index d0cf8205aa92f56509479e1f284acd663df585a6..c668e12560093122553306caae875469637e1b9a 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -582,8 +582,11 @@ "ਹੁਣੇ ਹੀ" "ਇਹ ਫ਼ੋਨ" "ਇਹ ਟੈਬਲੈੱਟ" + + + "ਮਾਈਕ੍ਰੋਫ਼ੋਨ (ਅੰਦਰੂਨੀ)" "ਡੌਕ ਸਪੀਕਰ" "ਬਾਹਰੀ ਡੀਵਾਈਸ" "ਕਨੈਕਟ ਕੀਤਾ ਡੀਵਾਈਸ" @@ -684,7 +687,14 @@ "ਬੰਦ ਕੀਤਾ ਗਿਆ" "ਚਾਲੂ ਕੀਤਾ ਗਿਆ" "ਇਸ ਤਬਦੀਲੀ ਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਰੀਬੂਟ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ। ਹੁਣੇ ਰੀਬੂਟ ਕਰੋ ਜਾਂ ਰੱਦ ਕਰੋ।" - "ਤਾਰ ਵਾਲੇ ਹੈੱਡਫ਼ੋਨ" + + + + + + + "ਮਾਈਕ ਜੈਕ" + "USB ਮਾਈਕ" "ਚਾਲੂ" "ਬੰਦ" "ਕੈਰੀਅਰ ਨੈੱਟਵਰਕ ਦੀ ਬਦਲੀ" diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index c6d78f6f9e20600a451814bd37771e6f49085a59..7e90b78ec1c562defead62ed1b5f8ad53f47edf2 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -582,8 +582,11 @@ "Przed chwilą" "Ten telefon" "Ten tablet" + + + "Mikrofon (wewnętrzny)" "Głośnik ze stacją dokującą" "Urządzenie zewnętrzne" "Połączone urządzenie" @@ -684,7 +687,14 @@ "Wyłączono" "Włączono" "Wprowadzenie zmiany wymaga ponownego uruchomienia urządzenia. Uruchom ponownie teraz lub anuluj." - "Słuchawki przewodowe" + + + + + + + "Gniazdo mikrofonu" + "Mikrofon USB" "Włączono" "Wyłączono" "Zmiana sieci operatora" diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 98bda8519c504dfc7193d24a64054c2ddf0107c7..e10ad07199176814c5e7f84670662bf2fff19284 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -582,8 +582,11 @@ "Agora" "Este telefone" "Este tablet" + + + "Microfone (interno)" "Alto-falante da base" "Dispositivo externo" "Dispositivo conectado" @@ -684,7 +687,14 @@ "Desativado" "Ativado" "É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele." - "Fones de ouvido com fio" + + + + + + + "Entrada para microfone" + "Microfone USB" "Ativado" "Desativado" "Alteração de rede da operadora" diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 20acb00d89e5c1c328310b0148140ef43085bf5b..92ab714d75b058c95c8ab265eba57a6553b62e37 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -582,8 +582,11 @@ "Agora mesmo" "Este telemóvel" "Este tablet" + + + "Microfone (interno)" "Altifalante estação carregamento" "Dispositivo externo" "Dispositivo associado" @@ -684,7 +687,14 @@ "Desativada" "Ativada" "É necessário reiniciar o dispositivo para aplicar esta alteração. Reinicie agora ou cancele." - "Auscultadores com fios" + + + + + + + "Entrada para microfone" + "Microfone USB" "Ligado" "Desligado" "Rede do operador em mudança." diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 98bda8519c504dfc7193d24a64054c2ddf0107c7..e10ad07199176814c5e7f84670662bf2fff19284 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -582,8 +582,11 @@ "Agora" "Este telefone" "Este tablet" + + + "Microfone (interno)" "Alto-falante da base" "Dispositivo externo" "Dispositivo conectado" @@ -684,7 +687,14 @@ "Desativado" "Ativado" "É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele." - "Fones de ouvido com fio" + + + + + + + "Entrada para microfone" + "Microfone USB" "Ativado" "Desativado" "Alteração de rede da operadora" diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index a6d17c19d6c1988a982ea6136840416b75556ca4..e71063464921ddcc571bb966eea9bcab44624e8f 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -582,8 +582,11 @@ "Chiar acum" "Acest telefon" "Această tabletă" + + + "Microfon (intern)" "Difuzorul dispozitivului de andocare" "Dispozitiv extern" "Dispozitiv conectat" @@ -684,7 +687,14 @@ "Dezactivat" "Activat" "Pentru ca modificarea să se aplice, trebuie să repornești dispozitivul. Repornește-l acum sau anulează." - "Căști cu fir" + + + + + + + "Mufă pentru microfon" + "Microfon USB" "Activat" "Dezactivat" "Se schimbă rețeaua operatorului" diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 838b5eda157343a3d5c26ce167bc42fac1a42d52..009e071e453676e9143657b0b3a56ead1c41c867 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -582,8 +582,11 @@ "Только что" "Этот смартфон" "Этот планшет" + + + "Микрофон (встроенный)" "Колонка с док-станцией" "Внешнее устройство" "Подключенное устройство" @@ -684,7 +687,14 @@ "Отключено" "Включено" "Чтобы изменение вступило в силу, необходимо перезапустить устройство. Вы можете сделать это сейчас или позже." - "Проводные наушники" + + + + + + + "Микрофонный разъем" + "USB-микрофон" "Вкл." "Выкл." "Сменить сеть" diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index cc7a45eb344f05a8b51d3ec4bc098558c8194afc..27154f62c28d94827fe7d79712cecef52582c9e3 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -582,8 +582,11 @@ "මේ දැන්" "මෙම දුරකථනය" "මෙම ටැබ්ලටය" + + + "මයික්‍රෆෝනය (අභ්‍යන්තර)" "ඩොක් ස්පීකරය" "බාහිර උපාංගය" "සම්බන්ධ කළ උපාංගය" @@ -684,7 +687,14 @@ "අබල කළා" "සබලයි" "මෙම වෙනස යෙදීමට ඔබේ උපාංගය නැවත පණ ගැන්විය යුතුය. දැන් නැවත පණ ගන්වන්න හෝ අවලංගු කරන්න." - "රැහැන්ගත කළ හෙඩ්ෆෝන්" + + + + + + + "මයික් ජැක්කුව" + "USB මයික්" "ක්‍රියාත්මකයි" "ක්‍රියාවිරහිතයි" "වාහක ජාලය වෙනස් වෙමින්" diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 43db02b48ce9d2d22f8e10d9c5dc9da38a5eaf05..ddb35d6f21194b18f068bf6ef591c578ceb1e0b0 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -582,8 +582,11 @@ "Teraz" "Tento telefón" "Tento tablet" + + + "Mikrofón (vnútorný)" "Reproduktor doku" "Externé zariadenie" "Pripojené zariadenie" @@ -684,7 +687,14 @@ "Vypnuté" "Zapnuté" "Zmena sa prejaví až po reštarte zariadenia. Môžete ho teraz reštartovať alebo akciu zrušiť." - "Slúchadlá s káblom" + + + + + + + "Konektor mikrofónu" + "Mikrofón USB" "Zapnúť" "Vypnúť" "Mení sa sieť operátora" diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index ddd0fb235be6c7e91dce58ff0c3945d1c90bd051..4ca5c489c4913bad980c65ead4ea55bfbb34cd14 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -582,8 +582,11 @@ "Pravkar" "Ta telefon" "Ta tablični računalnik" + + + "Mikrofon (notranji)" "Zvočnik nosilca" "Zunanja naprava" "Povezana naprava" @@ -684,7 +687,14 @@ "Onemogočeno" "Omogočeno" "Napravo je treba znova zagnati, da bo ta sprememba uveljavljena. Znova zaženite zdaj ali prekličite." - "Žične slušalke" + + + + + + + "Vtič za mikrofon" + "Mikrofon USB" "Vklop" "Izklop" "Spreminjanje omrežja operaterja" diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 3831cf4e3ec6d50f6e20ee7c836971e1ee18e664..d99f2c8f8ef1023a73cab7f6f51510ea3c09d8bc 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -582,8 +582,11 @@ "Pikërisht tani" "Ky telefon" "Ky tablet" + + + "Mikrofoni (i brendshëm)" "Altoparlanti i stacionit" "Pajisja e jashtme" "Pajisja e lidhur" @@ -684,7 +687,14 @@ "Joaktiv" "Aktiv" "Pajisja jote duhet të riniset që ky ndryshim të zbatohet. Rinise tani ose anuloje." - "Kufje me tela" + + + + + + + "Fisha e mikrofonit" + "Mikrofoni me USB" "Aktive" "Joaktive" "Rrjeti i operatorit celular po ndryshohet" diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 73794bd5f1be1be2cb1c526c6c0666a8d7561ffe..cc7c69dbc95754fad842536b598dbc64ded59cf0 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -582,8 +582,11 @@ "Управо" "Овај телефон" "Овај таблет" + + + "Микрофон (интерни)" "Звучник базне станице" "Спољни уређај" "Повезани уређај" @@ -684,7 +687,14 @@ "Онемогућено" "Омогућено" "Морате да рестартујете уређај да би се ова промена применила. Рестартујте га одмах или откажите." - "Жичане слушалице" + + + + + + + "Утикач за микрофон" + "USB микрофон" "Укључено" "Искључено" "Промена мреже мобилног оператера" diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index f47bdabaa59489810e5d33058411d846af3f0c3b..6b76f879d04f4765c0dc19d3b2928055dbf9f43f 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -582,8 +582,11 @@ "Nyss" "Den här telefonen" "Den här surfplattan" + + + "Mikrofon (inbyggd)" "Dockningsstationens högtalare" "Extern enhet" "Ansluten enhet" @@ -684,7 +687,14 @@ "Inaktiverat" "Aktiverat" "Enheten måste startas om för att ändringen ska börja gälla. Starta om nu eller avbryt." - "Hörlurar med sladd" + + + + + + + "Mikrofonuttag" + "USB-mikrofon" "På" "Av" "Byter leverantörsnätverk" diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 02db3e13c8bf358b45641af68452acc289935509..f113e6220b5153c61632d076645eeaad2d08ac7f 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -582,8 +582,11 @@ "Sasa hivi" "Simu hii" "Kishikwambi hiki" + + + "Maikrofoni (ya ndani)" "Spika ya kituo" "Kifaa cha Nje" "Kifaa kilichounganishwa" @@ -684,7 +687,14 @@ "Imezimwa" "Imewashwa" "Ni lazima uwashe tena kifaa chako ili mabadiliko haya yatekelezwe. Washa tena sasa au ughairi." - "Vipokea sauti vya waya" + + + + + + + "Pini ya maikrofoni" + "Maikrofoni ya USB" "Umewashwa" "Umezimwa" "Mabadiliko katika mtandao wa mtoa huduma" diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index a5a96f20b8e11155d6c18a96b3fc08c5f1fb1587..beca7380b0071013e81d59eb54705d232057127f 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -582,8 +582,11 @@ "சற்றுமுன்" "இந்த மொபைல்" "இந்த டேப்லெட்" + + + "மைக்ரோஃபோன் (அகம்)" "டாக் ஸ்பீக்கர்" "வெளிப்புறச் சாதனம்" "இணைக்கப்பட்டுள்ள சாதனம்" @@ -684,7 +687,14 @@ "முடக்கப்பட்டது" "இயக்கப்பட்டது" "இந்த மாற்றங்கள் செயல்படுத்தப்பட உங்கள் சாதனத்தை மறுபடி தொடங்க வேண்டும். இப்போதே மறுபடி தொடங்கவும் அல்லது ரத்துசெய்யவும்." - "வயருள்ள ஹெட்ஃபோன்" + + + + + + + "மைக் ஜாக்" + "USB மைக்" "ஆன்" "ஆஃப்" "மொபைல் நிறுவன நெட்வொர்க்கை மாற்றும்" diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 5577389cafa8ba30daadad0b2e854348dbf3b3aa..5fb829ddfaa9f6b2ab83182f1117e2fc4eca9cff 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -582,8 +582,11 @@ "ఇప్పుడే" "ఈ ఫోన్" "ఈ టాబ్లెట్" + + + "మైక్రోఫోన్ (అంతర్గతం)" "డాక్ స్పీకర్" "ఎక్స్‌టర్నల్ పరికరం" "కనెక్ట్ చేసిన పరికరం" @@ -684,7 +687,14 @@ "డిజేబుల్ చేయబడింది" "ఎనేబుల్ చేయబడింది" "ఈ మార్పును వర్తింపజేయాలంటే మీరు మీ పరికరాన్ని తప్పనిసరిగా రీబూట్ చేయాలి. ఇప్పుడే రీబూట్ చేయండి లేదా రద్దు చేయండి." - "వైర్ ఉన్న హెడ్‌ఫోన్" + + + + + + + "మైక్ జాక్" + "USB మైక్" "ఆన్‌లో ఉంది" "ఆఫ్‌లో ఉంది" "క్యారియర్ నెట్‌వర్క్ మారుతోంది" diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 48087c27cf9376d202c730777a77d253d16a4542..aa23a44fe1fb694c052ef2ff04abdfc2c47dea3c 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -582,8 +582,11 @@ "เมื่อสักครู่" "โทรศัพท์เครื่องนี้" "แท็บเล็ตเครื่องนี้" + + + "ไมโครโฟน (ภายใน)" "แท่นชาร์จที่มีลำโพง" "อุปกรณ์ภายนอก" "อุปกรณ์ที่เชื่อมต่อ" @@ -684,7 +687,14 @@ "ปิดใช้" "เปิดใช้" "คุณต้องรีบูตอุปกรณ์เพื่อให้การเปลี่ยนแปลงนี้มีผล รีบูตเลยหรือยกเลิก" - "หูฟังแบบมีสาย" + + + + + + + "ช่องเสียบไมค์" + "ไมค์ USB" "เปิด" "ปิด" "การเปลี่ยนเครือข่ายผู้ให้บริการ" diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 34d51d4434e9a27769bfd84572dd07931a8c1929..5a1d4d79831db20cb5ca44efc278642b55c00ef6 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -582,8 +582,11 @@ "Ngayon lang" "Ang teleponong ito" "Ang tablet na ito" + + + "Mikropono (internal)" "Speaker ng dock" "External na Device" "Nakakonektang device" @@ -684,7 +687,14 @@ "Naka-disable" "Na-enable" "Dapat i-reboot ang iyong device para mailapat ang pagbabagong ito. Mag-reboot ngayon o kanselahin." - "Wired na headphone" + + + + + + + "Jack ng mikropono" + "USB na mikropono" "Naka-on" "Naka-off" "Nagpapalit ng carrier network" diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 9b5cc2926479dafe19c46bb1bc805c6ea9c89df6..d7ad6b1a31842a0bd37963d6c24786fa54c8ea27 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -582,8 +582,11 @@ "Az önce" "Bu telefon" "Bu tablet" + + + "Mikrofon (dahili)" "Yuva hoparlörü" "Harici Cihaz" "Bağlı cihaz" @@ -684,7 +687,14 @@ "Devre dışı" "Etkin" "Bu değişikliğin geçerli olması için cihazınızın yeniden başlatılması gerekir. Şimdi yeniden başlatın veya iptal edin." - "Kablolu kulaklık" + + + + + + + "Mikrofon jakı" + "USB mikrofon" "Açık" "Kapalı" "Operatör ağı değiştiriliyor" diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index a3967987340f40253946a74124a7aba49f7400cf..2de4c097775c9f98e4c1e9326cb42436f2680485 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -577,13 +577,16 @@ "%1$s" "Тривалість" "Запитувати щоразу" - "Доки не вимкнути" + "Доки ви не вимкнете" "(Без імені)" "Щойно" "Цей телефон" "Цей планшет" + + + "Мікрофон (внутрішній)" "Динамік док-станції" "Зовнішній пристрій" "Підключений пристрій" @@ -684,7 +687,14 @@ "Вимкнено" "Увімкнено" "Щоб застосувати ці зміни, потрібний перезапуск. Перезапустіть пристрій або скасуйте зміни." - "Дротові навушники" + + + + + + + "Гніздо для мікрофона" + "USB-мікрофон" "Увімкнено" "Вимкнено" "Змінення мережі оператора" diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index c60a58bb41ba16499a36fe0046f8aa8d1f0b7c25..e2fbfab708c544be08325a22487e1450866013ff 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -582,8 +582,11 @@ "ابھی ابھی" "یہ فون" "یہ ٹیبلیٹ" + + + "مائیکروفون (داخلی)" "ڈاک اسپیکر" "بیرونی آلہ" "منسلک آلہ" @@ -684,7 +687,14 @@ "غیر فعال" "فعال" "اس تبدیلی کو لاگو کرنے کے ليے آپ کے آلہ کو ریبوٹ کرنا ضروری ہے۔ ابھی ریبوٹ کریں یا منسوخ کریں۔" - "وائرڈ ہیڈ فون" + + + + + + + "مائیک جیک" + "‏‫USB مائیک" "آن" "آف" "کیریئر نیٹ ورک کی تبدیلی" diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 3beb5148844d74ef51eeda7ccba2f727c9794aea..40d1d10f234b4b2d682516adf830ee950e855dfa 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -582,8 +582,11 @@ "Hozir" "Shu telefon" "Shu planshet" + + + "Mikrofon (ichki)" "Dok-stansiyali karnay" "Tashqi qurilma" "Ulangan qurilma" @@ -684,7 +687,14 @@ "Oʻchiq" "Yoniq" "Oʻzgarishlar kuchga kirishi uchun qurilmani oʻchirib yoqing. Buni hozir yoki keyinroq bajarishingiz mumkin." - "Simli quloqlik" + + + + + + + "Mikrofon ulagichi" + "USB mik" "Yoniq" "Oʻchiq" "Mobil tarmoqni o‘zgartirish" diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index d3152f4c785060afe87bcfbfddb063639270fcf9..874aa18dc70157875996539cd2bf101013bd63d5 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -582,8 +582,11 @@ "Vừa xong" "Điện thoại này" "Máy tính bảng này" + + + "Micrô (bên trong)" "Loa có gắn đế" "Thiết bị bên ngoài" "Thiết bị đã kết nối" @@ -684,7 +687,14 @@ "Đã tắt" "Đã bật" "Bạn phải khởi động lại thiết bị để áp dụng sự thay đổi này. Hãy khởi động lại ngay hoặc hủy." - "Tai nghe có dây" + + + + + + + "Giắc cắm micrô" + "Micrô có cổng USB" "Đang bật" "Đang tắt" "Thay đổi mạng của nhà mạng" diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 964c3dacafc20d6e4db1f72923d291be11b10cd0..8edc656b461452743bb6654ba553d30676bd4fb6 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -582,8 +582,11 @@ "刚刚" "这部手机" "这部平板电脑" + + + "麦克风(内部)" "基座音箱" "外部设备" "连接的设备" @@ -684,7 +687,14 @@ "已停用" "已启用" "设备必须重新启动才能应用此更改。您可以立即重新启动或取消。" - "有线耳机" + + + + + + + "麦克风插孔" + "USB 麦克风" "开启" "关闭" "运营商网络正在更改" diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index a35b9f23667c285abd68cd5560e8e1e7291f401e..533a53fc69cc2dd0decbc154cc6c3363990b64fd 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -582,8 +582,11 @@ "剛剛" "此手機" "此平板電腦" + + + "麥克風 (內置)" "插座喇叭" "外部裝置" "已連接的裝置" @@ -684,7 +687,14 @@ "已停用" "已啟用" "你的裝置必須重新開機,才能套用此變更。請立即重新開機或取消。" - "有線耳機" + + + + + + + "麥克風插孔" + "USB 麥克風" "開啟" "關閉" "流動網絡供應商網絡正在變更" diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 274767b11e9752d09d67e7d7c3582e8b7a675f41..692b6e106b231a58f78742302f25b025304bd1ec 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -582,8 +582,11 @@ "剛剛" "這支手機" "這台平板電腦" + + + "麥克風 (內部)" "座架喇叭" "外部裝置" "已連結的裝置" @@ -684,7 +687,14 @@ "已停用" "已啟用" "裝置必須重新啟動才能套用這項變更。請立即重新啟動或取消變更。" - "有線耳機" + + + + + + + "麥克風插孔" + "USB 麥克風" "開啟" "關閉" "電信業者網路正在進行變更" diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 9ccb2616e3ad039f53c0c93cc729a18b9f3820b3..4f0acbd292f6063f83f5ea5e7816419ccef09910 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -582,8 +582,11 @@ "Khona manje" "Le foni" "Le thebhulethi" + + + "Imakrofoni (okwangaphakathi)" "Isipikha sentuba" "Idivayisi Yangaphandle" "Idivayisi exhunyiwe" @@ -684,7 +687,14 @@ "Ikhutshaziwe" "Inikwe amandla" "Kufanele idivayisi yakho iqaliswe ukuze lolu shintsho lusebenze. Qalisa manje noma khansela." - "Ama-headphone anentambo" + + + + + + + "Umgodi we-earphone ye-mic" + "I-mic ye-USB" "Vuliwe" "Valiwe" "Inethiwekhi yenkampani yenethiwekhi iyashintsha" diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index feee89a51e7c62a4511cb6089dba071bad9d61e5..34e33c0df8f5b90a2e97ac953c108bb300dd7070 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1409,6 +1409,8 @@ This phone This tablet + + This computer (internal) @string/tv_media_transfer_default @@ -1637,7 +1639,13 @@ Your device must be rebooted for this change to apply. Reboot now or cancel. - Wired headphone + Wired headphone + + + Headphone + + + USB speaker Mic jack diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 6f2567b9c5dc84113604abf7084a52bbf3fd287c..a3f9e515a0bcb1fd3c31cab38a5decc6cecc297a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -80,7 +80,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE"; public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE"; public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE"; + public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE"; public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING"; + public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING"; public static final int BROADCAST_STATE_UNKNOWN = 0; public static final int BROADCAST_STATE_ON = 1; public static final int BROADCAST_STATE_OFF = 2; @@ -1224,7 +1226,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state); intent.setPackage(mContext.getPackageName()); - Log.e(TAG, "notifyBroadcastStateChange for state = " + state); + Log.d(TAG, "notifyBroadcastStateChange for state = " + state); mContext.sendBroadcast(intent); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java index 148e164e480661384405fe08e40649246852c390..c9f9d1be9c157f77482fe655e01cb6d15026fc40 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -151,7 +151,19 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); return; } - mService.addSource(sink, metadata, isGroupOp); + try { + mService.addSource(sink, metadata, isGroupOp); + } catch (IllegalStateException e) { + // BT will check callback registration before add source. + // If it throw callback exception when bt is disabled, then the failure is intended, + // just catch it here. + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null && adapter.isEnabled()) { + throw e; + } else { + Log.d(TAG, "Catch addSource failure when bt is disabled: " + e); + } + } } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java index 01bb6f013d169db28af3c1c45733f5d1372cb69e..7ee7180811dc326645deefd815ed3c0911061f3e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java @@ -33,6 +33,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa private final String mTitle; private final ImmutableList mToggleInfos; private final int mState; + private final boolean mIsActive; private final boolean mIsAllowedChangingState; private final Bundle mExtras; @@ -40,6 +41,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa @NonNull String title, List toggleInfos, int state, + boolean isActive, boolean allowChangingState, Bundle extras) { super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE); @@ -47,6 +49,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa mTitle = title; mToggleInfos = ImmutableList.copyOf(toggleInfos); mState = state; + mIsActive = isActive; mIsAllowedChangingState = allowChangingState; mExtras = extras; } @@ -67,9 +70,11 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa List toggleInfos = new ArrayList<>(); in.readTypedList(toggleInfos, ToggleInfo.CREATOR); int state = in.readInt(); + boolean isActive = in.readBoolean(); boolean allowChangingState = in.readBoolean(); Bundle extras = in.readBundle(Bundle.class.getClassLoader()); - return new MultiTogglePreference(title, toggleInfos, state, allowChangingState, extras); + return new MultiTogglePreference( + title, toggleInfos, state, isActive, allowChangingState, extras); } public static final Creator CREATOR = @@ -99,6 +104,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa dest.writeString(mTitle); dest.writeTypedList(mToggleInfos, flags); dest.writeInt(mState); + dest.writeBoolean(mIsActive); dest.writeBoolean(mIsAllowedChangingState); dest.writeBundle(mExtras); } @@ -108,6 +114,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa private String mTitle; private ImmutableList.Builder mToggleInfos = new ImmutableList.Builder<>(); private int mState; + private boolean mIsActive; private boolean mAllowChangingState; private Bundle mExtras = Bundle.EMPTY; @@ -147,6 +154,19 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa return this; } + /** + * Sets whether the current state is considered as an "active" state. If it's set to true, + * the toggle will be highlighted in UI. + * + * @param isActive The active state. + * @return Returns the Builder object. + */ + @NonNull + public Builder setIsActive(boolean isActive) { + mIsActive = isActive; + return this; + } + /** * Sets whether state can be changed by user. * @@ -178,7 +198,7 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa @NonNull public MultiTogglePreference build() { return new MultiTogglePreference( - mTitle, mToggleInfos.build(), mState, mAllowChangingState, mExtras); + mTitle, mToggleInfos.build(), mState, mIsActive, mAllowChangingState, mExtras); } } @@ -201,6 +221,16 @@ public class MultiTogglePreference extends DeviceSettingPreference implements Pa return mState; } + /** + * Whether the current state is considered as an active state. If it's set to true, the toggle + * will be highlighted in UI. + * + * @return Returns the active state. + */ + public boolean isActive() { + return mIsActive; + } + /** * Gets the toggle list in the preference. * diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt index 29664f63d3b2d28411c1fc6c730f7e2e6af1420a..851b614f5279ae9911e03a651766548b0fdcaeb3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt @@ -159,7 +159,7 @@ class DeviceSettingRepositoryImpl( title = pref.title, toggles = pref.toggleInfos.map { it.toModel() }, isAllowedChangingState = pref.isAllowedChangingState, - isActive = true, + isActive = pref.isActive, state = DeviceSettingStateModel.MultiTogglePreferenceState(pref.state), updateState = { newState -> coroutineScope.launch(backgroundCoroutineContext) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt index 7eae5b2a1f5f2abd27673ac3d4f388a2317bbe53..3d8ff86c937704445b9803d0f1f3256d48a84598 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt @@ -56,11 +56,13 @@ import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext @OptIn(ExperimentalCoroutinesApi::class) @@ -101,8 +103,7 @@ class DeviceSettingServiceConnection( } else if (allStatus.all { it is ServiceConnectionStatus.Connected }) { allStatus .filterIsInstance< - ServiceConnectionStatus.Connected< - IDeviceSettingsProviderService> + ServiceConnectionStatus.Connected >() .all { it.service.serviceStatus?.enabled == true } } else { @@ -232,7 +233,7 @@ class DeviceSettingServiceConnection( IDeviceSettingsProviderService.Stub::asInterface, ) .stateIn( - coroutineScope, + coroutineScope.plus(backgroundCoroutineContext), SharingStarted.WhileSubscribed(), ServiceConnectionStatus.Connecting, ) @@ -263,21 +264,30 @@ class DeviceSettingServiceConnection( transform: ((IBinder) -> T), ): Flow> { return callbackFlow { - val serviceConnection = - object : ServiceConnection { - override fun onServiceConnected(name: ComponentName, service: IBinder) { - launch { send(ServiceConnectionStatus.Connected(transform(service))) } - } + val serviceConnection = + object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + launch { send(ServiceConnectionStatus.Connected(transform(service))) } + } - override fun onServiceDisconnected(name: ComponentName?) { - launch { send(ServiceConnectionStatus.Connecting) } + override fun onServiceDisconnected(name: ComponentName?) { + launch { send(ServiceConnectionStatus.Connecting) } + } } + if ( + !context.bindService( + intent, + Context.BIND_AUTO_CREATE, + { launch { it.run() } }, + serviceConnection, + ) + ) { + Log.w(TAG, "Fail to bind service $intent") + launch { send(ServiceConnectionStatus.Failed) } } - if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) { - launch { send(ServiceConnectionStatus.Failed) } + awaitClose { context.unbindService(serviceConnection) } } - awaitClose { context.unbindService(serviceConnection) } - } + .flowOn(backgroundCoroutineContext) } private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? = diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java index 766cd438a8115332494aa39f29608e4d0045ad73..dae69e64934c313218d2b003f1a9d8f3fe58a991 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java @@ -80,6 +80,10 @@ public class InputMediaDevice extends MediaDevice { context, id, audioDeviceInfoType, maxVolume, currentVolume, isVolumeFixed); } + public @AudioDeviceType int getAudioDeviceInfoType() { + return mAudioDeviceInfoType; + } + public static boolean isSupportedInputDevice(@AudioDeviceType int audioDeviceInfoType) { return switch (audioDeviceInfoType) { case TYPE_BUILTIN_MIC, @@ -128,8 +132,7 @@ public class InputMediaDevice extends MediaDevice { @VisibleForTesting int getDrawableResId() { - // TODO(b/357122624): check with UX to obtain the icon for desktop devices. - return R.drawable.ic_media_tablet; + return R.drawable.ic_media_microphone; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java index 548eb3fd4b8fd114a5d1b0f924ea9d3856074200..0c50166fff8b49387876d5f0c4267c74589fc2b5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java @@ -15,13 +15,20 @@ */ package com.android.settingslib.media; +import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; + import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; +import android.media.MediaRecorder; import android.os.Handler; +import android.util.Slog; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -35,12 +42,18 @@ public final class InputRouteManager { private static final String TAG = "InputRouteManager"; + @VisibleForTesting + static final AudioAttributes INPUT_ATTRIBUTES = + new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.MIC).build(); + private final Context mContext; private final AudioManager mAudioManager; @VisibleForTesting final List mInputMediaDevices = new CopyOnWriteArrayList<>(); + private MediaDevice mSelectedInputDevice; + private final Collection mCallbacks = new CopyOnWriteArrayList<>(); @VisibleForTesting @@ -57,7 +70,7 @@ public final class InputRouteManager { } }; - /* package */ InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) { + public InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) { mContext = context; mAudioManager = audioManager; Handler handler = new Handler(context.getMainLooper()); @@ -76,8 +89,27 @@ public final class InputRouteManager { mCallbacks.remove(callback); } + public @Nullable MediaDevice getSelectedInputDevice() { + return mSelectedInputDevice; + } + private void dispatchInputDeviceListUpdate() { - // TODO (b/360175574): Get selected input device. + // Get selected input device. + List attributesOfSelectedInputDevices = + mAudioManager.getDevicesForAttributes(INPUT_ATTRIBUTES); + int selectedInputDeviceAttributesType; + if (attributesOfSelectedInputDevices.isEmpty()) { + Slog.e(TAG, "Unexpected empty list of input devices. Using built-in mic."); + selectedInputDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_MIC; + } else { + if (attributesOfSelectedInputDevices.size() > 1) { + Slog.w( + TAG, + "AudioManager.getDevicesForAttributes returned more than one element." + + " Using the first one."); + } + selectedInputDeviceAttributesType = attributesOfSelectedInputDevices.get(0).getType(); + } // Get all input devices. AudioDeviceInfo[] audioDeviceInfos = @@ -93,6 +125,10 @@ public final class InputRouteManager { getCurrentInputGain(), isInputGainFixed()); if (mediaDevice != null) { + if (info.getType() == selectedInputDeviceAttributesType) { + mediaDevice.setState(STATE_SELECTED); + mSelectedInputDevice = mediaDevice; + } mInputMediaDevices.add(mediaDevice); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 9eaf8d3838d80727126b364e755a3bb6314e0440..0b8fb22cef3a849d0f62429990da48427a447570 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -72,6 +72,8 @@ public class PhoneMediaDevice extends MediaDevice { return context.getString(R.string.media_transfer_this_device_name_tv); } else if (isTablet()) { return context.getString(R.string.media_transfer_this_device_name_tablet); + } else if (inputRoutingEnabledAndIsDesktop()) { + return context.getString(R.string.media_transfer_this_device_name_desktop); } else { return context.getString(R.string.media_transfer_this_device_name); } @@ -85,10 +87,18 @@ public class PhoneMediaDevice extends MediaDevice { switch (routeInfo.getType()) { case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: + name = + inputRoutingEnabledAndIsDesktop() + ? context.getString(R.string.media_transfer_headphone_name) + : context.getString(R.string.media_transfer_wired_headphone_name); + break; case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_ACCESSORY: - name = context.getString(R.string.media_transfer_wired_usb_device_name); + name = + inputRoutingEnabledAndIsDesktop() + ? context.getString(R.string.media_transfer_usb_speaker_name) + : context.getString(R.string.media_transfer_wired_headphone_name); break; case TYPE_DOCK: name = context.getString(R.string.media_transfer_dock_speaker_device_name); @@ -139,6 +149,16 @@ public class PhoneMediaDevice extends MediaDevice { .contains("tablet"); } + static boolean isDesktop() { + return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(",")) + .contains("desktop"); + } + + static boolean inputRoutingEnabledAndIsDesktop() { + return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl() + && isDesktop(); + } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. @SuppressWarnings("NewApi") @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java index 251cd3615897ab358b18a481296dd496665a88b8..9a9a96012984c5a80d6c43662878b466b1cd8ceb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java @@ -87,7 +87,7 @@ public class ConversationIconFactory extends BaseIconFactory { * Returns the conversation info drawable */ public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { - return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); + return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFullResIconDpi); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING b/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING index 71cbcb54a1ffbb99a019f73f97fa96fe8d4d3ed4..1346ee565a5d41c02d78827a93ff5afa61dd9b57 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING +++ b/packages/SettingsLib/src/com/android/settingslib/users/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "SettingsLibTests", - "options": [ - { - "include-filter": "com.android.settingslib.users." - } - ] + "name": "SettingsLibTests_settingslib_users" } ] } \ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java index 62fcb5ec3011ab4da59dbeb0de239540a4a62116..1c7b5bceafe78226e7a4d64032f0c2666dd53c55 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java @@ -120,6 +120,7 @@ public final class MultiTogglePreferenceTest { .addToggleInfo(TOGGLE_INFO_1) .addToggleInfo(TOGGLE_INFO_2) .setState(123) + .setIsActive(true) .setAllowChangingState(true) .setExtras(buildBundle("key1", "value1")) .build(); @@ -130,6 +131,7 @@ public final class MultiTogglePreferenceTest { assertThat(fromParcel.getToggleInfos().stream().map(ToggleInfo::getLabel).toList()) .containsExactly("label1", "label2"); assertThat(fromParcel.getState()).isEqualTo(preference.getState()); + assertThat(fromParcel.isActive()).isEqualTo(preference.isActive()); assertThat(fromParcel.isAllowedChangingState()) .isEqualTo(preference.isAllowedChangingState()); assertThat(fromParcel.getExtras().getString("key1")) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt index 81b56343ceed5969c78204516ad0f298872dee43..0cb6bc1b1261472f4bc37fde0dba7820d5c1792f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt @@ -102,9 +102,9 @@ class DeviceSettingRepositoryTest { `when`(settingProviderService2.queryLocalInterface(anyString())) .thenReturn(settingProviderService2) - `when`(context.bindService(any(), any(), anyInt())).then { input -> + `when`(context.bindService(any(), anyInt(), any(), any())).then { input -> val intent = input.getArgument(0) - val connection = input.getArgument(1) + val connection = input.getArgument(3) when (intent?.action) { CONFIG_SERVICE_INTENT_ACTION -> @@ -210,7 +210,7 @@ class DeviceSettingRepositoryTest { fun getDeviceSettingsConfig_bindingServiceFail_returnNull() { testScope.runTest { `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG) - doReturn(false).`when`(context).bindService(any(), any(), anyInt()) + doReturn(false).`when`(context).bindService(any(), anyInt(), any(), any()) val config = underTest.getDeviceSettingsConfig(cachedDevice) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java index bc1ea6c42fa3358245537eacfe1e1ce5f01e9bef..088d554326e7bd3cac3d8e11c0d11f8b71e75c19 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java @@ -18,9 +18,6 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import android.content.Context; import android.media.AudioDeviceInfo; import android.platform.test.flag.junit.SetFlagsRule; @@ -64,7 +61,7 @@ public class InputMediaDeviceTest { CURRENT_VOLUME, IS_VOLUME_FIXED); assertThat(builtinMediaDevice).isNotNull(); - assertThat(builtinMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_tablet); + assertThat(builtinMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_microphone); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java index 2501ae6769b63acb688b952b79b94f6f532dcb0f..8a18d0714e0a60e201e6679f62e12044e0a940a9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java @@ -16,6 +16,8 @@ package com.android.settingslib.media; +import static com.android.settingslib.media.InputRouteManager.INPUT_ATTRIBUTES; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -23,6 +25,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; +import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; @@ -36,6 +39,10 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowRouter2Manager.class}) public class InputRouteManagerTest { @@ -123,6 +130,97 @@ public class InputRouteManagerTest { assertThat(inputRouteManager.mInputMediaDevices).isEmpty(); } + @Test + public void getSelectedInputDevice_returnOneFromAudioManager() { + final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); + when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + + final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); + when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); + when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + + final AudioManager audioManager = mock(AudioManager.class); + AudioDeviceInfo[] devices = {info1, info2}; + when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices); + + // Mock audioManager.getDevicesForAttributes returns exactly one audioDeviceAttributes. + AudioDeviceAttributes audioDeviceAttributes = new AudioDeviceAttributes(info1); + when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES)) + .thenReturn(Collections.singletonList(audioDeviceAttributes)); + + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // The selected input device has the same type as the one returned from AudioManager. + InputMediaDevice selectedInputDevice = + (InputMediaDevice) inputRouteManager.getSelectedInputDevice(); + assertThat(selectedInputDevice.getAudioDeviceInfoType()) + .isEqualTo(AudioDeviceInfo.TYPE_WIRED_HEADSET); + } + + @Test + public void getSelectedInputDevice_returnMoreThanOneFromAudioManager() { + final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); + when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + + final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); + when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); + when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + + final AudioManager audioManager = mock(AudioManager.class); + AudioDeviceInfo[] devices = {info1, info2}; + when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices); + + // Mock audioManager.getDevicesForAttributes returns more than one audioDeviceAttributes. + AudioDeviceAttributes audioDeviceAttributes1 = new AudioDeviceAttributes(info1); + AudioDeviceAttributes audioDeviceAttributes2 = new AudioDeviceAttributes(info2); + List attributesOfSelectedInputDevices = new ArrayList<>(); + attributesOfSelectedInputDevices.add(audioDeviceAttributes1); + attributesOfSelectedInputDevices.add(audioDeviceAttributes2); + when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES)) + .thenReturn(attributesOfSelectedInputDevices); + + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // The selected input device has the same type as the first one returned from AudioManager. + InputMediaDevice selectedInputDevice = + (InputMediaDevice) inputRouteManager.getSelectedInputDevice(); + assertThat(selectedInputDevice.getAudioDeviceInfoType()) + .isEqualTo(AudioDeviceInfo.TYPE_WIRED_HEADSET); + } + + @Test + public void getSelectedInputDevice_returnEmptyFromAudioManager() { + final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); + when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + + final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); + when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); + when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + + final AudioManager audioManager = mock(AudioManager.class); + AudioDeviceInfo[] devices = {info1, info2}; + when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices); + + // Mock audioManager.getDevicesForAttributes returns empty list of audioDeviceAttributes. + List attributesOfSelectedInputDevices = new ArrayList<>(); + when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES)) + .thenReturn(attributesOfSelectedInputDevices); + + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // The selected input device has default type AudioDeviceInfo.TYPE_BUILTIN_MIC. + InputMediaDevice selectedInputDevice = + (InputMediaDevice) inputRouteManager.getSelectedInputDevice(); + assertThat(selectedInputDevice.getAudioDeviceInfoType()) + .isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_MIC); + } + @Test public void getMaxInputGain_returnMaxInputGain() { assertThat(mInputRouteManager.getMaxInputGain()).isEqualTo(15); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index e2d58d660fd5769692f31d6a573cbec5eecf8b04..da5f428ce23b44a81e0fad61bf60399350d21b23 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -47,6 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowSystemProperties; @RunWith(RobolectricTestRunner.class) public class PhoneMediaDeviceTest { @@ -105,12 +106,37 @@ public class PhoneMediaDeviceTest { when(mInfo.getName()).thenReturn(deviceName); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name)); when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE); assertThat(mPhoneMediaDevice.getName()) - .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name)); + .isEqualTo(mContext.getString(R.string.media_transfer_wired_headphone_name)); + + when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); + + assertThat(mPhoneMediaDevice.getName()).isEqualTo(getMediaTransferThisDeviceName(mContext)); + } + + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) + @Test + public void getName_returnCorrectName_desktop() { + ShadowSystemProperties.override("ro.build.characteristics", "desktop"); + + when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES); + + assertThat(mPhoneMediaDevice.getName()) + .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name)); + + when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET); + + assertThat(mPhoneMediaDevice.getName()) + .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name)); + + when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE); + + assertThat(mPhoneMediaDevice.getName()) + .isEqualTo(mContext.getString(R.string.media_transfer_usb_speaker_name)); when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index c107ff5a34ceec50f1e9c69ee8a31789f871082f..1a99d25786ffbc66a9df0ba0f787e4168c9f8b57 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -36,6 +36,7 @@ android_library { "aconfig_new_storage_flags_lib", "aconfigd_java_utils", "aconfig_demo_flags_java_lib", + "configinfra_framework_flags_java_lib", "device_config_service_flags_java", "libaconfig_java_proto_lite", "SettingsLibDeviceStateRotationLock", diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING index 0eed2b7490d4da3d795ff1a96af63a78da4d5c10..cf9ed2e6c1df3f05ff221dd8bcec9dfa9a8ef6db 100644 --- a/packages/SettingsProvider/TEST_MAPPING +++ b/packages/SettingsProvider/TEST_MAPPING @@ -4,12 +4,7 @@ "name": "SettingsProviderTest" }, { - "name": "CtsProviderTestCases", - "options": [ - { - "include-filter": "android.provider.cts.settings." - } - ] + "name": "CtsProviderTestCases_cts_settings" } ], "postsubmit": [ diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 40a8199a0690462c04eb0749d1083e7460cc273c..d7109398b956807fb1e7fdba3eb1df37f499603e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -86,6 +86,7 @@ public class SecureSettings { Settings.Secure.DOUBLE_TAP_TO_WAKE, Settings.Secure.WAKE_GESTURE_ENABLED, Settings.Secure.LONG_PRESS_TIMEOUT, + Settings.Secure.KEY_REPEAT_ENABLED, Settings.Secure.KEY_REPEAT_TIMEOUT_MS, Settings.Secure.KEY_REPEAT_DELAY_MS, Settings.Secure.CAMERA_GESTURE_DISABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 3b9c683896325d4ba1845156852c8b9a3253506a..fa16a44f45926c8a6af11ec95dcd4284a02f4736 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -133,6 +133,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.DOUBLE_TAP_TO_WAKE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.WAKE_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LONG_PRESS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.KEY_REPEAT_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.KEY_REPEAT_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.KEY_REPEAT_DELAY_MS, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index bfbf41dc87ce422be4d901fd8d93434fa3ff3e12..fbce6ca07b3ecb4b15fa76ea574d1ff13b31a146 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -99,13 +99,23 @@ public final class DeviceConfigService extends Binder { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.print("SyncDisabledForTests: "); - MyShellCommand.getSyncDisabledForTests(pw, pw); - - pw.print("Is mainline: "); - pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + if (android.provider.flags.Flags.dumpImprovements()) { + pw.print("SyncDisabledForTests: "); + MyShellCommand.getSyncDisabledForTests(pw, pw); + + pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): "); + pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + + pw.println("DeviceConfig provider: "); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) { + DeviceConfig.dump(pfd, pw, /* prefix= */ " ", args); + } catch (IOException e) { + pw.print("IOException creating ParcelFileDescriptor: "); + pw.println(e); + } + } - final IContentProvider iprovider = mProvider.getIContentProvider(); + IContentProvider iprovider = mProvider.getIContentProvider(); pw.println("DeviceConfig flags:"); for (String line : MyShellCommand.listAll(iprovider)) { pw.println(line); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index ba59ce81d362cf5c8fd6e068fc98462db04ef20f..f64c3059e3a44f0464f1b227fc198227fcd281ed 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -545,6 +545,10 @@ public class SettingsProvider extends ContentProvider { reportDeviceConfigAccess(prefix); return result; } + case Settings.CALL_METHOD_LIST_NAMESPACES_CONFIG -> { + Bundle result = packageNamespacesForCallResult(getAllConfigFlagNamespaces()); + return result; + } case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> { RemoteCallback callback = args.getParcelable( Settings.CALL_METHOD_MONITOR_CALLBACK_KEY); @@ -1336,6 +1340,23 @@ public class SettingsProvider extends ContentProvider { return false; } + @NonNull + private HashSet getAllConfigFlagNamespaces() { + Set flagNames = getAllConfigFlags(null).keySet(); + HashSet namespaces = new HashSet(); + for (String name : flagNames) { + int slashIndex = name.indexOf("/"); + boolean validSlashIndex = slashIndex != -1 + && slashIndex != 0 + && slashIndex != name.length(); + if (validSlashIndex) { + String namespace = name.substring(0, slashIndex); + namespaces.add(namespace); + } + } + return namespaces; + } + @NonNull private HashMap getAllConfigFlags(@Nullable String prefix) { if (DEBUG) { @@ -1995,8 +2016,6 @@ public class SettingsProvider extends ContentProvider { if (!isValidMediaUri(name, value)) { return false; } - // Invalidate any relevant cache files - cacheFile.delete(); } final boolean success; @@ -2034,6 +2053,11 @@ public class SettingsProvider extends ContentProvider { return false; } + if (cacheFile != null) { + // Invalidate any relevant cache files + cacheFile.delete(); + } + if ((operation == MUTATION_OPERATION_INSERT || operation == MUTATION_OPERATION_UPDATE) && cacheFile != null && value != null) { final Uri ringtoneUri = Uri.parse(value); @@ -2561,6 +2585,12 @@ public class SettingsProvider extends ContentProvider { return result; } + private Bundle packageNamespacesForCallResult(@NonNull HashSet namespaces) { + Bundle result = new Bundle(); + result.putSerializable(Settings.NameValueTable.VALUE, namespaces); + return result; + } + private void setMonitorCallback(RemoteCallback callback) { if (callback == null) { return; diff --git a/packages/Shell/TEST_MAPPING b/packages/Shell/TEST_MAPPING index 9bb1b4b9920756b5bb90a257e1ceacb524a6fdaf..6b9f1ebd4061bf88e42188071d0c38ad9e1c9c73 100644 --- a/packages/Shell/TEST_MAPPING +++ b/packages/Shell/TEST_MAPPING @@ -1,23 +1,10 @@ { "presubmit": [ { - "name": "CtsBugreportTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsBugreportTestCases_android_server_os" }, { - "name": "ShellTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ShellTests_android_server_os" }, { "name": "CtsUiAutomationTestCases", diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index f59eab001be958de34616560483aaf6531a96587..bd7067bc1293cb573ee865eda42f8b8ef97a8f68 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -452,7 +452,7 @@ filegroup { "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java", "tests/src/**/systemui/doze/DozeScreenStateTest.java", "tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java", - "tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java", + "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java", "tests/src/**/systemui/navigationbar/views/NavigationBarTest.java", "tests/src/**/systemui/power/PowerNotificationWarningsTest.java", "tests/src/**/systemui/power/PowerUITest.java", @@ -860,6 +860,7 @@ android_app { resource_dirs: [], kotlincflags: ["-Xjvm-default=all"], optimize: { + optimize: false, shrink_resources: false, optimized_shrink_resources: false, proguard_flags_files: ["proguard.flags"], diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 16dd4e5a800e13708c4d7d8f75576c3e687218db..07a1e630e1ad2044e37f1e503e860830350acd3d 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -21,15 +21,7 @@ // v2/android-virtual-infra/test_mapping/presubmit-avd "presubmit": [ { - "name": "SystemUIGoogleTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "SystemUIGoogleTests" }, { // Permission indicators @@ -48,15 +40,7 @@ }, { // Permission indicators - "name": "CtsVoiceRecognitionTestCases", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVoiceRecognitionTestCases" } ], diff --git a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING index 4a10108b3e048d4858704401f0cae053cc2b06e8..1820f39bb1805dc94a649d2f0774af8950ec9309 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING +++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING @@ -2,12 +2,7 @@ // TODO: b/324945360 - Re-enable on presubmit after fixing failures "postsubmit": [ { - "name": "AccessibilityMenuServiceTests", - "options": [ - { - "exclude-annotation": "android.support.test.filters.FlakyTest" - } - ] + "name": "AccessibilityMenuServiceTests" } ] } \ No newline at end of file diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 02e8cd6c398d0bb2cd45a692ebfe1acaea0831fe..c49ffb49a1da22b22dedd65260aed7c7f24e471d 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -148,6 +148,16 @@ flag { } } +flag { + name: "modes_dialog_single_rows" + namespace: "systemui" + description: "[Experiment] Display one entry per grid row in the Modes Dialog." + bug: "366034002" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" @@ -527,6 +537,13 @@ flag { bug: "310715220" } +flag { + name: "status_bar_connected_displays" + namespace: "systemui" + description: "Shows the status bar on connected displays" + bug: "362720336" +} + flag { name: "status_bar_switch_to_spn_from_data_spn" namespace: "systemui" @@ -538,10 +555,10 @@ flag { } flag { - name: "haptic_volume_slider" + name: "status_bar_simple_fragment" namespace: "systemui" - description: "Adds haptic feedback to the volume slider." - bug: "316953430" + description: "Feature flag for refactoring the collapsed status bar fragment" + bug: "364360986" } flag { @@ -667,13 +684,6 @@ flag { bug: "278086361" } -flag { - name: "compose_lockscreen" - namespace: "systemui" - description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass." - bug: "301968149" -} - flag { name: "enable_contextual_tip_for_power_off" namespace: "systemui" @@ -1052,6 +1062,13 @@ flag { } } +flag { + name: "communal_widget_resizing" + namespace: "systemui" + description: "Allow resizing of widgets on glanceable hub" + bug: "368053818" +} + flag { name: "app_clips_backlinks" namespace: "systemui" @@ -1312,6 +1329,16 @@ flag { } } +flag { + name: "sim_pin_bouncer_reset" + namespace: "systemui" + description: "The SIM PIN bouncer does not close after unlocking" + bug: "297461589" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "use_transitions_for_keyguard_occluded" namespace: "systemui" @@ -1395,3 +1422,13 @@ flag { description: "Allow non-touchscreen devices to bypass falsing" bug: "319809270" } + +flag { + name: "media_projection_dialog_behind_lockscreen" + namespace: "systemui" + description: "Ensure MediaProjection Dialog appears behind the lockscreen" + bug: "351409536" + metadata { + purpose: PURPOSE_BUGFIX + } +} \ No newline at end of file diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 9d0b095ad4cc29b22ce78084b43b1d53e7b3a9f5..d02527531a5385901c412050c3318cc17444d14c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -57,6 +57,7 @@ import com.android.systemui.Flags.activityTransitionUseLargestWindow import com.android.systemui.Flags.translucentOccludingActivityFix import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary +import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived import com.android.wm.shell.shared.IShellTransitions import com.android.wm.shell.shared.ShellTransitions import java.util.concurrent.Executor @@ -607,8 +608,8 @@ constructor( * this registration. */ fun register(controller: Controller) { - check(returnAnimationFrameworkLibrary()) { - "Long-lived registrations cannot be used when the returnAnimationFrameworkLibrary " + + check(returnAnimationFrameworkLongLived()) { + "Long-lived registrations cannot be used when the returnAnimationFrameworkLongLived " + "flag is disabled" } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt index 907c39d842cee211c1faca425eb17bc1137e4bca..f5d01d70e077fefac6d8d24b2a48f281e4cdda09 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt @@ -944,26 +944,9 @@ private class AnimatedDialog( } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - // onLaunchAnimationEnd is called by an Animator at the end of the animation, - // on a Choreographer animation tick. The following calls will move the animated - // content from the dialog overlay back to its original position, and this - // change must be reflected in the next frame given that we then sync the next - // frame of both the content and dialog ViewRoots. However, in case that content - // is rendered by Compose, whose compositions are also scheduled on a - // Choreographer frame, any state change made *right now* won't be reflected in - // the next frame given that a Choreographer frame can't schedule another and - // have it happen in the same frame. So we post the forwarded calls to - // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring - // that the move of the content back to its original window will be reflected in - // the next frame right after [onLaunchAnimationEnd] is called. - // - // TODO(b/330672236): Move this to TransitionAnimator. - dialog.context.mainExecutor.execute { - startController.onTransitionAnimationEnd(isExpandingFullyAbove) - endController.onTransitionAnimationEnd(isExpandingFullyAbove) - - onLaunchAnimationEnd() - } + startController.onTransitionAnimationEnd(isExpandingFullyAbove) + endController.onTransitionAnimationEnd(isExpandingFullyAbove) + onLaunchAnimationEnd() } override fun onTransitionAnimationProgress( diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index fc4cf1d1e21e96a6983b0dcdc7ff27fc83b888ca..859fc4e09bb21638409a282194d3df8a8bb672cd 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -379,13 +379,26 @@ class TransitionAnimator( Log.d(TAG, "Animation ended") } - // TODO(b/330672236): Post this to the main thread instead so that it does not - // flicker with Flexiglass enabled. - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - transitionContainerOverlay.remove(windowBackgroundLayer) - - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + // onAnimationEnd is called at the end of the animation, on a Choreographer + // animation tick. During dialog launches, the following calls will move the + // animated content from the dialog overlay back to its original position, and + // this change must be reflected in the next frame given that we then sync the + // next frame of both the content and dialog ViewRoots. During SysUI activity + // launches, we will instantly collapse the shade at the end of the transition. + // However, if those are rendered by Compose, whose compositions are also + // scheduled on a Choreographer frame, any state change made *right now* won't + // be reflected in the next frame given that a Choreographer frame can't + // schedule another and have it happen in the same frame. So we post the + // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor, + // leaving this Choreographer frame, ensuring that any state change applied by + // onTransitionAnimationEnd() will be reflected in the same frame. + mainExecutor.execute { + controller.onTransitionAnimationEnd(isExpandingFullyAbove) + transitionContainerOverlay.remove(windowBackgroundLayer) + + if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { + openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + } } } } diff --git a/packages/SystemUI/compose/core/TEST_MAPPING b/packages/SystemUI/compose/core/TEST_MAPPING index b71c5fb29fd783908bc7e1b1edef6feb47e43b1e..56e531d2bee0b953ca7af8fbe7f068eb9febb2a9 100644 --- a/packages/SystemUI/compose/core/TEST_MAPPING +++ b/packages/SystemUI/compose/core/TEST_MAPPING @@ -1,26 +1,10 @@ { "presubmit": [ { - "name": "PlatformComposeCoreTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "PlatformComposeCoreTests" }, { - "name": "SystemUIComposeGalleryTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "SystemUIComposeGalleryTests" } ] } \ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 163b35596c1b6e3e3c5de8b8e3995b28800877fe..8321238b28b11207df29d95fda788311068f8a77 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -16,7 +16,6 @@ package com.android.systemui.bouncer.ui.composable -import android.view.HapticFeedbackConstants import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D @@ -133,10 +132,7 @@ fun PatternBouncer( // Perform haptic feedback, but only if the current dot is not null, so we don't perform it // when the UI first shows up or when the user lifts their pointer/finger. if (currentDot != null) { - view.performHapticFeedback( - HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, - ) + viewModel.performDotFeedback(view) } if (!isAnimationEnabled) { @@ -206,10 +202,7 @@ fun PatternBouncer( // Show the failure animation if the user entered the wrong input. LaunchedEffect(animateFailure) { if (animateFailure) { - showFailureAnimation( - dots = dots, - scalingAnimatables = dotScalingAnimatables, - ) + showFailureAnimation(dots = dots, scalingAnimatables = dotScalingAnimatables) viewModel.onFailureAnimationShown() } } @@ -358,15 +351,10 @@ fun PatternBouncer( (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset drawCircle( center = - pixelOffset( - dot, - spacing, - horizontalOffset, - verticalOffset + appearOffset, - ), + pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset), color = dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value), - radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value + radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value, ) } } @@ -387,7 +375,7 @@ private suspend fun showEntryAnimation( delayMillis = 33 * dot.y, durationMillis = 450, easing = Easings.LegacyDecelerate, - ) + ), ) } } @@ -400,7 +388,7 @@ private suspend fun showEntryAnimation( delayMillis = 0, durationMillis = 450 + (33 * dot.y), easing = Easings.StandardDecelerate, - ) + ), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 489e24e8b3281084f55cc1a0ac29c46b46d2e5e5..0830c9b359a48e96c56b210f45c905c235cdef01 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -16,8 +16,8 @@ package com.android.systemui.bouncer.ui.composable -import android.view.HapticFeedbackConstants import android.view.MotionEvent +import android.view.View import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec @@ -72,11 +72,7 @@ import kotlinx.coroutines.launch /** Renders the PIN button pad. */ @Composable -fun PinPad( - viewModel: PinBouncerViewModel, - verticalSpacing: Dp, - modifier: Modifier = Modifier, -) { +fun PinPad(viewModel: PinBouncerViewModel, verticalSpacing: Dp, modifier: Modifier = Modifier) { DisposableEffect(Unit) { onDispose { viewModel.onHidden() } } val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() @@ -104,7 +100,7 @@ fun PinPad( columns = columns, verticalSpacing = verticalSpacing, horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp), - modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid") + modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid"), ) { repeat(9) { index -> DigitButton( @@ -126,10 +122,11 @@ fun PinPad( ), isInputEnabled = isInputEnabled, onClicked = viewModel::onBackspaceButtonClicked, + onPointerDown = viewModel::onBackspaceButtonPressed, onLongPressed = viewModel::onBackspaceButtonLongPressed, appearance = backspaceButtonAppearance, scaling = buttonScaleAnimatables[9]::value, - elementId = "delete_button" + elementId = "delete_button", ) DigitButton( @@ -138,7 +135,7 @@ fun PinPad( onClicked = viewModel::onPinButtonClicked, scaling = buttonScaleAnimatables[10]::value, isAnimationEnabled = isDigitButtonAnimationEnabled, - onPointerDown = viewModel::onDigitButtonDown + onPointerDown = viewModel::onDigitButtonDown, ) ActionButton( @@ -152,7 +149,7 @@ fun PinPad( onClicked = viewModel::onAuthenticateButtonClicked, appearance = confirmButtonAppearance, scaling = buttonScaleAnimatables[11]::value, - elementId = "key_enter" + elementId = "key_enter", ) } } @@ -162,7 +159,7 @@ private fun DigitButton( digit: Int, isInputEnabled: Boolean, onClicked: (Int) -> Unit, - onPointerDown: () -> Unit, + onPointerDown: (View?) -> Unit, scaling: () -> Float, isAnimationEnabled: Boolean, ) { @@ -178,7 +175,7 @@ private fun DigitButton( val scale = if (isAnimationEnabled) scaling() else 1f scaleX = scale scaleY = scale - } + }, ) { contentColor -> // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes // it into Text, use that here, to animate more efficiently. @@ -197,6 +194,7 @@ private fun ActionButton( onClicked: () -> Unit, elementId: String, onLongPressed: (() -> Unit)? = null, + onPointerDown: ((View?) -> Unit)? = null, appearance: ActionButtonAppearance, scaling: () -> Float, ) { @@ -222,18 +220,16 @@ private fun ActionButton( foregroundColor = foregroundColor, isAnimationEnabled = true, elementId = elementId, + onPointerDown = onPointerDown, modifier = Modifier.graphicsLayer { alpha = hiddenAlpha val scale = scaling() scaleX = scale scaleY = scale - } + }, ) { contentColor -> - Icon( - icon = icon, - tint = contentColor(), - ) + Icon(icon = icon, tint = contentColor()) } } @@ -247,22 +243,13 @@ private fun PinPadButton( modifier: Modifier = Modifier, elementId: String? = null, onLongPressed: (() -> Unit)? = null, - onPointerDown: (() -> Unit)? = null, + onPointerDown: ((View?) -> Unit)? = null, content: @Composable (contentColor: () -> Color) -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val indication = LocalIndication.current.takeUnless { isPressed } - val view = LocalView.current - LaunchedEffect(isPressed) { - if (isPressed) { - view.performHapticFeedback( - HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, - ) - } - } // Pin button animation specification is asymmetric: fast animation to the pressed state, and a // slow animation upon release. Note that isPressed is guaranteed to be true for at least the @@ -277,7 +264,7 @@ private fun PinPadButton( animateDpAsState( if (isAnimationEnabled && isPressed) 24.dp else pinButtonMaxSize / 2, label = "PinButton round corners", - animationSpec = tween(animDurationMillis, easing = animEasing) + animationSpec = tween(animDurationMillis, easing = animEasing), ) val colorAnimationSpec: AnimationSpec = tween(animDurationMillis, easing = animEasing) val containerColor: Color by @@ -287,7 +274,7 @@ private fun PinPadButton( else -> backgroundColor }, label = "Pin button container color", - animationSpec = colorAnimationSpec + animationSpec = colorAnimationSpec, ) val contentColor = animateColorAsState( @@ -296,7 +283,7 @@ private fun PinPadButton( else -> foregroundColor }, label = "Pin button container color", - animationSpec = colorAnimationSpec + animationSpec = colorAnimationSpec, ) Box( @@ -319,11 +306,11 @@ private fun PinPadButton( interactionSource = interactionSource, indication = indication, onClick = onClicked, - onLongClick = onLongPressed + onLongClick = onLongPressed, ) .pointerInteropFilter { motionEvent -> if (motionEvent.action == MotionEvent.ACTION_DOWN) { - onPointerDown?.let { it() } + onPointerDown?.let { it(view) } } false } @@ -353,10 +340,7 @@ private suspend fun showFailureAnimation( animatable.animateTo( targetValue = 1f, animationSpec = - tween( - durationMillis = pinButtonErrorRevertMs, - easing = Easings.Legacy, - ), + tween(durationMillis = pinButtonErrorRevertMs, easing = Easings.Legacy), ) } } @@ -364,9 +348,7 @@ private suspend fun showFailureAnimation( } /** Returns the amount of horizontal spacing between columns, in dips. */ -private fun calculateHorizontalSpacingBetweenColumns( - gridWidth: Dp, -): Dp { +private fun calculateHorizontalSpacingBetweenColumns(gridWidth: Dp): Dp { return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt index 296fc27ac0ff90ca8e9de2792dfe5a3737268c19..dcf32b2bcda49d6dfe6a70cd62331d74d74240ea 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt @@ -16,15 +16,10 @@ package com.android.systemui.common.ui.compose.windowinsets -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -36,9 +31,6 @@ val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() } /** The corner radius in px of the current display. */ val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp } -/** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */ -val LocalRawScreenHeight = staticCompositionLocalOf { 0f } - @Composable fun ScreenDecorProvider( displayCutout: StateFlow, @@ -48,22 +40,9 @@ fun ScreenDecorProvider( val cutout by displayCutout.collectAsStateWithLifecycle() val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() } - val density = LocalDensity.current - val navBarHeight = - with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() } - val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding() - val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding() - val screenHeight = - with(density) { - (LocalConfiguration.current.screenHeightDp.dp + - maxOf(statusBarHeight, displayCutoutHeight)) - .toPx() - } + navBarHeight - CompositionLocalProvider( LocalScreenCornerRadius provides screenCornerRadiusDp, LocalDisplayCutout provides cutout, - LocalRawScreenHeight provides screenHeight, ) { content() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index 5f7b1adca6a0d420f4c5f5f06f924a3e0446f82d..f4d9e820ad8ff782d894f0de3cd8d04b3a0937f9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -35,7 +35,6 @@ import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.res.R import com.android.systemui.util.animation.MeasurementInput -import kotlinx.coroutines.ExperimentalCoroutinesApi object MediaCarousel { object Elements { @@ -47,7 +46,6 @@ object MediaCarousel { } } -@ExperimentalCoroutinesApi @Composable fun SceneScope.MediaCarousel( isVisible: Boolean, @@ -79,7 +77,7 @@ fun SceneScope.MediaCarousel( offsetProvider?.invoke() ?: IntOffset.Zero ) } - } + }, ) .layout { measurable, constraints -> val placeable = measurable.measure(constraints) @@ -89,7 +87,7 @@ fun SceneScope.MediaCarousel( MeasurementInput(placeable.width, placeable.height) carouselController.setSceneContainerSize( placeable.width, - placeable.height + placeable.height, ) layout(placeable.width, placeable.height) { @@ -106,7 +104,7 @@ fun SceneScope.MediaCarousel( } }, update = { it.setView(carouselController.mediaFrame) }, - onRelease = { it.removeAllViews() } + onRelease = { it.removeAllViews() }, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt index 897a8613263f2259dad713cec25e2d18052c699d..a2ae8bbf66e4dacab6dac876671d18e757cac50e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt @@ -24,9 +24,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp import com.android.compose.nestedscroll.PriorityNestedScrollConnection -import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import kotlin.math.max import kotlin.math.roundToInt import kotlin.math.tanh @@ -36,9 +38,10 @@ import kotlinx.coroutines.launch @Composable fun Modifier.stackVerticalOverscroll( coroutineScope: CoroutineScope, - canScrollForward: () -> Boolean + canScrollForward: () -> Boolean, ): Modifier { - val screenHeight = LocalRawScreenHeight.current + val screenHeight = + with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } val overscrollOffset = remember { Animatable(0f) } val stackNestedScrollConnection = remember { NotificationStackNestedScrollConnection( @@ -60,10 +63,10 @@ fun Modifier.stackVerticalOverscroll( overscrollOffset.animateTo( targetValue = 0f, initialVelocity = velocityAvailable, - animationSpec = tween() + animationSpec = tween(), ) } - } + }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 91ecfc18a76eba8f85a7e46de102b587db564200..1b99a9644575a92267e819cd91852b6ab4a26933 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable import android.util.Log import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background @@ -29,6 +30,8 @@ import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.absoluteOffset @@ -36,9 +39,11 @@ import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme @@ -68,6 +73,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp @@ -81,7 +87,6 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf -import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession @@ -96,6 +101,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import kotlin.math.roundToInt +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch object Notifications { @@ -171,7 +177,7 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( setCurrent = { scrollOffset = it }, min = minScrollOffset, max = maxScrollOffset, - delta + delta, ) } @@ -209,8 +215,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( calculateHeadsUpPlaceholderYOffset( scrollOffset.roundToInt(), minScrollOffset.roundToInt(), - stackScrollView.topHeadsUpHeight - ) + stackScrollView.topHeadsUpHeight, + ), ) } .thenIf(isHeadsUp) { @@ -218,11 +224,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( bottomBehavior = NestedScrollBehavior.EdgeAlways ) .nestedScroll(nestedScrollConnection) - .scrollable( - orientation = Orientation.Vertical, - state = scrollableState, - ) - } + .scrollable(orientation = Orientation.Vertical, state = scrollableState) + }, ) } @@ -259,6 +262,7 @@ fun SceneScope.ConstrainedNotificationStack( * Adds the space where notification stack should appear in the scene, with a scrim and nested * scrolling. */ +@OptIn(ExperimentalLayoutApi::class) @Composable fun SceneScope.NotificationScrollingStack( shadeSession: SaveableSession, @@ -291,7 +295,7 @@ fun SceneScope.NotificationScrollingStack( val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp - val screenHeight = LocalRawScreenHeight.current + val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } /** * The height in px of the contents of notification stack. Depending on the number of @@ -325,6 +329,14 @@ fun SceneScope.NotificationScrollingStack( screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() } } + val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false) + + // The bottom Y bound of the currently focused remote input notification. + val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f) + + // The top y bound of the IME. + val imeTop = remember { mutableFloatStateOf(0f) } + // we are not scrolled to the top unless the scrim is at its maximum offset. LaunchedEffect(viewModel, scrimOffset) { snapshotFlow { scrimOffset.value >= 0f } @@ -342,15 +354,34 @@ fun SceneScope.NotificationScrollingStack( LaunchedEffect(syntheticScroll, scrimOffset, scrollState) { snapshotFlow { syntheticScroll.value } .collect { delta -> - val minOffset = minScrimOffset() - if (scrimOffset.value > minOffset) { - val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f) - scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset)) - if (remainingDelta > 0f) { - scrollState.scrollBy(remainingDelta) - } - } else { - scrollState.scrollTo(delta.roundToInt()) + scrollNotificationStack( + scope = coroutineScope, + delta = delta, + animate = false, + scrimOffset = scrimOffset, + minScrimOffset = minScrimOffset, + scrollState = scrollState, + ) + } + } + + // if remote input state changes, compare the row and IME's overlap and offset the scrim and + // placeholder accordingly. + LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) { + imeTop.floatValue = 0f + snapshotFlow { imeTop.floatValue } + .collect { imeTopValue -> + // only scroll the stack if ime value has been populated (ime placeholder has been + // composed at least once), and our remote input row overlaps with the ime bounds. + if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) { + scrollNotificationStack( + scope = coroutineScope, + delta = remoteInputRowBottom - imeTopValue, + animate = true, + scrimOffset = scrimOffset, + minScrimOffset = minScrimOffset, + scrollState = scrollState, + ) } } } @@ -394,12 +425,12 @@ fun SceneScope.NotificationScrollingStack( scrimOffset.value < 0 && layoutState.isTransitioning( from = Scenes.Shade, - to = Scenes.QuickSettings + to = Scenes.QuickSettings, ) ) { IntOffset( x = 0, - y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt() + y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(), ) } else { IntOffset(x = 0, y = scrimOffset.value.roundToInt()) @@ -458,13 +489,11 @@ fun SceneScope.NotificationScrollingStack( .thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() } .debugBackground(viewModel, DEBUG_BOX_COLOR) ) { - NotificationPlaceholder( - stackScrollView = stackScrollView, - viewModel = viewModel, + Column( modifier = Modifier.verticalNestedScrollToScene( topBehavior = NestedScrollBehavior.EdgeWithPreview, - isExternalOverscrollGesture = { isCurrentGestureOverscroll.value } + isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }, ) .thenIf(shadeMode == ShadeMode.Single) { Modifier.nestedScroll(scrimNestedScrollConnection) @@ -473,18 +502,31 @@ fun SceneScope.NotificationScrollingStack( .verticalScroll(scrollState) .padding(top = topPadding) .fillMaxWidth() - .notificationStackHeight( - view = stackScrollView, - totalVerticalPadding = topPadding + bottomPadding, - ) - .onSizeChanged { size -> stackHeight.intValue = size.height }, - ) + ) { + NotificationPlaceholder( + stackScrollView = stackScrollView, + viewModel = viewModel, + modifier = + Modifier.notificationStackHeight( + view = stackScrollView, + totalVerticalPadding = topPadding + bottomPadding, + ) + .onSizeChanged { size -> stackHeight.intValue = size.height }, + ) + Spacer( + modifier = + Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget) + .onGloballyPositioned { coordinates: LayoutCoordinates -> + imeTop.floatValue = screenHeight - coordinates.size.height + } + ) + } } if (shouldIncludeHeadsUpSpace) { HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, - modifier = Modifier.padding(top = topPadding) + modifier = Modifier.padding(top = topPadding), ) } } @@ -572,6 +614,42 @@ private fun SceneScope.NotificationPlaceholder( ) } +private suspend fun scrollNotificationStack( + scope: CoroutineScope, + delta: Float, + animate: Boolean, + scrimOffset: Animatable, + minScrimOffset: () -> Float, + scrollState: ScrollState, +) { + val minOffset = minScrimOffset() + if (scrimOffset.value > minOffset) { + val remainingDelta = + (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt() + if (remainingDelta > 0) { + if (animate) { + // launch a new coroutine for the remainder animation so that it doesn't suspend the + // scrim animation, allowing both to play simultaneously. + scope.launch { scrollState.animateScrollTo(remainingDelta) } + } else { + scrollState.scrollTo(remainingDelta) + } + } + val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset) + if (animate) { + scrimOffset.animateTo(newScrimOffset) + } else { + scrimOffset.snapTo(newScrimOffset) + } + } else { + if (animate) { + scrollState.animateScrollBy(delta) + } else { + scrollState.scrollBy(delta) + } + } +} + private fun calculateCornerRadius( scrimCornerRadius: Dp, screenCornerRadius: Dp, @@ -618,7 +696,7 @@ private fun consumeDeltaWithinRange( setCurrent: (Float) -> Unit, min: Float, max: Float, - delta: Float + delta: Float, ): Float { return if (delta < 0 && current > min) { val remainder = (current + delta - min).coerceAtMost(0f) @@ -631,10 +709,7 @@ private fun consumeDeltaWithinRange( } else 0f } -private inline fun debugLog( - viewModel: NotificationsPlaceholderViewModel, - msg: () -> Any, -) { +private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) { if (viewModel.isDebugLoggingEnabled) { Log.d(TAG, msg().toString()) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index fa92bef34f38bb7d0ba4c47cdef8e39e9ac692ec..d91958adaa1bcd5b1991afa76dfe31b416d1d3cb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource @@ -79,7 +80,6 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout -import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.ExclusiveActivatable @@ -229,17 +229,16 @@ private fun SceneScope.QuickSettingsScene( } .thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() } ) { + val density = LocalDensity.current val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() val customizingAnimationDuration by viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() - val screenHeight = LocalRawScreenHeight.current + val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() } - val collapsedHeaderHeight = - with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } val lifecycleOwner = LocalLifecycleOwner.current val footerActionsViewModel = remember(lifecycleOwner, viewModel) { @@ -268,7 +267,6 @@ private fun SceneScope.QuickSettingsScene( val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() - val density = LocalDensity.current val bottomPadding by animateDpAsState( targetValue = if (isCustomizing) 0.dp else navBarBottomHeight, @@ -394,7 +392,7 @@ private fun SceneScope.QuickSettingsScene( { mediaOffset.roundToPx() }, ) } - Box(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) { + Column(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) { if (mediaInRow) { Layout(content = content, measurePolicy = landscapeQsMediaMeasurePolicy) } else { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index f8d0588c9ae6c6375fe471c30b79da1862721213..8e6cb3fe9fb97cc3f1b463fc03b40836c67e15fe 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -22,6 +22,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -34,6 +35,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope +import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.battery.BatteryMeterViewController @@ -41,6 +43,7 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.panels.ui.compose.EditMode import com.android.systemui.qs.panels.ui.compose.TileGrid import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel @@ -79,16 +82,11 @@ constructor( } @Composable - override fun ContentScope.Content( - modifier: Modifier, - ) { + override fun ContentScope.Content(modifier: Modifier) { val viewModel = rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() } - OverlayShade( - modifier = modifier, - onScrimClicked = viewModel::onScrimClicked, - ) { + OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) { Column { ExpandedShadeHeader( viewModelFactory = viewModel.shadeHeaderViewModelFactory, @@ -98,40 +96,36 @@ constructor( modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding), ) - ShadeBody( - viewModel = viewModel.quickSettingsContainerViewModel, - ) + ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel) } } } } @Composable -fun ShadeBody( - viewModel: QuickSettingsContainerViewModel, -) { +fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) { val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() AnimatedContent( targetState = isEditing, - transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) } + transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) }, ) { editing -> if (editing) { EditMode( viewModel = viewModel.editModeViewModel, - modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding) + modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), ) } else { QuickSettingsLayout( viewModel = viewModel, - modifier = Modifier.sysuiResTag("quick_settings_panel") + modifier = Modifier.sysuiResTag("quick_settings_panel"), ) } } } @Composable -private fun QuickSettingsLayout( +private fun SceneScope.QuickSettingsLayout( viewModel: QuickSettingsContainerViewModel, modifier: Modifier = Modifier, ) { @@ -143,15 +137,18 @@ private fun QuickSettingsLayout( BrightnessSliderContainer( viewModel = viewModel.brightnessSliderViewModel, modifier = - Modifier.fillMaxWidth() - .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight), - ) - TileGrid( - viewModel = viewModel.tileGridViewModel, - modifier = - Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), - viewModel.editModeViewModel::startEditing, + Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight), ) + Box { + GridAnchor() + TileGrid( + viewModel = viewModel.tileGridViewModel, + modifier = + Modifier.fillMaxWidth() + .heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), + viewModel.editModeViewModel::startEditing, + ) + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index b85523bc1694f82761de1050e2f78da1f3f76f07..4162891c0e0b078c46d76708493266ea24b99949 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade.ui.composable +import android.view.HapticFeedbackConstants import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -39,17 +40,20 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.systemui.scene.shared.model.Scenes /** Renders a lightweight shade UI container, as an overlay. */ @Composable @@ -58,6 +62,13 @@ fun SceneScope.OverlayShade( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { + val view = LocalView.current + LaunchedEffect(Unit) { + if (layoutState.currentTransition?.fromContent == Scenes.Gone) { + view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START) + } + } + Box(modifier) { Scrim(onClicked = onScrimClicked) @@ -141,14 +152,17 @@ private fun Modifier.panelPadding(): Modifier { /** Creates a union of [paddingValues] by using the max padding of each edge. */ @Composable private fun combinePaddings(vararg paddingValues: PaddingValues): PaddingValues { - val layoutDirection = LocalLayoutDirection.current - - return PaddingValues( - start = paddingValues.maxOfOrNull { it.calculateStartPadding(layoutDirection) } ?: 0.dp, - top = paddingValues.maxOfOrNull { it.calculateTopPadding() } ?: 0.dp, - end = paddingValues.maxOfOrNull { it.calculateEndPadding(layoutDirection) } ?: 0.dp, - bottom = paddingValues.maxOfOrNull { it.calculateBottomPadding() } ?: 0.dp, - ) + return if (paddingValues.isEmpty()) { + PaddingValues(0.dp) + } else { + val layoutDirection = LocalLayoutDirection.current + PaddingValues( + start = paddingValues.maxOf { it.calculateStartPadding(layoutDirection) }, + top = paddingValues.maxOf { it.calculateTopPadding() }, + end = paddingValues.maxOf { it.calculateEndPadding(layoutDirection) }, + bottom = paddingValues.maxOf { it.calculateBottomPadding() }, + ) + } } object OverlayShade { diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING index f9424ed62d7824594da60ad2a1e5147a2131beb2..65ba037cf995cfdd3a7d1fca19bba63f6b9fb5d4 100644 --- a/packages/SystemUI/compose/scene/TEST_MAPPING +++ b/packages/SystemUI/compose/scene/TEST_MAPPING @@ -1,37 +1,13 @@ { "presubmit": [ { - "name": "PlatformComposeSceneTransitionLayoutTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "PlatformComposeSceneTransitionLayoutTests" }, { - "name": "PlatformComposeCoreTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "PlatformComposeCoreTests" }, { - "name": "SystemUIComposeGalleryTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "SystemUIComposeGalleryTests" } ] } \ No newline at end of file diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index fb9dde345251eeb7583d5ccfd641047684e31141..0bb1d928c2b4d5970bb85c187c29d9c7542875f7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAny +import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.fastSumBy import com.android.compose.ui.util.SpaceVectorConverter @@ -234,8 +235,15 @@ internal class MultiPointerDraggableNode( pointersDown == 0 -> { startedPosition = null - val lastPointerUp = changes.single { it.id == velocityPointerId } - velocityTracker.addPointerInputChange(lastPointerUp) + // In case of multiple events with 0 pointers down (not pressed) we may have + // already removed the velocityPointer + val lastPointerUp = changes.fastFilter { it.id == velocityPointerId } + check(lastPointerUp.isEmpty() || lastPointerUp.size == 1) { + "There are ${lastPointerUp.size} pointers up: $lastPointerUp" + } + if (lastPointerUp.size == 1) { + velocityTracker.addPointerInputChange(lastPointerUp.first()) + } } // The first pointer down, startedPosition was not set. diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 502dbe3e423c6a5d29d50179f2cd7a9186bb9ba8..5ed11ad345ad78cc64b9ebccc507ce71e6ac79a1 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -34,6 +34,7 @@ import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockFaceEvents import com.android.systemui.plugins.clocks.ClockMessageBuffers +import com.android.systemui.plugins.clocks.ClockReactiveSetting import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.DefaultClockFaceLayout import com.android.systemui.plugins.clocks.WeatherData @@ -73,7 +74,7 @@ class DefaultClockController( ClockConfig( DEFAULT_CLOCK_ID, resources.getString(R.string.clock_default_name), - resources.getString(R.string.clock_default_description) + resources.getString(R.string.clock_default_description), ) } @@ -84,14 +85,14 @@ class DefaultClockController( layoutInflater.inflate(R.layout.clock_default_small, parent, false) as AnimatableClockView, settings?.seedColor, - messageBuffers?.smallClockMessageBuffer + messageBuffers?.smallClockMessageBuffer, ) largeClock = LargeClockFaceController( layoutInflater.inflate(R.layout.clock_default_large, parent, false) as AnimatableClockView, settings?.seedColor, - messageBuffers?.largeClockMessageBuffer + messageBuffers?.largeClockMessageBuffer, ) clocks = listOf(smallClock.view, largeClock.view) @@ -272,8 +273,12 @@ class DefaultClockController( } override fun onWeatherDataChanged(data: WeatherData) {} + override fun onAlarmDataChanged(data: AlarmData) {} + override fun onZenDataChanged(data: ZenData) {} + + override fun onReactiveAxesChanged(axes: List) {} } open inner class DefaultClockAnimations( @@ -340,10 +345,9 @@ class DefaultClockController( } } - class AnimationState( - var fraction: Float, - ) { + class AnimationState(var fraction: Float) { var isActive: Boolean = fraction > 0.5f + fun update(newFraction: Float): Pair { if (newFraction == fraction) { return Pair(isActive, false) diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md index 0ac15c583b2988a50bf51a03a3019409299dadf8..234c7a032d2e719e1053c349472b43ce014cc374 100644 --- a/packages/SystemUI/docs/scene.md +++ b/packages/SystemUI/docs/scene.md @@ -68,15 +68,13 @@ file evalutes to `true`. 1. Set a collection of **aconfig flags** to `true` by running the following commands: ```console - $ adb shell device_config override systemui com.android.systemui.scene_container true - $ adb shell device_config override systemui com.android.systemui.compose_lockscreen true $ adb shell device_config override systemui com.android.systemui.keyguard_bottom_area_refactor true $ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true - $ adb shell device_config override systemui com.android.systemui.media_in_scene_container true $ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true - $ adb shell device_config override systemui com.android.systemui.notifications_heads_up_refactor true + $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true $ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true $ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true + $ adb shell device_config override systemui com.android.systemui.scene_container true ``` 2. **Restart** System UI by issuing the following command: ```console diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 156e06843d15df1952728095c6324d700a1951e3..312e62d0b62494bb790833afffb03ef4e6342206 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -195,6 +195,28 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID); } + @Test + public void testUserSwitcherToOneHandedRemovesViews() { + // Can happen when a SIM is inserted into a large screen device + initMode(MODE_USER_SWITCHER); + { + View view1 = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + View view2 = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_header); + assertThat(view1).isNotNull(); + assertThat(view2).isNotNull(); + } + + initMode(MODE_ONE_HANDED); + { + View view1 = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + View view2 = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_header); + assertThat(view1).isNull(); + assertThat(view2).isNull(); + } + } + @Test public void updatePosition_movesKeyguard() { setupForUpdateKeyguardPosition(/* oneHandedMode= */ true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index d86890bf23693346f198044c129c374aec3dbcf8..437a4b35737273c0daa2b3437cf0391e6751c0b8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; @@ -1437,4 +1438,38 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN vibrate is used verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); } + + @Test + public void onAcquiredCalbacks() { + runWithAllParams( + this::ultrasonicCallbackOnAcquired); + } + + public void ultrasonicCallbackOnAcquired(TestParams testParams) throws RemoteException{ + if (testParams.sensorProps.sensorType + == FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC) { + reset(mUdfpsView); + + UdfpsController.Callback callbackMock = mock(UdfpsController.Callback.class); + mUdfpsController.addCallback(callbackMock); + + // GIVEN UDFPS overlay is showing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricRequestConstants.REASON_AUTH_KEYGUARD, + mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mFingerprintManager).setUdfpsOverlayController( + mUdfpsOverlayControllerCaptor.capture()); + mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_START); + mFgExecutor.runAllReady(); + + verify(callbackMock).onFingerDown(); + + mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_GOOD); + mFgExecutor.runAllReady(); + + verify(callbackMock).onFingerUp(); + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index deef65218c4b0448d9642c501a2ec7fef09a7f81..9552564cf1a2f91c169f4e98f8e786420ef0c666 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -16,18 +16,26 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.haptics.msdl.bouncerHapticPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.testKosmos +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -39,11 +47,15 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope + private val msdlPlayer = kosmos.fakeMSDLPlayer + private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer + private val authInteractionProperties = AuthInteractionProperties() private val underTest = kosmos.pinBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Pin, + bouncerHapticPlayer = bouncerHapticPlayer, ) @Before @@ -77,4 +89,42 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(animateFailure).isFalse() } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onAuthenticationResult_playUnlockTokenIfSuccessful() = + testScope.runTest { + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + // Correct PIN: + FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> + underTest.onPinButtonClicked(digit) + } + underTest.onAuthenticateButtonClicked() + runCurrent() + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onAuthenticationResult_playFailureTokenIfFailure() = + testScope.runTest { + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + // Wrong PIN: + FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit -> + underTest.onPinButtonClicked(digit) + } + underTest.onAuthenticateButtonClicked() + runCurrent() + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 492543f215b79ec83dbbf5e08b8d05f3d3312cae..af3ddfca14b6e62154ae724783f2e70e7d52512e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -310,6 +310,41 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { .isEqualTo(displayId) } + @Test + fun afterSuccessfulAuthentication_focusIsNotRequested() = + testScope.runTest { + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) + val textInputFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) + lockDeviceAndOpenPasswordBouncer() + + // remove focus from text field + underTest.onTextFieldFocusChanged(false) + runCurrent() + + // focus should be requested + assertThat(textInputFocusRequested).isTrue() + + // simulate text field getting focus + underTest.onTextFieldFocusChanged(true) + runCurrent() + + // focus should not be requested anymore + assertThat(textInputFocusRequested).isFalse() + + // authenticate successfully. + underTest.onPasswordInputChanged("password") + underTest.onAuthenticateKeyPressed() + runCurrent() + + assertThat(authResult).isTrue() + + // remove focus from text field + underTest.onTextFieldFocusChanged(false) + runCurrent() + // focus should not be requested again + assertThat(textInputFocusRequested).isFalse() + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer @@ -327,10 +362,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { switchToScene(Scenes.Bouncer) } - private suspend fun TestScope.setLockout( - isLockedOut: Boolean, - failedAttemptCount: Int = 5, - ) { + private suspend fun TestScope.setLockout(isLockedOut: Boolean, failedAttemptCount: Int = 5) { if (isLockedOut) { repeat(failedAttemptCount) { kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false) @@ -350,7 +382,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { kosmos.fakeUserRepository.selectedUser.value = SelectedUserModel( userInfo = userInfo, - selectionStatus = SelectionStatus.SELECTION_COMPLETE + selectionStatus = SelectionStatus.SELECTION_COMPLETE, ) advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES) } @@ -374,7 +406,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { subtypes = List(auxiliarySubtypes + nonAuxiliarySubtypes) { InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes) - } + }, ) } @@ -383,9 +415,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private const val WRONG_PASSWORD = "Wrong password" private val USER_INFOS = - listOf( - UserInfo(100, "First user", 0), - UserInfo(101, "Second user", 0), - ) + listOf(UserInfo(100, "First user", 0), UserInfo(101, "Second user", 0)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 7c773a90236725dd3d8f4e4cbf3ec52dd8cb96d8..c163c6fc0a30f2b9e13d5cfdae2b53817c8d6543 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -16,9 +16,11 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.authenticationRepository @@ -27,12 +29,16 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.haptics.msdl.FakeMSDLPlayer +import com.android.systemui.haptics.msdl.bouncerHapticPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -55,10 +61,13 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel } + private val msdlPlayer: FakeMSDLPlayer = kosmos.fakeMSDLPlayer + private val bouncerHapticHelper = kosmos.bouncerHapticPlayer private val underTest = kosmos.patternBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true).asStateFlow(), onIntentionalUserInput = {}, + bouncerHapticPlayer = bouncerHapticHelper, ) private val containerSize = 90 // px @@ -115,10 +124,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { .that(selectedDots) .isEqualTo( CORRECT_PATTERN.subList(0, index + 1).map { - PatternDotViewModel( - x = it.x, - y = it.y, - ) + PatternDotViewModel(x = it.x, y = it.y) } ) assertWithMessage("Wrong current dot for index $index") @@ -174,7 +180,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { listOf( PatternDotViewModel(0, 0), PatternDotViewModel(1, 0), - PatternDotViewModel(2, 0) + PatternDotViewModel(2, 0), ) ) } @@ -200,7 +206,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { listOf( PatternDotViewModel(1, 0), PatternDotViewModel(1, 1), - PatternDotViewModel(1, 2) + PatternDotViewModel(1, 2), ) ) } @@ -228,7 +234,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { listOf( PatternDotViewModel(2, 0), PatternDotViewModel(1, 1), - PatternDotViewModel(0, 2) + PatternDotViewModel(0, 2), ) ) } @@ -300,10 +306,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1 repeat(attempts) { attempt -> underTest.onDragStart() - CORRECT_PATTERN.subList( - 0, - kosmos.authenticationRepository.minPatternLength - 1, - ) + CORRECT_PATTERN.subList(0, kosmos.authenticationRepository.minPatternLength - 1) .forEach { coordinate -> underTest.onDrag( xPx = 30f * coordinate.x + 15, @@ -341,6 +344,16 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(authResult).isTrue() } + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun performDotFeedback_deliversDragToken() = + testScope.runTest { + underTest.performDotFeedback(null) + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + private fun dragOverCoordinates(vararg coordinatesDragged: Point) { underTest.onDragStart() coordinatesDragged.forEach(::dragToCoordinate) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 2ee4aee2abab43e5719af77212bff3343c6ab0d6..af5f2acb444d14688ee0cc3e0050c2f514e63e90 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.input.key.KeyEventType import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -35,12 +36,15 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository import com.android.systemui.classifier.fakeFalsingCollector import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.haptics.msdl.bouncerHapticPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlin.random.Random import kotlin.random.nextInt @@ -64,11 +68,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } + private val msdlPlayer = kosmos.fakeMSDLPlayer + private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer private val underTest by lazy { kosmos.pinBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Pin, + bouncerHapticPlayer = bouncerHapticPlayer, ) } @@ -97,6 +104,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Sim, + bouncerHapticPlayer = bouncerHapticPlayer, ) assertThat(underTest.isSimAreaVisible).isTrue() @@ -122,6 +130,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { isInputEnabled = MutableStateFlow(true), onIntentionalUserInput = {}, authenticationMethod = AuthenticationMethodModel.Pin, + bouncerHapticPlayer = bouncerHapticPlayer, ) kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) val hintedPinLength by collectLastValue(underTest.hintedPinLength) @@ -487,11 +496,39 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { lockDeviceAndOpenPinBouncer() - underTest.onDigitButtonDown() + underTest.onDigitButtonDown(null) assertTrue(kosmos.fakeFalsingCollector.wasLastGestureAvoided()) } + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onDigiButtonDown_deliversKeyStandardToken() = + testScope.runTest { + underTest.onDigitButtonDown(null) + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_STANDARD) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onBackspaceButtonPressed_deliversKeyDeleteToken() { + underTest.onBackspaceButtonPressed(null) + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_DELETE) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun onBackspaceButtonLongPressed_deliversLongPressToken() { + underTest.onBackspaceButtonLongPressed() + + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 956c12916c98c799c8ade5881d0fa9f8e0c0c7f5..2028d28804bddb6899b745cb9e635056e01e30c4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.view.MotionEvent; import android.view.accessibility.AccessibilityManager; @@ -33,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -201,12 +204,21 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) public void testTrackpadGesture() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); } + @Test + @EnableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) + public void testTrackpadGesture_touchScreenSource_false() { + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + when(mFalsingDataProvider.isTouchScreenSource()).thenReturn(false); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + } + @Test public void testAddAndRemoveFalsingBeliefListener() { verify(mHistoryTracker, never()).addBeliefListener(any()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index df4b0480f5c72deb51c8c7b3b81c9b2de2e37c09..5a4799cecae554002b25816ca9a92413b83e94b5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -312,6 +312,7 @@ public class FalsingDataProviderTest extends ClassifierTest { } @Test + @DisableFlags(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING) public void test_IsFromTrackpad() { MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 70529cc762e074215133fa1db3a0e4ab83052b5d..ee65fbd810aef94a8bdc2f393a4729907a7c3a01 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -21,6 +21,8 @@ import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.logging.uiEventLogger +import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR import com.android.systemui.SysuiTestCase @@ -28,6 +30,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.coroutines.collectLastValue @@ -35,7 +38,6 @@ import com.android.systemui.dock.dockManager import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -93,6 +95,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { bgScope = applicationCoroutineScope, mainDispatcher = testDispatcher, centralSurfacesOpt = centralSurfacesOptional, + uiEventLogger = uiEventLoggerFake, ) .apply { start() } @@ -119,7 +122,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -140,7 +143,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -161,7 +164,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) @@ -181,7 +184,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE, - testScope = this + testScope = this, ) // Scene change will be handled in EditWidgetsActivity not here assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -200,7 +203,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -220,7 +223,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) } @@ -240,7 +243,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -258,7 +261,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Blank) } @@ -276,7 +279,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -298,7 +301,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2) @@ -307,7 +310,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, to = KeyguardState.GLANCEABLE_HUB, - testScope = this + testScope = this, ) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY) @@ -327,7 +330,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) updateDocked(true) @@ -349,7 +352,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - testScope = this + testScope = this, ) updateDocked(true) @@ -361,7 +364,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.DREAMING, - testScope = this + testScope = this, ) advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY) assertThat(scene).isEqualTo(CommunalScenes.Blank) @@ -511,6 +514,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) + assertThat(uiEventLoggerFake.logs.first().eventId) + .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) } } @@ -526,7 +532,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB, - testScope = this + testScope = this, ) assertThat(scene).isEqualTo(CommunalScenes.Communal) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt index b3ffc7159f7c675add20cb4904be047f3a43fcac..d6734e85ed77a2de9f56288e54307e734b400a99 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.domain.interactor import android.app.ActivityManager.RunningTaskInfo import android.app.usage.UsageEvents import android.content.pm.UserInfo +import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -28,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.activityStarter import com.android.systemui.settings.fakeUserTracker import com.android.systemui.shared.system.taskStackChangeListeners @@ -48,6 +50,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times @@ -89,6 +92,27 @@ class WidgetTrampolineInteractorTest : SysuiTestCase() { verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any()) } + @Test + fun testNewTaskStartsWhileOnHub_stopsDream() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + moveTaskToFront() + + argumentCaptor().apply { + verify(activityStarter).dismissKeyguardThenExecute(capture(), anyOrNull(), any()) + + firstValue.onDismiss() + runCurrent() + + // Dream is stopped once keyguard is dismissed. + verify(kosmos.dreamManager).stopDream() + } + } + @Test fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() = testScope.runTest { @@ -209,7 +233,7 @@ class WidgetTrampolineInteractorTest : SysuiTestCase() { ownerName = "test", ), ), - testScope + testScope, ) runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index c2acc5ff668986ba1b09e8399578a2dfc902904d..160865d625f5264d46a9a63fdcb128583dfcaea0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -31,8 +31,11 @@ import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.AuthenticationFlags +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest @@ -211,7 +214,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) kosmos.fakeUserRepository.setSelectedUserInfo( primaryUser, - SelectionStatus.SELECTION_COMPLETE + SelectionStatus.SELECTION_COMPLETE, ) kosmos.fakeTrustRepository.setCurrentUserTrusted(true) @@ -239,6 +242,49 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { assertThat(deviceUnlockStatus?.isUnlocked).isFalse() } + @Test + fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = this, + ) + kosmos.powerInteractor.setAsleepForTest() + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + } + + @Test + fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + assertThat(kosmos.keyguardTransitionInteractor.getCurrentState()) + .isEqualTo(KeyguardState.LOCKSCREEN) + + kosmos.powerInteractor.setAsleepForTest() + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + } + @Test fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() = testScope.runTest { @@ -273,7 +319,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to DeviceEntryRestrictionReason.UserLockdown, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to - DeviceEntryRestrictionReason.PolicyLockdown + DeviceEntryRestrictionReason.PolicyLockdown, ) } @@ -285,7 +331,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - "not mainline reboot" + "not mainline reboot", ) runCurrent() @@ -321,7 +367,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - "not mainline reboot" + "not mainline reboot", ) runCurrent() @@ -358,7 +404,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - "not mainline reboot" + "not mainline reboot", ) runCurrent() @@ -394,12 +440,12 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { collectLastValue(underTest.deviceEntryRestrictionReason) kosmos.fakeSystemPropertiesHelper.set( DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP, - DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE + DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE, ) kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags( AuthenticationFlags( userId = 1, - flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT + flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT, ) ) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt index 50b727c3fed92a2a94d934297334958e93ae9fa0..9cfd328a948435ae4a670676a8b963a78332628b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.coroutines.collectLastValue import com.android.systemui.education.data.model.EduDeviceConnectionTime import com.android.systemui.education.data.model.GestureEduModel +import com.android.systemui.education.domain.interactor.mockEduInputManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -62,7 +63,13 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { // Create TestContext here because TemporaryFolder.create() is called in @Before. It is // needed before calling TemporaryFolder.newFolder(). val testContext = TestContext(context, tmpFolder.newFolder()) - underTest = UserContextualEducationRepository(testContext, dsScopeProvider) + underTest = + UserContextualEducationRepository( + testContext, + dsScopeProvider, + kosmos.mockEduInputManager, + kosmos.testDispatcher + ) underTest.setUser(testUserId) } @@ -99,7 +106,8 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(), lastEducationTime = kosmos.fakeEduClock.instant(), usageSessionStartTime = kosmos.fakeEduClock.instant(), - userId = testUserId + userId = testUserId, + gestureType = BACK ) underTest.updateGestureEduModel(BACK) { newModel } val model by collectLastValue(underTest.readGestureEduModelFlow(BACK)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt index 64915fbf551f784ab257e7992ec0e5645ff15a6c..8201bbe4dc47ea407d207aac7f6cce4d9913e26f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt @@ -17,15 +17,13 @@ package com.android.systemui.education.domain.interactor import android.content.pm.UserInfo -import android.hardware.input.InputManager -import android.hardware.input.KeyGestureEvent -import android.view.KeyEvent -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.contextualeducation.GestureType import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.education.data.model.GestureEduModel @@ -40,20 +38,21 @@ import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.kotlin.any -import org.mockito.kotlin.verify +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi -class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { +class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val contextualEduInteractor = kosmos.contextualEducationInteractor @@ -71,21 +70,27 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { underTest.start() contextualEduInteractor.start() userRepository.setUserInfos(USER_INFOS) + testScope.launch { + contextualEduInteractor.updateKeyboardFirstConnectionTime() + contextualEduInteractor.updateTouchpadFirstConnectionTime() + } } @Test fun newEducationInfoOnMaxSignalCountReached() = testScope.runTest { - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) val model by collectLastValue(underTest.educationTriggered) - assertThat(model?.gestureType).isEqualTo(BACK) + + assertThat(model?.gestureType).isEqualTo(gestureType) } @Test fun newEducationToastOn1stEducation() = testScope.runTest { val model by collectLastValue(underTest.educationTriggered) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) + assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast) } @@ -93,12 +98,12 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun newEducationNotificationOn2ndEducation() = testScope.runTest { val model by collectLastValue(underTest.educationTriggered) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) // runCurrent() to trigger 1st education runCurrent() eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) } @@ -106,7 +111,7 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { @Test fun noEducationInfoBeforeMaxSignalCountReached() = testScope.runTest { - contextualEduInteractor.incrementSignalCount(BACK) + contextualEduInteractor.incrementSignalCount(gestureType) val model by collectLastValue(underTest.educationTriggered) assertThat(model).isNull() } @@ -115,8 +120,8 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun noEducationInfoWhenShortcutTriggeredPreviously() = testScope.runTest { val model by collectLastValue(underTest.educationTriggered) - contextualEduInteractor.updateShortcutTriggerTime(BACK) - triggerMaxEducationSignals(BACK) + contextualEduInteractor.updateShortcutTriggerTime(gestureType) + triggerMaxEducationSignals(gestureType) assertThat(model).isNull() } @@ -124,12 +129,12 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun no2ndEducationBeforeMinEduIntervalReached() = testScope.runTest { val models by collectValues(underTest.educationTriggered) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() // Offset a duration that is less than the required education interval eduClock.offset(1.seconds) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() assertThat(models.filterNotNull().size).isEqualTo(1) @@ -140,15 +145,15 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { testScope.runTest { val models by collectValues(underTest.educationTriggered) // Trigger 2 educations - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() // Try triggering 3rd education eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) assertThat(models.filterNotNull().size).isEqualTo(2) } @@ -157,18 +162,21 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() = testScope.runTest { val model by - collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK)) - contextualEduInteractor.incrementSignalCount(BACK) + collectLastValue( + kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType) + ) + contextualEduInteractor.incrementSignalCount(gestureType) eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds)) val secondSignalReceivedTime = eduClock.instant() - contextualEduInteractor.incrementSignalCount(BACK) + contextualEduInteractor.incrementSignalCount(gestureType) assertThat(model) .isEqualTo( GestureEduModel( signalCount = 1, usageSessionStartTime = secondSignalReceivedTime, - userId = 0 + userId = 0, + gestureType = gestureType ) ) } @@ -252,22 +260,9 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { @Test fun updateShortcutTimeOnKeyboardShortcutTriggered() = testScope.runTest { - // runCurrent() to trigger inputManager#registerKeyGestureEventListener in the - // interactor - runCurrent() - val listenerCaptor = - ArgumentCaptor.forClass(InputManager.KeyGestureEventListener::class.java) - verify(kosmos.mockEduInputManager) - .registerKeyGestureEventListener(any(), listenerCaptor.capture()) - - val allAppsKeyGestureEvent = - KeyGestureEvent.Builder() - .setDeviceId(1) - .setModifierState(KeyEvent.META_META_ON) - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) - .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - .build() - listenerCaptor.value.onKeyGestureEvent(allAppsKeyGestureEvent) + // Only All Apps needs to update the keyboard shortcut + assumeTrue(gestureType == ALL_APPS) + kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS) val model by collectLastValue( @@ -293,10 +288,18 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { runCurrent() } + private suspend fun setUpForDeviceConnection() { + contextualEduInteractor.updateKeyboardFirstConnectionTime() + contextualEduInteractor.updateTouchpadFirstConnectionTime() + } + companion object { - private val USER_INFOS = - listOf( - UserInfo(101, "Second User", 0), - ) + private val USER_INFOS = listOf(UserInfo(101, "Second User", 0)) + + @JvmStatic + @Parameters(name = "{0}") + fun getGestureTypes(): List { + return listOf(BACK, HOME, OVERVIEW, ALL_APPS) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt index c4ac585f7e4abe0fdf12cbb896e419410068be96..ab33269ec954b58199c42c1522af1a6730875e28 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -69,6 +70,11 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { @Before fun setUp() { + testScope.launch { + interactor.updateKeyboardFirstConnectionTime() + interactor.updateTouchpadFirstConnectionTime() + } + val viewModel = ContextualEduViewModel( kosmos.applicationContext.resources, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index 686b518b56e0781c368117fedabec724a4bc6825..366b55db4f20b965b616238a36456e57ac557206 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.classifier.falsingManager import com.android.systemui.haptics.fakeVibratorHelper import com.android.systemui.kosmos.testScope import com.android.systemui.log.core.FakeLogBuffer @@ -68,11 +69,13 @@ class QSLongPressEffectTest : SysuiTestCase() { vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_SPIN] = spinDuration whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true) + kosmos.falsingManager.setFalseLongTap(false) longPressEffect = QSLongPressEffect( vibratorHelper, kosmos.keyguardStateController, + kosmos.falsingManager, FakeLogBuffer.Factory.create(), ) longPressEffect.callback = callback @@ -180,11 +183,7 @@ class QSLongPressEffectTest : SysuiTestCase() { // THEN the expected texture is played val reverseHaptics = - LongPressHapticBuilder.createReversedEffect( - progress, - lowTickDuration, - effectDuration, - ) + LongPressHapticBuilder.createReversedEffect(progress, lowTickDuration, effectDuration) assertThat(reverseHaptics).isNotNull() assertThat(vibratorHelper.hasVibratedWithEffects(reverseHaptics!!)).isTrue() } @@ -223,6 +222,20 @@ class QSLongPressEffectTest : SysuiTestCase() { verify(callback, times(1)).onResetProperties() } + @Test + fun onAnimationComplete_isFalseLongClick_effectEndsInIdleWithReset() = + testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) { + // GIVEN that the long-click is false + kosmos.falsingManager.setFalseLongTap(true) + + // GIVEN that the animation completes + longPressEffect.handleAnimationComplete() + + // THEN the long-press effect ends in the idle state and the properties are reset + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + verify(callback, times(1)).onResetProperties() + } + @Test fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() = testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt index 0c716137f4343334716f8401480863a53036822d..639737b37efd843fa79c08090016e11a75a722e1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD @@ -53,6 +54,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock +import org.mockito.kotlin.mock @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -81,6 +83,7 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() { Optional.of(kosmos.touchpadGesturesInteractor), KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo), hasTouchpadTutorialScreens, + mock(), SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral)) ) lifecycle.addObserver(viewModel) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt index 361e768a5b51dd3b6de560de55537ffe2ed968c1..8f9e23824809ce9970a10c64dc8161b5c7cef817 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt @@ -17,11 +17,13 @@ package com.android.systemui.keyboard.data.repository -import android.hardware.input.InputManager +import android.hardware.input.FakeInputManager +import android.hardware.input.InputManager.InputDeviceListener import android.hardware.input.InputManager.KeyboardBacklightListener import android.hardware.input.KeyboardBacklightState +import android.hardware.input.fakeInputManager import android.testing.TestableLooper -import android.view.InputDevice +import android.view.InputDevice.SOURCE_KEYBOARD import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -30,10 +32,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.inputdevice.data.repository.InputDeviceRepository import com.android.systemui.keyboard.data.model.Keyboard -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.nullable -import com.android.systemui.util.mockito.whenever +import com.android.systemui.testKosmos import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher @@ -50,9 +49,10 @@ 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.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -60,10 +60,9 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class KeyboardRepositoryTest : SysuiTestCase() { - @Captor - private lateinit var deviceListenerCaptor: ArgumentCaptor + @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor @Captor private lateinit var backlightListenerCaptor: ArgumentCaptor - @Mock private lateinit var inputManager: InputManager + private lateinit var fakeInputManager: FakeInputManager private lateinit var underTest: KeyboardRepository private lateinit var dispatcher: CoroutineDispatcher @@ -73,16 +72,14 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf()) - whenever(inputManager.getInputDevice(any())).then { invocation -> - val id = invocation.arguments.first() - INPUT_DEVICES_MAP[id] - } + fakeInputManager = testKosmos().fakeInputManager dispatcher = StandardTestDispatcher() testScope = TestScope(dispatcher) val handler = FakeHandler(TestableLooper.get(this).looper) - inputDeviceRepo = InputDeviceRepository(handler, testScope.backgroundScope, inputManager) - underTest = KeyboardRepositoryImpl(dispatcher, inputManager, inputDeviceRepo) + inputDeviceRepo = + InputDeviceRepository(handler, testScope.backgroundScope, fakeInputManager.inputManager) + underTest = + KeyboardRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo) } @Test @@ -95,7 +92,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() = testScope.runTest { - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID)) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) val initialValue = underTest.isAnyKeyboardConnected.first() assertThat(initialValue).isTrue() } @@ -103,74 +100,77 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsConnected_whenNewPhysicalKeyboardConnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() } @Test - fun emitsDisconnected_whenDeviceWithIdDoesNotExist() = + fun emitsDisconnected_whenDeviceNotFound() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - - deviceListener.onInputDeviceAdded(NULL_DEVICE_ID) + fakeInputManager.addDevice(NULL_DEVICE_ID, SOURCE_KEYBOARD, isNotFound = true) assertThat(isKeyboardConnected).isFalse() } @Test fun emitsDisconnected_whenKeyboardDisconnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() - deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isFalse() } - private suspend fun captureDeviceListener(): InputManager.InputDeviceListener { + private suspend fun captureDeviceListener() { underTest.isAnyKeyboardConnected.first() - verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable()) - return deviceListenerCaptor.value + verify(fakeInputManager.inputManager) + .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull()) + fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value) } @Test fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard( + PHYSICAL_NOT_FULL_KEYBOARD_ID, + isFullKeyboard = false + ) assertThat(isKeyboardConnected).isFalse() - deviceListener.onInputDeviceAdded(VIRTUAL_FULL_KEYBOARD_ID) + fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD) assertThat(isKeyboardConnected).isFalse() } @Test fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isFalse() } @Test fun emitsConnected_whenAnotherDeviceDisconnects() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(VIRTUAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() } @@ -178,12 +178,12 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() = testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceRemoved(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() } @@ -195,7 +195,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { // subscribed to and listener is actually registered in inputManager val backlight by collectLastValueImmediately(underTest.backlight) - verify(inputManager) + verify(fakeInputManager.inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) backlightListenerCaptor.value.onBacklightChanged(current = 1, max = 5) @@ -217,7 +217,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun keyboardBacklightValuesNotPassed_fromBacklightListener_whenNotTriggeredByKeyPress() { testScope.runTest { val backlight by collectLastValueImmediately(underTest.backlight) - verify(inputManager) + verify(fakeInputManager.inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) backlightListenerCaptor.value.onBacklightChanged( @@ -233,7 +233,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun passesKeyboardBacklightValues_fromBacklightListener_whenTriggeredByKeyPress() { testScope.runTest { val backlight by collectLastValueImmediately(underTest.backlight) - verify(inputManager) + verify(fakeInputManager.inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) backlightListenerCaptor.value.onBacklightChanged( @@ -248,14 +248,11 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun passessAllKeyboards_thatWereAlreadyConnectedOnInitialization() { testScope.runTest { - whenever(inputManager.inputDeviceIds) - .thenReturn( - intArrayOf( - PHYSICAL_FULL_KEYBOARD_ID, - ANOTHER_PHYSICAL_FULL_KEYBOARD_ID, - VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2 - ) - ) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + // not a physical keyboard - that's why result is 2 + fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD) + val keyboards by collectValues(underTest.newlyConnectedKeyboard) assertThat(keyboards).hasSize(2) @@ -265,9 +262,9 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun passesNewlyConnectedKeyboard() { testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID, VENDOR_ID, PRODUCT_ID) assertThat(underTest.newlyConnectedKeyboard.first()) .isEqualTo(Keyboard(VENDOR_ID, PRODUCT_ID)) @@ -277,13 +274,12 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsOnlyNewlyConnectedKeyboards() { testScope.runTest { - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID)) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) underTest.newlyConnectedKeyboard.first() - verify(inputManager) - .registerInputDeviceListener(deviceListenerCaptor.capture(), nullable()) - val deviceListener = deviceListenerCaptor.value - deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + captureDeviceListener() + + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) val keyboards by collectValues(underTest.newlyConnectedKeyboard) assertThat(keyboards).hasSize(1) @@ -293,14 +289,11 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun stillEmitsNewKeyboardEvenIfFlowWasSubscribedAfterOtherFlows() { testScope.runTest { - whenever(inputManager.inputDeviceIds) - .thenReturn( - intArrayOf( - PHYSICAL_FULL_KEYBOARD_ID, - ANOTHER_PHYSICAL_FULL_KEYBOARD_ID, - VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2 - ) - ) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + // not a physical keyboard - that's why result is 2 + fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD) + collectLastValueImmediately(underTest.isAnyKeyboardConnected) // let's pretend second flow is subscribed after some delay @@ -314,12 +307,12 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsKeyboardWhenItWasReconnected() { testScope.runTest { - val deviceListener = captureDeviceListener() + captureDeviceListener() val keyboards by collectValues(underTest.newlyConnectedKeyboard) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) - deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID) + fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID) assertThat(keyboards).hasSize(2) } @@ -339,30 +332,13 @@ class KeyboardRepositoryTest : SysuiTestCase() { private companion object { private const val PHYSICAL_FULL_KEYBOARD_ID = 1 - private const val VIRTUAL_FULL_KEYBOARD_ID = 2 + private const val VIRTUAL_FULL_KEYBOARD_ID = -2 // Virtual keyboards has id with minus value private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3 private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4 - private const val NULL_DEVICE_ID = 5 + private const val NULL_DEVICE_ID = -5 private const val VENDOR_ID = 99 private const val PRODUCT_ID = 101 - - private val INPUT_DEVICES_MAP: Map = - mapOf( - PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true), - VIRTUAL_FULL_KEYBOARD_ID to inputDevice(virtual = true, fullKeyboard = true), - PHYSICAL_NOT_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = false), - ANOTHER_PHYSICAL_FULL_KEYBOARD_ID to - inputDevice(virtual = false, fullKeyboard = true) - ) - - private fun inputDevice(virtual: Boolean, fullKeyboard: Boolean): InputDevice = - mock().also { - whenever(it.isVirtual).thenReturn(virtual) - whenever(it.isFullKeyboard).thenReturn(fullKeyboard) - whenever(it.vendorId).thenReturn(VENDOR_ID) - whenever(it.productId).thenReturn(PRODUCT_ID) - } } private class TestBacklightState( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index 6c3c7ef0162d378285e3d9d3bd4782dc4198dc0f..fcf4662be145d2364e4855e16d0815e97c018026 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -16,7 +16,10 @@ */ package com.android.systemui.keyguard.data.quickaffordance +import android.app.Flags import android.net.Uri +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF @@ -25,6 +28,7 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.EnableZenModeDialog +import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription @@ -35,7 +39,11 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.settings.data.repository.secureSettingsRepository import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository +import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository +import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq @@ -43,6 +51,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat +import java.time.Duration import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -66,8 +75,13 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { private val kosmos = testKosmos() private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope + private val settings = kosmos.fakeSettings + private val zenModeRepository = kosmos.fakeZenModeRepository + private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository + private val secureSettingsRepository = kosmos.secureSettingsRepository + @Mock private lateinit var zenModeController: ZenModeController @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var conditionUri: Uri @@ -85,16 +99,35 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { DoNotDisturbQuickAffordanceConfig( context, zenModeController, + kosmos.zenModeInteractor, settings, userTracker, testDispatcher, + testScope.backgroundScope, conditionUri, enableZenModeDialog, ) } @Test + @EnableFlags(Flags.FLAG_MODES_UI) fun dndNotAvailable_pickerStateHidden() = + testScope.runTest { + deviceProvisioningRepository.setDeviceProvisioned(false) + runCurrent() + + val result = underTest.getPickerScreenState() + runCurrent() + + assertEquals( + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, + result, + ) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun controllerDndNotAvailable_pickerStateHidden() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(false) @@ -105,12 +138,32 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { // then assertEquals( KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, - result + result, ) } @Test + @EnableFlags(Flags.FLAG_MODES_UI) fun dndAvailable_pickerStateVisible() = + testScope.runTest { + deviceProvisioningRepository.setDeviceProvisioned(true) + runCurrent() + + val result = underTest.getPickerScreenState() + runCurrent() + + assertThat(result) + .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java) + val defaultPickerState = + result as KeyguardQuickAffordanceConfig.PickerScreenState.Default + assertThat(defaultPickerState.configureIntent).isNotNull() + assertThat(defaultPickerState.configureIntent?.action) + .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun controllerDndAvailable_pickerStateVisible() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -129,7 +182,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsNotOff_setToOff() = + testScope.runTest { + val currentModes by collectLastValue(zenModeRepository.modes) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2) + collectLastValue(underTest.lockScreenState) + runCurrent() + + val result = underTest.onTriggered(null) + runCurrent() + + val dndMode = currentModes!!.single() + assertThat(dndMode.isActive).isFalse() + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -140,11 +213,12 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { // when val result = underTest.onTriggered(null) + verify(zenModeController) .setZen( spyZenMode.capture(), spyConditionId.capture(), - eq(DoNotDisturbQuickAffordanceConfig.TAG) + eq(DoNotDisturbQuickAffordanceConfig.TAG), ) // then @@ -154,7 +228,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsOff_settingFOREVER_setZenWithoutCondition() = + testScope.runTest { + val currentModes by collectLastValue(zenModeRepository.modes) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER) + collectLastValue(underTest.lockScreenState) + runCurrent() + + val result = underTest.onTriggered(null) + runCurrent() + + val dndMode = currentModes!!.single() + assertThat(dndMode.isActive).isTrue() + assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull() + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -169,7 +264,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { .setZen( spyZenMode.capture(), spyConditionId.capture(), - eq(DoNotDisturbQuickAffordanceConfig.TAG) + eq(DoNotDisturbQuickAffordanceConfig.TAG), ) // then @@ -179,7 +274,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() = + testScope.runTest { + val currentModes by collectLastValue(zenModeRepository.modes) + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900) + runCurrent() + + val result = underTest.onTriggered(null) + runCurrent() + + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + val dndMode = currentModes!!.single() + assertThat(dndMode.isActive).isTrue() + assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)) + .isEqualTo(Duration.ofMinutes(-900)) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -194,7 +309,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { .setZen( spyZenMode.capture(), spyConditionId.capture(), - eq(DoNotDisturbQuickAffordanceConfig.TAG) + eq(DoNotDisturbQuickAffordanceConfig.TAG), ) // then @@ -204,7 +319,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun onTriggered_dndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() = + testScope.runTest { + val expandable: Expandable = mock() + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT) + whenever(enableZenModeDialog.createDialog()).thenReturn(mock()) + collectLastValue(underTest.lockScreenState) + runCurrent() + + val result = underTest.onTriggered(expandable) + + assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog) + assertEquals( + expandable, + (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable, + ) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() = testScope.runTest { // given val expandable: Expandable = mock() @@ -222,12 +358,30 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog) assertEquals( expandable, - (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable + (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable, ) } @Test + @EnableFlags(Flags.FLAG_MODES_UI) fun lockScreenState_dndAvailableStartsAsTrue_changeToFalse_StateIsHidden() = + testScope.runTest { + deviceProvisioningRepository.setDeviceProvisioned(true) + val valueSnapshot = collectLastValue(underTest.lockScreenState) + val secondLastValue = valueSnapshot() + runCurrent() + + deviceProvisioningRepository.setDeviceProvisioned(false) + runCurrent() + val lastValue = valueSnapshot() + + assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun lockScreenState_controllerDndAvailableStartsAsTrue_changeToFalse_StateIsHidden() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -246,7 +400,44 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun lockScreenState_dndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() = + @EnableFlags(Flags.FLAG_MODES_UI) + fun lockScreenState_dndModeStartsAsOff_changeToOn_StateVisible() = + testScope.runTest { + val lockScreenState by collectLastValue(underTest.lockScreenState) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + runCurrent() + + assertThat(lockScreenState) + .isEqualTo( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_off, + ContentDescription.Resource(R.string.dnd_is_off), + ), + ActivationState.Inactive, + ) + ) + + zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id) + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE) + runCurrent() + + assertThat(lockScreenState) + .isEqualTo( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_on, + ContentDescription.Resource(R.string.dnd_is_on), + ), + ActivationState.Active, + ) + ) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + fun lockScreenState_controllerDndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() = testScope.runTest { // given whenever(zenModeController.isZenAvailable).thenReturn(true) @@ -265,9 +456,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_dnd_icon_off, - ContentDescription.Resource(R.string.dnd_is_off) + ContentDescription.Resource(R.string.dnd_is_off), ), - ActivationState.Inactive + ActivationState.Inactive, ), secondLastValue, ) @@ -275,9 +466,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_dnd_icon_on, - ContentDescription.Resource(R.string.dnd_is_on) + ContentDescription.Resource(R.string.dnd_is_on), ), - ActivationState.Active + ActivationState.Active, ), lastValue, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index c18deb134075d90d64b901eba58b04e70b35e1f5..fac931273ac74a374f52e30683cd5b3f96031516 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -14,22 +14,6 @@ * limitations under the License. */ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.android.systemui.keyguard.domain.interactor import android.os.PowerManager @@ -47,7 +31,6 @@ import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -129,7 +112,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.DOZING, - testScope + testScope, ) kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE) reset(transitionRepository) @@ -145,10 +128,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Under default conditions, we should transition to LOCKSCREEN when waking up. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -166,10 +146,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible and communal is available, then we should transition to // GLANCEABLE_HUB when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.GLANCEABLE_HUB, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB) } @Test @@ -186,8 +163,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible and communal is available, then we should transition to // GLANCEABLE_HUB when waking up due to power button press. - verify(kosmos.fakeCommunalSceneRepository) - .changeScene(CommunalScenes.Communal, CommunalTransitionKeys.Immediately) + verify(kosmos.fakeCommunalSceneRepository).snapToScene(CommunalScenes.Communal) } @Test @@ -204,10 +180,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is NOT possible but communal is available, then we should transition to // LOCKSCREEN when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -224,10 +197,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // If dreaming is possible but communal is NOT available, then we should transition to // LOCKSCREEN when waking up due to power button press. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) } @Test @@ -245,10 +215,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Under default conditions, we should transition to LOCKSCREEN when waking up. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.GLANCEABLE_HUB, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB) } @Test @@ -261,10 +228,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) } @Test @@ -282,10 +246,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. assertThat(transitionRepository) - .startedTransition( - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, - ) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index 59f16d70fab5135a3284e122611b98cb77968b60..84b7f5c282653d5c2b19c54c1f11dc251b28352b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -17,17 +17,16 @@ package com.android.systemui.keyguard.domain.interactor -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint @@ -59,8 +58,8 @@ import org.mockito.MockitoAnnotations class KeyguardBlueprintInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val underTest = kosmos.keyguardBlueprintInteractor - private val keyguardBlueprintRepository = kosmos.keyguardBlueprintRepository + private val underTest by lazy { kosmos.keyguardBlueprintInteractor } + private val keyguardBlueprintRepository by lazy { kosmos.keyguardBlueprintRepository } private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository } private val configurationRepository by lazy { kosmos.fakeConfigurationRepository } private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository } @@ -75,7 +74,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { sensorId = 1, strength = SensorStrength.STRONG, sensorType = FingerprintSensorType.POWER_BUTTON, - sensorLocations = mapOf() + sensorLocations = mapOf(), ) } @@ -93,7 +92,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer fun testAppliesSplitShadeBlueprint() { testScope.runTest { val blueprintId by collectLastValue(underTest.blueprintId) @@ -107,7 +106,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + @EnableSceneContainer fun testDoesNotApplySplitShadeBlueprint() { testScope.runTest { val blueprintId by collectLastValue(underTest.blueprintId) @@ -122,7 +121,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer fun fingerprintPropertyInitialized_updatesBlueprint() { testScope.runTest { underTest.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt index 41cc953cd1c21bdf22a0bee139ba2911b8b0ed8a..ba689179c33d8d95873603959b5102421a2da863 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt @@ -81,6 +81,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { powerInteractor = kosmos.powerInteractor, alternateBouncerInteractor = kosmos.alternateBouncerInteractor, shadeInteractor = { kosmos.shadeInteractor }, + keyguardInteractor = { kosmos.keyguardInteractor }, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 5a6f2be1c9f050dfe5ecc1b4dbb711cf764774ab..77106aec2fb47af87c1dead9d8564d76c6d5f8be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -137,9 +137,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @JvmStatic @Parameters(name = "{0}") fun getParams(): List { - return FlagsParameterization.allCombinationsOf( - FLAG_COMMUNAL_SCENE_KTF_REFACTOR, - ) + return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) .andSceneContainer() } } @@ -157,9 +155,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) if (!SceneContainerFlag.isEnabled) { - mSetFlagsRule.disableFlags( - Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - ) + mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) } featureFlags = FakeFeatureFlags() @@ -194,7 +190,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest ownerName = "FromLockscreenTransitionInteractor" + "(#listenForLockscreenToPrimaryBouncer)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -219,7 +215,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DOZING, from = KeyguardState.OCCLUDED, ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -244,7 +240,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.AOD, from = KeyguardState.OCCLUDED, ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -273,7 +269,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DREAMING, from = KeyguardState.LOCKSCREEN, ownerName = "FromLockscreenTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -303,7 +299,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, from = KeyguardState.LOCKSCREEN, ownerName = "FromLockscreenTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -328,7 +324,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DOZING, from = KeyguardState.LOCKSCREEN, ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -353,7 +349,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.AOD, from = KeyguardState.LOCKSCREEN, ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -373,7 +369,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( KeyguardState.GONE, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ) // WHEN the lockscreen hosted dream stops @@ -385,7 +381,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.LOCKSCREEN, from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ownerName = "FromDreamingLockscreenHostedTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -398,7 +394,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( KeyguardState.GONE, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ) // WHEN biometrics succeeds with wake and unlock from dream mode @@ -412,7 +408,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.GONE, from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ownerName = "FromDreamingLockscreenHostedTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -429,7 +425,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( KeyguardState.GONE, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ) // WHEN the primary bouncer is set to show @@ -441,7 +437,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.PRIMARY_BOUNCER, from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ownerName = "FromDreamingLockscreenHostedTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -458,7 +454,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( KeyguardState.GONE, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ) // WHEN the device begins to sleep @@ -473,7 +469,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DOZING, from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ownerName = "FromDreamingLockscreenHostedTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -491,7 +487,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( KeyguardState.GONE, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ) // WHEN the keyguard is occluded and the lockscreen hosted dream stops @@ -504,7 +500,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.OCCLUDED, from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, ownerName = "FromDreamingLockscreenHostedTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -526,7 +522,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest .startedTransition( to = KeyguardState.LOCKSCREEN, from = KeyguardState.DOZING, - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -538,7 +534,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest transitionRepository.sendTransitionSteps( KeyguardState.LOCKSCREEN, KeyguardState.DOZING, - testScheduler + testScheduler, ) // GIVEN a prior transition has started to LOCKSCREEN transitionRepository.sendTransitionStep( @@ -591,7 +587,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.GONE, from = KeyguardState.DOZING, ownerName = "FromDozingTransitionInteractor(biometric wake and unlock)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -615,7 +611,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest .startedTransition( from = KeyguardState.DOZING, to = KeyguardState.PRIMARY_BOUNCER, - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -645,7 +641,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.GONE, from = KeyguardState.DREAMING, ownerName = "FromDreamingTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -677,7 +673,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest .startedTransition( from = KeyguardState.DOZING, to = KeyguardState.GLANCEABLE_HUB, - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -703,7 +699,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DOZING, from = KeyguardState.GONE, ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -729,7 +725,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.AOD, from = KeyguardState.GONE, ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -751,7 +747,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.LOCKSCREEN, from = KeyguardState.GONE, ownerName = "FromGoneTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -775,7 +771,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.OCCLUDED, ownerName = "FromGoneTransitionInteractor" + "(Dismissible keyguard with occlusion)", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -800,7 +796,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DREAMING, from = KeyguardState.GONE, ownerName = "FromGoneTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -831,7 +827,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.GLANCEABLE_HUB, from = KeyguardState.GONE, ownerName = FromGoneTransitionInteractor::class.simpleName, - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -854,7 +850,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.GLANCEABLE_HUB, from = KeyguardState.GONE, ownerName = CommunalSceneTransitionInteractor::class.simpleName, - animatorAssertion = { it.isNull() } + animatorAssertion = { it.isNull() }, ) coroutineContext.cancelChildren() @@ -867,7 +863,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN a prior transition has run to ALTERNATE_BOUNCER runTransitionAndSetWakefulness( KeyguardState.LOCKSCREEN, - KeyguardState.ALTERNATE_BOUNCER + KeyguardState.ALTERNATE_BOUNCER, ) // WHEN the alternateBouncer stops showing and then the primary bouncer shows @@ -879,7 +875,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.PRIMARY_BOUNCER, from = KeyguardState.ALTERNATE_BOUNCER, ownerName = "FromAlternateBouncerTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -892,7 +888,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( KeyguardState.LOCKSCREEN, - KeyguardState.ALTERNATE_BOUNCER + KeyguardState.ALTERNATE_BOUNCER, ) // GIVEN the primary bouncer isn't showing, aod available and starting to sleep @@ -909,7 +905,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.AOD, from = KeyguardState.ALTERNATE_BOUNCER, ownerName = "FromAlternateBouncerTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -922,7 +918,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( KeyguardState.LOCKSCREEN, - KeyguardState.ALTERNATE_BOUNCER + KeyguardState.ALTERNATE_BOUNCER, ) // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep @@ -940,7 +936,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest to = KeyguardState.DOZING, from = KeyguardState.ALTERNATE_BOUNCER, ownerName = "FromAlternateBouncerTransitionInteractor", - animatorAssertion = { it.isNotNull() } + animatorAssertion = { it.isNotNull() }, ) coroutineContext.cancelChildren() @@ -953,7 +949,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( KeyguardState.LOCKSCREEN, - KeyguardState.ALTERNATE_BOUNCER + KeyguardState.ALTERNATE_BOUNCER, ) // GIVEN the primary bouncer isn't showing and device not sleeping @@ -982,7 +978,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( KeyguardState.LOCKSCREEN, - KeyguardState.ALTERNATE_BOUNCER + KeyguardState.ALTERNATE_BOUNCER, ) // GIVEN the keyguard is going away @@ -1013,7 +1009,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( KeyguardState.LOCKSCREEN, - KeyguardState.ALTERNATE_BOUNCER + KeyguardState.ALTERNATE_BOUNCER, ) // GIVEN the primary bouncer isn't showing and device not sleeping @@ -1131,7 +1127,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest bouncerRepository.setPrimaryShow(true) runTransitionAndSetWakefulness( KeyguardState.GLANCEABLE_HUB, - KeyguardState.PRIMARY_BOUNCER + KeyguardState.PRIMARY_BOUNCER, ) // WHEN the primaryBouncer stops showing @@ -1165,7 +1161,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest bouncerRepository.setPrimaryShow(true) runTransitionAndSetWakefulness( KeyguardState.GLANCEABLE_HUB, - KeyguardState.PRIMARY_BOUNCER + KeyguardState.PRIMARY_BOUNCER, ) // GIVEN that we are dreaming and occluded @@ -1189,36 +1185,6 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest coroutineContext.cancelChildren() } - @Test - @DisableSceneContainer - fun primaryBouncerToDreamingLockscreenHosted() = - testScope.runTest { - // GIVEN device dreaming with the lockscreen hosted dream and not dozing - keyguardRepository.setIsActiveDreamLockscreenHosted(true) - - // GIVEN a prior transition has run to PRIMARY_BOUNCER - bouncerRepository.setPrimaryShow(true) - runTransitionAndSetWakefulness( - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - KeyguardState.PRIMARY_BOUNCER - ) - - // WHEN the primary bouncer stops showing and lockscreen hosted dream still active - bouncerRepository.setPrimaryShow(false) - runCurrent() - - // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur - assertThat(transitionRepository) - .startedTransition( - ownerName = "FromPrimaryBouncerTransitionInteractor", - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - animatorAssertion = { it.isNotNull() }, - ) - - coroutineContext.cancelChildren() - } - @Test @BrokenWithSceneContainer(339465026) fun occludedToGone() = @@ -1585,10 +1551,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // WHEN the device starts DOZE_AOD keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - from = DozeStateModel.INITIALIZED, - to = DozeStateModel.DOZE_AOD, - ) + DozeTransitionModel(from = DozeStateModel.INITIALIZED, to = DozeStateModel.DOZE_AOD) ) runCurrent() @@ -2268,7 +2231,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest private suspend fun TestScope.runTransitionAndSetWakefulness( from: KeyguardState, - to: KeyguardState + to: KeyguardState, ) { transitionRepository.sendTransitionStep( TransitionStep( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 41c5b7332a4fd728244f3d6948983f4d482d1794..ff6ea3a14ff29d939d42b4bf37e0e0049b322d88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor @@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers @@ -71,10 +74,8 @@ class AodBurnInViewModelTest : SysuiTestCase() { private val burnInFlow = MutableStateFlow(BurnInModel()) @Before - @DisableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @DisableSceneContainer fun setUp() { MockitoAnnotations.initMocks(this) whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) @@ -112,18 +113,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(0) assertThat(movement?.translationY).isEqualTo(0) @@ -143,17 +139,12 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) assertThat(movement?.translationY).isEqualTo(30) @@ -166,7 +157,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 0f, - transitionState = TransitionState.STARTED + transitionState = TransitionState.STARTED, ), validateStep = false, ) @@ -180,11 +171,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() = testScope.runTest { - burnInParameters = - burnInParameters.copy( - minViewY = 100, - topInset = 80, - ) + burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80) val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) @@ -193,18 +180,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = -30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) // -20 instead of -30, due to inset of 80 assertThat(movement?.translationY).isEqualTo(-20) @@ -217,7 +199,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 0f, - transitionState = TransitionState.STARTED + transitionState = TransitionState.STARTED, ), validateStep = false, ) @@ -231,11 +213,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() = testScope.runTest { - burnInParameters = - burnInParameters.copy( - minViewY = 100, - topInset = 80, - ) + burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80) val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) @@ -244,18 +222,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = -30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) // -20 instead of -30, due to inset of 80 assertThat(movement?.translationY).isEqualTo(-20) @@ -268,7 +241,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 0f, - transitionState = TransitionState.STARTED + transitionState = TransitionState.STARTED, ), validateStep = false, ) @@ -291,18 +264,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) assertThat(movement?.translationY).isEqualTo(30) @@ -311,9 +279,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_weatherLargeClock() = + fun translationAndScale_sceneContainerOff_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, @@ -321,9 +289,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_weatherSmallClock() = + fun translationAndScale_sceneContainerOff_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, @@ -331,9 +299,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_nonWeatherLargeClock() = + fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, @@ -341,9 +309,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @DisableSceneContainer @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_composeFlagOff_nonWeatherSmallClock() = + fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, @@ -351,11 +319,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_weatherLargeClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + fun translationAndScale_sceneContainerOn_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, @@ -363,11 +329,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_weatherSmallClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + fun translationAndScale_sceneContainerOn_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, @@ -375,11 +339,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_nonWeatherLargeClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, @@ -387,11 +349,10 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags( - AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, - AConfigFlags.FLAG_COMPOSE_LOCKSCREEN - ) - fun translationAndScale_composeFlagOn_nonWeatherSmallClock() = + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableSceneContainer + @Ignore("b/367659687") + fun translationAndScale_sceneContainerOn_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, @@ -421,18 +382,13 @@ class AodBurnInViewModelTest : SysuiTestCase() { from = KeyguardState.LOCKSCREEN, to = KeyguardState.AOD, value = 1f, - transitionState = TransitionState.FINISHED + transitionState = TransitionState.FINISHED, ), validateStep = false, ) // Trigger a change to the burn-in model - burnInFlow.value = - BurnInModel( - translationX = 20, - translationY = 30, - scale = 0.5f, - ) + burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) assertThat(movement?.translationX).isEqualTo(20) assertThat(movement?.translationY).isEqualTo(30) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt index 17e1b53a3ba90caeda483a6ed3abee73ad183890..05a6b8785dafc49eb9a2121844e1f17c1fe3d857 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt @@ -16,14 +16,13 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardClockRepository @@ -229,8 +228,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_splitShade_composeLockscreenOn() = + @EnableSceneContainer + fun testSmallClockTop_splitShade_sceneContainerOn() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(true) @@ -244,8 +243,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_splitShade_composeLockscreenOff() = + @DisableSceneContainer + fun testSmallClockTop_splitShade_sceneContainerOff() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(true) @@ -257,8 +256,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_nonSplitShade_composeLockscreenOn() = + @EnableSceneContainer + fun testSmallClockTop_nonSplitShade_sceneContainerOn() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(false) @@ -270,8 +269,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testSmallClockTop_nonSplitShade_composeLockscreenOff() = + @DisableSceneContainer + fun testSmallClockTop_nonSplitShade_sceneContainerOff() = testScope.runTest { with(kosmos) { shadeRepository.setShadeLayoutWide(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt index d594f3a2f932045b5d9c89b6b412540c4b2f7da1..62d06254e5411a0b370512d48fc3b4892407f090 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt @@ -121,7 +121,7 @@ class MediaControlInteractorTest : SysuiTestCase() { MediaData( userId = USER_ID, instanceId = InstanceId.fakeInstanceId(2), - artist = ARTIST + artist = ARTIST, ) mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) @@ -145,10 +145,17 @@ class MediaControlInteractorTest : SysuiTestCase() { val clickIntent = mock { whenever(it.isActivity).thenReturn(true) } val expandable = mock() + val activityController = mock() + whenever(expandable.activityTransitionController(any())).thenReturn(activityController) underTest.startClickIntent(expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, 1) - verify(clickIntent).send(any()) + verify(activityStarter) + .startPendingIntentMaybeDismissingKeyguard( + eq(clickIntent), + eq(null), + eq(activityController), + ) } @Test @@ -174,7 +181,7 @@ class MediaControlInteractorTest : SysuiTestCase() { mediaData.appUid, surface = SURFACE, cardinality = 2, - rank = 1 + rank = 1, ) verify(activityStarter) .postStartActivityDismissingKeyguard(eq(clickIntent), eq(activityController)) @@ -232,7 +239,7 @@ class MediaControlInteractorTest : SysuiTestCase() { eq(true), eq(dialogTransitionController), eq(null), - eq(null) + eq(null), ) } @@ -248,7 +255,7 @@ class MediaControlInteractorTest : SysuiTestCase() { .createBroadcastDialogWithController( eq(APP_NAME), eq(PACKAGE_NAME), - eq(dialogTransitionController) + eq(dialogTransitionController), ) } @@ -279,7 +286,7 @@ class MediaControlInteractorTest : SysuiTestCase() { anyInt(), anyInt(), anyInt(), - anyBoolean() + anyBoolean(), ) verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) } @@ -307,7 +314,7 @@ class MediaControlInteractorTest : SysuiTestCase() { mediaData.appUid, surface = SURFACE, cardinality = 2, - rank = 1 + rank = 1, ) verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt index 69ccc58cadbfafcc30a0de337dc826b703e37ed7..7da2e9a8a283bc6319d3b137a96de5f641bf782b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.graphics.ImageLoader import com.android.systemui.graphics.imageLoader import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -45,12 +46,18 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever private const val KEY = "KEY" @@ -88,12 +95,13 @@ class MediaDataLoaderTest : SysuiTestCase() { mediaControllerFactory, mediaFlags, kosmos.imageLoader, - statusBarManager + statusBarManager, ) @Before fun setUp() { mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController) + whenever(mediaController.metadata).then { metadataBuilder.build() } } @Test @@ -115,7 +123,7 @@ class MediaDataLoaderTest : SysuiTestCase() { 0, 0, AudioAttributes.Builder().build(), - null + null, ) ) whenever(mediaController.metadata) @@ -126,7 +134,7 @@ class MediaDataLoaderTest : SysuiTestCase() { .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT, ) .build() ) @@ -161,12 +169,12 @@ class MediaDataLoaderTest : SysuiTestCase() { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED, ) extras.putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.3) extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT, ) val description = @@ -189,7 +197,7 @@ class MediaDataLoaderTest : SysuiTestCase() { session.sessionToken, APP_NAME, intent, - PACKAGE_NAME + PACKAGE_NAME, ) assertThat(result).isNotNull() assertThat(result?.appName).isEqualTo(APP_NAME) @@ -372,9 +380,37 @@ class MediaDataLoaderTest : SysuiTestCase() { assertThat(result).isNotNull() } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testLoadMediaDataInBg_cancelMultipleScheduledTasks() = + testScope.runTest { + val mockImageLoader = mock() + val mediaDataLoader = + MediaDataLoader( + context, + testDispatcher, + testScope, + mediaControllerFactory, + mediaFlags, + mockImageLoader, + statusBarManager, + ) + metadataBuilder.putString( + MediaMetadata.METADATA_KEY_ALBUM_ART_URI, + "content://album_art_uri", + ) + + testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) } + testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) } + testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) } + testScope.advanceUntilIdle() + + verify(mockImageLoader, times(1)).loadBitmap(any(), anyInt(), anyInt(), anyInt()) + } + private fun createMediaNotification( mediaSession: MediaSession? = session, - applicationInfo: ApplicationInfo? = null + applicationInfo: ApplicationInfo? = null, ): StatusBarNotification = SbnBuilder().run { setPkg(PACKAGE_NAME) @@ -385,7 +421,7 @@ class MediaDataLoaderTest : SysuiTestCase() { val bundle = Bundle() bundle.putParcelable( Notification.EXTRA_BUILDER_APPLICATION_INFO, - applicationInfo + applicationInfo, ) it.addExtras(bundle) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt index 9558e5d23a2513a92fb0885edbbe68147068eae8..01220285e10c352b4838534ebe9c45b373bdb190 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt @@ -24,26 +24,25 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope -import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.util.mediaInstanceId import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever 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.ArgumentMatchers +import org.mockito.ArgumentMatchers.any import org.mockito.Mockito +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -52,30 +51,31 @@ class MediaControlViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter + private val mediaDataFilter = kosmos.mediaDataFilter private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager private val packageManager = kosmos.packageManager private val drawable = context.getDrawable(R.drawable.ic_media_play) - private val instanceId: InstanceId = kosmos.mediaInstanceId - + private val instanceId = kosmos.mediaInstanceId private val underTest: MediaControlViewModel = kosmos.mediaControlViewModel + @Before + fun setUp() { + whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable) + whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) + .thenReturn(drawable) + whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt())) + .thenReturn(ApplicationInfo()) + whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) + whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) + whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) + context.setMockPackageManager(packageManager) + } + @Test fun addMediaControl_mediaControlViewModelIsLoaded() = testScope.runTest { - whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable) - whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) - .thenReturn(drawable) - whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt())) - .thenReturn(ApplicationInfo()) - whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) - whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) - whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) val playerModel by collectLastValue(underTest.player) - - context.setMockPackageManager(packageManager) - - val mediaData = initMediaData() + val mediaData = initMediaData(ARTIST, TITLE) mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) @@ -88,7 +88,51 @@ class MediaControlViewModelTest : SysuiTestCase() { assertThat(playerModel?.playTurbulenceNoise).isFalse() } - private fun initMediaData(): MediaData { + @Test + fun emitDuplicateMediaControls_mediaControlIsNotBound() = + testScope.runTest { + val playerModel by collectLastValue(underTest.player) + val mediaData = initMediaData(ARTIST, TITLE) + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE) + assertThat(playerModel?.artistName).isEqualTo(ARTIST) + assertThat(underTest.isNewPlayer(playerModel!!)).isTrue() + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE) + assertThat(playerModel?.artistName).isEqualTo(ARTIST) + assertThat(underTest.isNewPlayer(playerModel!!)).isFalse() + } + + @Test + fun emitDifferentMediaControls_mediaControlIsBound() = + testScope.runTest { + val playerModel by collectLastValue(underTest.player) + var mediaData = initMediaData(ARTIST, TITLE) + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE) + assertThat(playerModel?.artistName).isEqualTo(ARTIST) + assertThat(underTest.isNewPlayer(playerModel!!)).isTrue() + + mediaData = initMediaData(ARTIST_2, TITLE_2) + + mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) + + assertThat(playerModel).isNotNull() + assertThat(playerModel?.titleName).isEqualTo(TITLE_2) + assertThat(playerModel?.artistName).isEqualTo(ARTIST_2) + assertThat(underTest.isNewPlayer(playerModel!!)).isTrue() + } + + private fun initMediaData(artist: String, title: String): MediaData { val device = MediaDeviceData(true, null, DEVICE_NAME, null, showBroadcastButton = true) // Create media session @@ -111,12 +155,12 @@ class MediaControlViewModelTest : SysuiTestCase() { return MediaData( userId = USER_ID, - artist = ARTIST, - song = TITLE, + artist = artist, + song = title, packageName = PACKAGE, token = session.sessionToken, device = device, - instanceId = instanceId + instanceId = instanceId, ) } @@ -127,6 +171,8 @@ class MediaControlViewModelTest : SysuiTestCase() { private const val PACKAGE = "PKG" private const val ARTIST = "ARTIST" private const val TITLE = "TITLE" + private const val ARTIST_2 = "ARTIST_2" + private const val TITLE_2 = "TITLE_2" private const val DEVICE_NAME = "DEVICE_NAME" private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_ARTIST = "SESSION_ARTIST" diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 7203b61ecc9f954a8f11b320df689ead3a10e496..6f20e70f84a8126d8df007456ae908791cd0d997 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -78,8 +78,6 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { Dispatchers.resetMain() } - // For now the state changes at 0.5f expansion. This will change once we implement animation - // (and this test will fail) @Test fun qsExpansionValueChanges_correctExpansionState() = with(kosmos) { @@ -87,18 +85,27 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { val expansionState by collectLastValue(underTest.expansionState) underTest.qsExpansionValue = 0f - assertThat(expansionState) - .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) + assertThat(expansionState!!.progress).isEqualTo(0f) underTest.qsExpansionValue = 0.3f - assertThat(expansionState) - .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) - - underTest.qsExpansionValue = 0.7f - assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + assertThat(expansionState!!.progress).isEqualTo(0.3f) underTest.qsExpansionValue = 1f - assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + assertThat(expansionState!!.progress).isEqualTo(1f) + } + } + + @Test + fun qsExpansionValueChanges_clamped() = + with(kosmos) { + testScope.testWithinLifecycle { + val expansionState by collectLastValue(underTest.expansionState) + + underTest.qsExpansionValue = -1f + assertThat(expansionState!!.progress).isEqualTo(0f) + + underTest.qsExpansionValue = 2f + assertThat(expansionState!!.progress).isEqualTo(1f) } } @@ -110,7 +117,7 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, - true + true, ) fakeConfigurationRepository.onConfigurationChange() @@ -126,7 +133,7 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, - false + false, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt deleted file mode 100644 index 42db96e917eeb52a09fd06d663208d921213479a..0000000000000000000000000000000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt +++ /dev/null @@ -1,183 +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.systemui.qs.panels.domain.interactor - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.testScope -import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository -import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository -import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository -import com.android.systemui.qs.panels.shared.model.GridLayoutType -import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType -import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository -import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidJUnit4::class) -class GridConsistencyInteractorTest : SysuiTestCase() { - - data object NoopGridLayoutType : GridLayoutType - - private val kosmos = - testKosmos().apply { - defaultLargeTilesRepository = - object : DefaultLargeTilesRepository { - override val defaultLargeTiles = - setOf( - TileSpec.create("largeA"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("largeD"), - ) - } - gridConsistencyInteractorsMap = - mapOf( - Pair(NoopGridLayoutType, noopGridConsistencyInteractor), - Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor) - ) - } - - private val underTest = with(kosmos) { gridConsistencyInteractor } - - @Before - fun setUp() { - // Mostly testing InfiniteGridConsistencyInteractor because it reorders tiles - with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) } - underTest.start() - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun changeLayoutType_usesCorrectGridConsistencyInteractor() = - with(kosmos) { - testScope.runTest { - // Using the no-op grid consistency interactor - gridLayoutTypeRepository.setLayout(NoopGridLayoutType) - - // Setting an invalid layout with holes - // [ Large A ] [ sa ] - // [ Large B ] [ Large C ] - // [ sb ] [ Large D ] - val newTiles = - listOf( - TileSpec.create("largeA"), - TileSpec.create("smallA"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("smallB"), - TileSpec.create("largeD"), - ) - tileSpecRepository.setTiles(0, newTiles) - - runCurrent() - - val tiles = currentTilesInteractor.currentTiles.value - val tileSpecs = tiles.map { it.spec } - - // Saved tiles should be unchanged - assertThat(tileSpecs).isEqualTo(newTiles) - } - } - - @Test - fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() = - with(kosmos) { - testScope.runTest { - // Setting a valid layout with holes - // [ Large A ] [ sa ][ sb ] - // [ Large B ] [ Large C ] - // [ Large D ] - val newTiles = - listOf( - TileSpec.create("largeA"), - TileSpec.create("smallA"), - TileSpec.create("smallB"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("largeD"), - ) - tileSpecRepository.setTiles(0, newTiles) - - runCurrent() - - val tiles = currentTilesInteractor.currentTiles.value - val tileSpecs = tiles.map { it.spec } - - // Saved tiles should be unchanged - assertThat(tileSpecs).isEqualTo(newTiles) - } - } - - @Test - fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() = - with(kosmos) { - testScope.runTest { - // Setting an invalid layout with holes - // [ sa ] [ Large A ] - // [ Large B ] [ sb ] [ sc ] - // [ sd ] [ se ] [ Large C ] - val newTiles = - listOf( - TileSpec.create("smallA"), - TileSpec.create("largeA"), - TileSpec.create("largeB"), - TileSpec.create("smallB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - TileSpec.create("largeC"), - ) - tileSpecRepository.setTiles(0, newTiles) - - runCurrent() - - val tiles = currentTilesInteractor.currentTiles.value - val tileSpecs = tiles.map { it.spec } - - // Expected grid - // [ sa ] [ Large A ] [ sb ] - // [ Large B ] [ sc ] [ sd ] - // [ se ] [ Large C ] - val expectedTiles = - listOf( - TileSpec.create("smallA"), - TileSpec.create("largeA"), - TileSpec.create("smallB"), - TileSpec.create("largeB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - TileSpec.create("largeC"), - ) - - // Saved tiles should be unchanged - assertThat(tileSpecs).isEqualTo(expectedTiles) - } - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt deleted file mode 100644 index ea51398e6256dc14963881b4404395b04db28ab7..0000000000000000000000000000000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt +++ /dev/null @@ -1,187 +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.systemui.qs.panels.domain.interactor - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.testScope -import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository -import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidJUnit4::class) -class InfiniteGridConsistencyInteractorTest : SysuiTestCase() { - - private val kosmos = - testKosmos().apply { - defaultLargeTilesRepository = - object : DefaultLargeTilesRepository { - override val defaultLargeTiles: Set = - setOf( - TileSpec.create("largeA"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("largeD"), - ) - } - } - private val underTest = with(kosmos) { infiniteGridConsistencyInteractor } - - @Test - fun validTiles_returnsUnchangedList() = - with(kosmos) { - testScope.runTest { - // Original grid - // [ Large A ] [ sa ][ sb ] - // [ Large B ] [ Large C ] - // [ Large D ] - val tiles = - listOf( - TileSpec.create("largeA"), - TileSpec.create("smallA"), - TileSpec.create("smallB"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("largeD"), - ) - - val newTiles = underTest.reconcileTiles(tiles) - - assertThat(newTiles).isEqualTo(tiles) - } - } - - @Test - fun invalidTiles_moveIconTileForward() = - with(kosmos) { - testScope.runTest { - // Original grid - // [ Large A ] [ sa ] - // [ Large B ] [ Large C ] - // [ sb ] [ Large D ] - val tiles = - listOf( - TileSpec.create("largeA"), - TileSpec.create("smallA"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("smallB"), - TileSpec.create("largeD"), - ) - // Expected grid - // [ Large A ] [ sa ][ sb ] - // [ Large B ] [ Large C ] - // [ Large D ] - val expectedTiles = - listOf( - TileSpec.create("largeA"), - TileSpec.create("smallA"), - TileSpec.create("smallB"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("largeD"), - ) - - val newTiles = underTest.reconcileTiles(tiles) - - assertThat(newTiles).isEqualTo(expectedTiles) - } - } - - @Test - fun invalidTiles_moveIconTileBack() = - with(kosmos) { - testScope.runTest { - // Original grid - // [ sa ] [ Large A ] - // [ Large B ] [ Large C ] - // [ Large D ] - val tiles = - listOf( - TileSpec.create("smallA"), - TileSpec.create("largeA"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("largeD"), - ) - // Expected grid - // [ Large A ] [ Large B ] - // [ Large C ] [ Large D ] - // [ sa ] - val expectedTiles = - listOf( - TileSpec.create("largeA"), - TileSpec.create("largeB"), - TileSpec.create("largeC"), - TileSpec.create("largeD"), - TileSpec.create("smallA"), - ) - - val newTiles = underTest.reconcileTiles(tiles) - - assertThat(newTiles).isEqualTo(expectedTiles) - } - } - - @Test - fun invalidTiles_multipleCorrections() = - with(kosmos) { - testScope.runTest { - // Original grid - // [ sa ] [ Large A ] - // [ Large B ] [ sb ] [ sc ] - // [ sd ] [ se ] [ Large C ] - val tiles = - listOf( - TileSpec.create("smallA"), - TileSpec.create("largeA"), - TileSpec.create("largeB"), - TileSpec.create("smallB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - TileSpec.create("largeC"), - ) - // Expected grid - // [ sa ] [ Large A ] [ sb ] - // [ Large B ] [ sc ] [ sd ] - // [ se ] [ Large C ] - val expectedTiles = - listOf( - TileSpec.create("smallA"), - TileSpec.create("largeA"), - TileSpec.create("smallB"), - TileSpec.create("largeB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - TileSpec.create("largeC"), - ) - - val newTiles = underTest.reconcileTiles(tiles) - - assertThat(newTiles).isEqualTo(expectedTiles) - } - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt index 53384afb66bec4ea8fbf149a3277a91dfe6df087..9e90090549dda036b914e199484a86a0a518f38d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt @@ -22,6 +22,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository +import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel @@ -44,12 +45,7 @@ class InfiniteGridLayoutTest : SysuiTestCase() { } private val underTest = - with(kosmos) { - InfiniteGridLayout( - iconTilesViewModel, - fixedColumnsSizeViewModel, - ) - } + with(kosmos) { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) } @Test fun correctPagination_underOnePage_sameOrder() = @@ -65,7 +61,7 @@ class InfiniteGridLayoutTest : SysuiTestCase() { smallTile(), largeTile(), largeTile(), - smallTile() + smallTile(), ) val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..fa72d740b2f0038223ab2d5e240c021ec8db017c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose.selection + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class MutableSelectionStateTest : SysuiTestCase() { + private val underTest = MutableSelectionState() + + @Test + fun selectTile_isCorrectlySelected() { + assertThat(underTest.isSelected(TEST_SPEC)).isFalse() + + underTest.select(TEST_SPEC) + assertThat(underTest.isSelected(TEST_SPEC)).isTrue() + + underTest.unSelect() + assertThat(underTest.isSelected(TEST_SPEC)).isFalse() + + val newSpec = TileSpec.create("newSpec") + underTest.select(TEST_SPEC) + underTest.select(newSpec) + assertThat(underTest.isSelected(TEST_SPEC)).isFalse() + assertThat(underTest.isSelected(newSpec)).isTrue() + } + + @Test + fun startResize_createsResizingState() { + assertThat(underTest.resizingState).isNull() + + // Resizing starts but no tile is selected + underTest.onResizingDragStart(TileWidths(0, 0, 1)) {} + assertThat(underTest.resizingState).isNull() + + // Resizing starts with a selected tile + underTest.select(TEST_SPEC) + underTest.onResizingDragStart(TileWidths(0, 0, 1)) {} + + assertThat(underTest.resizingState).isNotNull() + } + + @Test + fun endResize_clearsResizingState() { + val spec = TileSpec.create("testSpec") + + // Resizing starts with a selected tile + underTest.select(spec) + underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {} + assertThat(underTest.resizingState).isNotNull() + + underTest.onResizingDragEnd() + assertThat(underTest.resizingState).isNull() + } + + @Test + fun unselect_clearsResizingState() { + // Resizing starts with a selected tile + underTest.select(TEST_SPEC) + underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {} + assertThat(underTest.resizingState).isNotNull() + + underTest.unSelect() + assertThat(underTest.resizingState).isNull() + } + + @Test + fun onResizingDrag_updatesResizingState() { + // Resizing starts with a selected tile + underTest.select(TEST_SPEC) + underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {} + assertThat(underTest.resizingState).isNotNull() + + underTest.onResizingDrag(5f) + assertThat(underTest.resizingState?.width).isEqualTo(5) + + underTest.onResizingDrag(2f) + assertThat(underTest.resizingState?.width).isEqualTo(7) + + underTest.onResizingDrag(-6f) + assertThat(underTest.resizingState?.width).isEqualTo(1) + } + + @Test + fun onResizingDrag_receivesResizeCallback() { + var resized = false + val onResize: () -> Unit = { resized = !resized } + + // Resizing starts with a selected tile + underTest.select(TEST_SPEC) + underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10), onResize) + assertThat(underTest.resizingState).isNotNull() + + // Drag under the threshold + underTest.onResizingDrag(1f) + assertThat(resized).isFalse() + + // Drag over the threshold + underTest.onResizingDrag(5f) + assertThat(resized).isTrue() + + // Drag back under the threshold + underTest.onResizingDrag(-5f) + assertThat(resized).isFalse() + } + + companion object { + private val TEST_SPEC = TileSpec.create("testSpec") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingStateTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6e66783da44ed26ce4fab6cb190955ee837fa660 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingStateTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose.selection + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ResizingStateTest : SysuiTestCase() { + + @Test + fun drag_updatesStateCorrectly() { + var resized = false + val underTest = + ResizingState(TileWidths(base = 0, min = 0, max = 10)) { resized = !resized } + + assertThat(underTest.width).isEqualTo(0) + + underTest.onDrag(2f) + assertThat(underTest.width).isEqualTo(2) + + underTest.onDrag(1f) + assertThat(underTest.width).isEqualTo(3) + assertThat(resized).isTrue() + + underTest.onDrag(-1f) + assertThat(underTest.width).isEqualTo(2) + assertThat(resized).isFalse() + } + + @Test + fun dragOutOfBounds_isClampedCorrectly() { + val underTest = ResizingState(TileWidths(base = 0, min = 0, max = 10)) {} + + assertThat(underTest.width).isEqualTo(0) + + underTest.onDrag(100f) + assertThat(underTest.width).isEqualTo(10) + + underTest.onDrag(-200f) + assertThat(underTest.width).isEqualTo(0) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b144f0678471d7350e50fefced1469fc2c0ab544 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Resources +import android.content.res.mainResources +import android.service.quicksettings.Tile +import android.widget.Button +import android.widget.Switch +import androidx.compose.ui.semantics.Role +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TileUiStateTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val resources: Resources + get() = kosmos.mainResources + + @Test + fun stateUnavailable_secondaryLabelNotmodified() { + val testString = "TEST STRING" + val state = + QSTile.State().apply { + state = Tile.STATE_UNAVAILABLE + secondaryLabel = testString + } + + val uiState = state.toUiState() + + assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun accessibilityRole_switch() { + val stateSwitch = + QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name } + val uiState = stateSwitch.toUiState() + assertThat(uiState.accessibilityRole).isEqualTo(Role.Switch) + } + + @Test + fun accessibilityRole_button() { + val stateButton = + QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name } + val uiState = stateButton.toUiState() + assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) + } + + @Test + fun accessibilityRole_switchWithSecondaryClick() { + val stateSwitchWithSecondaryClick = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + handlesSecondaryClick = true + } + val uiState = stateSwitchWithSecondaryClick.toUiState() + assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) + } + + @Test + fun switchInactive_secondaryLabelNotModified() { + val testString = "TEST STRING" + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_INACTIVE + secondaryLabel = testString + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(testString) + } + + @Test + fun switchActive_secondaryLabelNotModified() { + val testString = "TEST STRING" + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_ACTIVE + secondaryLabel = testString + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(testString) + } + + @Test + fun buttonInactive_secondaryLabelNotModifiedWhenEmpty() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Button::class.java.name + state = Tile.STATE_INACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEmpty() + } + + @Test + fun buttonActive_secondaryLabelNotModifiedWhenEmpty() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Button::class.java.name + state = Tile.STATE_ACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEmpty() + } + + @Test + fun buttonUnavailable_emptySecondaryLabel_default() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Button::class.java.name + state = Tile.STATE_UNAVAILABLE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) + } + + @Test + fun switchUnavailable_emptySecondaryLabel_defaultUnavailable() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_UNAVAILABLE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) + } + + @Test + fun switchInactive_emptySecondaryLabel_defaultOff() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_INACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_off)) + } + + @Test + fun switchActive_emptySecondaryLabel_defaultOn() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_ACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_on)) + } + + @Test + fun disabledByPolicy_inactive_appearsAsUnavailable() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + disabledByPolicy = true + } + + val uiState = stateDisabledByPolicy.toUiState() + + assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun disabledByPolicy_active_appearsAsUnavailable() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_ACTIVE + disabledByPolicy = true + } + + val uiState = stateDisabledByPolicy.toUiState() + + assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun disabledByPolicy_clickLabel() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + disabledByPolicy = true + } + + val uiState = stateDisabledByPolicy.toUiState() + assertThat(uiState.accessibilityUiState.clickLabel) + .isEqualTo( + resources.getString( + R.string.accessibility_tile_disabled_by_policy_action_description + ) + ) + } + + @Test + fun notDisabledByPolicy_clickLabel_null() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + disabledByPolicy = false + } + + val uiState = stateDisabledByPolicy.toUiState() + assertThat(uiState.accessibilityUiState.clickLabel).isNull() + } + + @Test + fun disabledByPolicy_unavailableInStateDescription() { + val state = + QSTile.State().apply { + disabledByPolicy = true + state = Tile.STATE_INACTIVE + } + + val uiState = state.toUiState() + assertThat(uiState.accessibilityUiState.stateDescription) + .contains(resources.getString(R.string.tile_unavailable)) + } + + private fun QSTile.State.toUiState() = toUiState(resources) +} + +private val TileUiState.accessibilityRole: Role + get() = accessibilityUiState.accessibilityRole diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 763a1a943bf810b3139d8ab0878059c7b49076fa..385089122fc4b5c7365fcd3d5d429078d49334e8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -27,6 +27,7 @@ import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.uiEventLoggerFake import com.android.internal.policy.IKeyguardDismissCallback @@ -88,9 +89,11 @@ import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.repository.Transition import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor @@ -161,6 +164,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(DualShade.FLAG_NAME) fun hydrateVisibility() = testScope.runTest { val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -220,6 +224,87 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(isVisible).isFalse() } + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun hydrateVisibility_dualShade() = + testScope.runTest { + val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene) + val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays) + val isVisible by collectLastValue(sceneInteractor.isVisible) + val transitionStateFlow = + prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = true, + initialSceneKey = Scenes.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone) + assertThat(currentDesiredOverlays).isEmpty() + assertThat(isVisible).isTrue() + + underTest.start() + assertThat(isVisible).isFalse() + + // Expand the notifications shade. + fakeSceneDataSource.pause() + sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Scenes.Gone, + toContent = Overlays.NotificationsShade, + currentScene = Scenes.Gone, + currentOverlays = flowOf(emptySet()), + progress = flowOf(0.5f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + assertThat(isVisible).isTrue() + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + transitionStateFlow.value = + ObservableTransitionState.Idle( + currentScene = Scenes.Gone, + currentOverlays = setOf(Overlays.NotificationsShade), + ) + assertThat(isVisible).isTrue() + + // Collapse the notifications shade. + fakeSceneDataSource.pause() + sceneInteractor.hideOverlay(Overlays.NotificationsShade, "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Overlays.NotificationsShade, + toContent = Scenes.Gone, + currentScene = Scenes.Gone, + currentOverlays = flowOf(setOf(Overlays.NotificationsShade)), + progress = flowOf(0.5f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + assertThat(isVisible).isTrue() + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + transitionStateFlow.value = + ObservableTransitionState.Idle( + currentScene = Scenes.Gone, + currentOverlays = emptySet(), + ) + assertThat(isVisible).isFalse() + + kosmos.headsUpNotificationRepository.setNotifications( + buildNotificationRows(isPinned = true) + ) + assertThat(isVisible).isTrue() + + kosmos.headsUpNotificationRepository.setNotifications( + buildNotificationRows(isPinned = false) + ) + assertThat(isVisible).isFalse() + } + @Test fun hydrateVisibility_basedOnDeviceProvisioning() = testScope.runTest { @@ -1621,6 +1706,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(DualShade.FLAG_NAME) fun hydrateInteractionState_whileLocked() = testScope.runTest { val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen) @@ -1707,6 +1793,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(DualShade.FLAG_NAME) fun hydrateInteractionState_whileUnlocked() = testScope.runTest { val transitionStateFlow = @@ -1794,6 +1881,186 @@ class SceneContainerStartableTest : SysuiTestCase() { ) } + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun hydrateInteractionState_dualShade_whileLocked() = + testScope.runTest { + val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays) + val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen) + underTest.start() + runCurrent() + verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true) + assertThat(currentDesiredOverlays).isEmpty() + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Bouncer, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces) + .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true) + }, + ) + + clearInvocations(centralSurfaces) + emulateOverlayTransition( + transitionStateFlow = transitionStateFlow, + toOverlay = Overlays.NotificationsShade, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces) + .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true) + }, + ) + + clearInvocations(centralSurfaces) + emulateOverlayTransition( + transitionStateFlow = transitionStateFlow, + toOverlay = Overlays.QuickSettingsShade, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun hydrateInteractionState_dualShade_whileUnlocked() = + testScope.runTest { + val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays) + val transitionStateFlow = + prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = true, + initialSceneKey = Scenes.Gone, + ) + underTest.start() + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + assertThat(currentDesiredOverlays).isEmpty() + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Bouncer, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Shade, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.Lockscreen, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + + clearInvocations(centralSurfaces) + emulateSceneTransition( + transitionStateFlow = transitionStateFlow, + toScene = Scenes.QuickSettings, + verifyBeforeTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyDuringTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + verifyAfterTransition = { + verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) + }, + ) + } + @Test fun respondToFalsingDetections() = testScope.runTest { @@ -2131,19 +2398,40 @@ class SceneContainerStartableTest : SysuiTestCase() { verifyAfterTransition: (() -> Unit)? = null, ) { val fromScene = sceneInteractor.currentScene.value + val fromOverlays = sceneInteractor.currentOverlays.value sceneInteractor.changeScene(toScene, "reason") runCurrent() verifyBeforeTransition?.invoke() transitionStateFlow.value = - ObservableTransitionState.Transition( - fromScene = fromScene, - toScene = toScene, - currentScene = flowOf(fromScene), - progress = flowOf(0.5f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(true), - ) + if (fromOverlays.isEmpty()) { + // Regular scene-to-scene transition. + ObservableTransitionState.Transition.ChangeScene( + fromScene = fromScene, + toScene = toScene, + currentScene = flowOf(fromScene), + currentOverlays = fromOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } else { + // An overlay is present; hide it. + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = fromOverlays.first(), + fromContent = fromOverlays.first(), + toContent = toScene, + currentScene = fromScene, + currentOverlays = sceneInteractor.currentOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } runCurrent() verifyDuringTransition?.invoke() @@ -2152,6 +2440,60 @@ class SceneContainerStartableTest : SysuiTestCase() { verifyAfterTransition?.invoke() } + private fun TestScope.emulateOverlayTransition( + transitionStateFlow: MutableStateFlow, + toOverlay: OverlayKey, + verifyBeforeTransition: (() -> Unit)? = null, + verifyDuringTransition: (() -> Unit)? = null, + verifyAfterTransition: (() -> Unit)? = null, + ) { + val fromScene = sceneInteractor.currentScene.value + val fromOverlays = sceneInteractor.currentOverlays.value + sceneInteractor.showOverlay(toOverlay, "reason") + runCurrent() + verifyBeforeTransition?.invoke() + + transitionStateFlow.value = + if (fromOverlays.isEmpty()) { + // Show a new overlay. + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = toOverlay, + fromContent = fromScene, + toContent = toOverlay, + currentScene = fromScene, + currentOverlays = sceneInteractor.currentOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } else { + // Overlay-to-overlay transition. + ObservableTransitionState.Transition.ReplaceOverlay( + fromOverlay = fromOverlays.first(), + toOverlay = toOverlay, + currentScene = fromScene, + currentOverlays = sceneInteractor.currentOverlays, + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } + runCurrent() + verifyDuringTransition?.invoke() + + transitionStateFlow.value = + ObservableTransitionState.Idle( + currentScene = fromScene, + currentOverlays = setOf(toOverlay), + ) + runCurrent() + verifyAfterTransition?.invoke() + } + private fun TestScope.prepareState( isDeviceUnlocked: Boolean = false, isBypassEnabled: Boolean = false, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt index 4d69f0ddc4b7044b559c60e11f526f531af33462..f86337ec63dcae1a94dda3c91d14fdb442855134 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.scene.shared.flag import android.platform.test.flag.junit.FlagsParameterization import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG +import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.flags.andSceneContainer @@ -66,7 +66,7 @@ internal class SceneContainerFlagParameterizationTest : SysuiTestCase() { @Test fun oneDependencyAndSceneContainer() { - val dependentFlag = FLAG_COMPOSE_LOCKSCREEN + val dependentFlag = FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer() Truth.assertThat(result).hasSize(3) Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 40fb7691c0c2e6f23c67bc5cb8cb20a8b30afa8d..614d51e7ac9983b036f5a5c11ac16ab7884e6079 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper.RunWithLooper; import android.view.View; @@ -53,6 +54,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.flags.SceneContainerFlagParameterizationKt; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.kosmos.KosmosJavaAdapter; @@ -466,6 +468,32 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0); } + @Test + @EnableSceneContainer + public void configChanged_boundsUpdate() { + when(mNotificationShadeWindowView.getWidth()).thenReturn(1600); + when(mNotificationShadeWindowView.getHeight()).thenReturn(800); + when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE); + Configuration newConfig = new Configuration(); + // swap width and height in new bounds to simulate auto-rotate + newConfig.windowConfiguration.setBounds(new Rect(0, 0, 800, 1600)); + mNotificationShadeWindowController.onConfigChanged(newConfig); + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), any()); + } + + @Test + @EnableSceneContainer + public void configChanged_boundsDontUpdate() { + when(mNotificationShadeWindowView.getWidth()).thenReturn(1600); + when(mNotificationShadeWindowView.getHeight()).thenReturn(800); + when(mNotificationShadeWindowView.getVisibility()).thenReturn(View.INVISIBLE); + Configuration newConfig = new Configuration(); + // same bounds as view's current bounds + newConfig.windowConfiguration.setBounds(new Rect(0, 0, 1600, 800)); + mNotificationShadeWindowController.onConfigChanged(newConfig); + verify(mWindowManager, never()).updateViewLayout(any(), any()); + } + private void setKeyguardShowing() { mNotificationShadeWindowController.setKeyguardShowing(true); mNotificationShadeWindowController.setKeyguardGoingAway(false); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt index 30536721357158146468c9aa1e706bb1238e6991..2c8cc1ae6ba428747cef38a84edc19630a71958a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt @@ -47,6 +47,7 @@ import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.SystemClock +import com.google.common.truth.Truth.assertThat import junit.framework.Assert import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -247,32 +248,35 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager @Test @EnableFlags(NotificationThrottleHun.FLAG_NAME) - fun testShowNotification_reorderNotAllowed_notPulsing_seenInShadeTrue() { - whenever(mVSProvider.isReorderingAllowed).thenReturn(false) + fun testShowNotification_removeWhenReorderingAllowedTrue() { + whenever(mVSProvider.isReorderingAllowed).thenReturn(true) val hmp = createHeadsUpManagerPhone() val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val row = mock() - whenever(row.showingPulsing()).thenReturn(false) - notifEntry.row = row + hmp.showNotification(notifEntry) + assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue(); + } + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + fun testShowNotification_reorderNotAllowed_seenInShadeTrue() { + whenever(mVSProvider.isReorderingAllowed).thenReturn(false) + val hmp = createHeadsUpManagerPhone() + + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) hmp.showNotification(notifEntry) - Assert.assertTrue(notifEntry.isSeenInShade) + assertThat(notifEntry.isSeenInShade).isTrue(); } @Test @EnableFlags(NotificationThrottleHun.FLAG_NAME) - fun testShowNotification_reorderAllowed_notPulsing_seenInShadeFalse() { + fun testShowNotification_reorderAllowed_seenInShadeFalse() { whenever(mVSProvider.isReorderingAllowed).thenReturn(true) val hmp = createHeadsUpManagerPhone() val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val row = mock() - whenever(row.showingPulsing()).thenReturn(false) - notifEntry.row = row - hmp.showNotification(notifEntry) - Assert.assertFalse(notifEntry.isSeenInShade) + assertThat(notifEntry.isSeenInShade).isFalse(); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt index fb32855ee2b7a7803cd13eb42e3b6fc4715bf887..0f6dc0723f42c345c68bfeb65e4afff71a9c654c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.policy.domain.interactor import android.app.AutomaticZenRule +import android.app.Flags import android.app.NotificationManager.Policy +import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.provider.Settings.Secure.ZEN_DURATION import android.provider.Settings.Secure.ZEN_DURATION_FOREVER @@ -32,6 +34,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.shared.settings.data.repository.secureSettingsRepository +import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -50,9 +53,30 @@ class ZenModeInteractorTest : SysuiTestCase() { private val testScope = kosmos.testScope private val zenModeRepository = kosmos.fakeZenModeRepository private val settingsRepository = kosmos.secureSettingsRepository + private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository private val underTest = kosmos.zenModeInteractor + @Test + fun isZenAvailable_off() = + testScope.runTest { + val isZenAvailable by collectLastValue(underTest.isZenAvailable) + deviceProvisioningRepository.setDeviceProvisioned(false) + runCurrent() + + assertThat(isZenAvailable).isFalse() + } + + @Test + fun isZenAvailable_on() = + testScope.runTest { + val isZenAvailable by collectLastValue(underTest.isZenAvailable) + deviceProvisioningRepository.setDeviceProvisioned(true) + runCurrent() + + assertThat(isZenAvailable).isTrue() + } + @Test fun isZenModeEnabled_off() = testScope.runTest { @@ -337,4 +361,22 @@ class ZenModeInteractorTest : SysuiTestCase() { runCurrent() assertThat(mainActiveMode).isNull() } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + fun dndMode_flows() = + testScope.runTest { + val dndMode by collectLastValue(underTest.dndMode) + + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE) + runCurrent() + + assertThat(dndMode!!.isActive).isFalse() + + zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id) + zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE) + runCurrent() + + assertThat(dndMode!!.isActive).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyCallbackTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyCallbackTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/temporarydisplay/TemporaryViewUiEventLoggerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/FakeMotionEvent.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/tracing/TraceUtilsTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/tracing/TraceUtilsTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt similarity index 91% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 21a45ecb5922637c06338d01b4ce64003150aac7..9dcbe1b591e56d4a640f0175cce92cca6655af4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -615,6 +615,71 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) } + @Test + fun angleDecreaseAfterCancelAnimation_emitsStartClosingEvent() { + setFoldState(folded = true) + sendHingeAngleEvent(0) + foldUpdates.clear() + + setFoldState(folded = false) + rotationListener.value.onRotationChanged(1) + sendHingeAngleEvent(180) + screenOnStatusProvider.notifyScreenTurningOn() + screenOnStatusProvider.notifyScreenTurnedOn() + + // Start folding + (180 downTo 60).forEach { + sendHingeAngleEvent(it) + } + // Stopped folding and simulate timeout in this posture + simulateTimeout() + + assertThat(foldUpdates) + .containsExactly( + FOLD_UPDATE_START_OPENING, // unfolded + FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation + FOLD_UPDATE_START_CLOSING, // start closing the device + FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state + ) + + } + + @Test + fun angleIncreaseDecreaseAfterHalfUnfold_emitsStartClosingEvent() { + setFoldState(folded = true) + sendHingeAngleEvent(0) + foldUpdates.clear() + + setFoldState(folded = false) + sendHingeAngleEvent(90) + screenOnStatusProvider.notifyScreenTurningOn() + screenOnStatusProvider.notifyScreenTurnedOn() + + // Stopped folding and simulate timeout in this posture + simulateTimeout() + + // Unfold further + (90 until 180).forEach { + sendHingeAngleEvent(it) + } + // Start folding + (180 downTo 90).forEach { + sendHingeAngleEvent(it) + } + + // Stopped folding and simulate timeout in this posture + simulateTimeout() + + assertThat(foldUpdates) + .containsExactly( + FOLD_UPDATE_START_OPENING, // unfolded + FOLD_UPDATE_FINISH_HALF_OPEN, // force-finished the animation because of rotation + FOLD_UPDATE_START_CLOSING, // start closing the device + FOLD_UPDATE_FINISH_HALF_OPEN, // finished closing and timed-out in this state + ) + + } + @Test fun onUnfold_onSmallScreen_emitsStartOpening() { // the new display state might arrive later, so it shouldn't be used to decide to send the diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldProvider.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldProvider.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldProvider.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/RingerModeLiveDataTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/RingerModeLiveDataTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/WallpaperControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/WallpaperControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/animation/AnimationUtilTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/animation/AnimationUtilTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/FlowProvider.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/FlowProvider.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/leak/LeakReporterTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/leak/LeakReporterTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ref/GcWeakReference.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ref/GcWeakReference.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/ref/GcWeakReference.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/ref/GcWeakReference.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PackageObserverTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/service/PackageObserverTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/FakeSettingsTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/FakeSettingsTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ui/AnimatedValueTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/ui/AnimatedValueTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/data/repository/VolumeDialogRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/VolumeDialogInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/BubblesTestActivity.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/BubblesTestActivity.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/SyncExecutor.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/SyncExecutor.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubblePositioner.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubblePositioner.java diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 4812ff03ef36b4456f220c65ae7ba841913faa2b..8dc4815b6f5795b148faf53faf6491731b06f11d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -69,11 +69,7 @@ interface ClockController { val events: ClockEvents /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ - fun initialize( - resources: Resources, - dozeFraction: Float, - foldFraction: Float, - ) + fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) @@ -109,11 +105,7 @@ data class ClockMessageBuffers( val largeClockMessageBuffer: MessageBuffer, ) -data class AodClockBurnInModel( - val scale: Float, - val translationX: Float, - val translationY: Float, -) +data class AodClockBurnInModel(val scale: Float, val translationX: Float, val translationY: Float) /** Specifies layout information for the */ interface ClockFaceLayout { @@ -180,8 +172,20 @@ interface ClockEvents { /** Call with zen/dnd information */ fun onZenDataChanged(data: ZenData) + + /** Update reactive axes for this clock */ + fun onReactiveAxesChanged(axes: List) } +/** Axis setting value for a clock */ +data class ClockReactiveSetting( + /** Axis key; matches ClockReactiveAxis.key */ + val key: String, + + /** Value to set this axis to */ + val value: Float, +) + /** Methods which trigger various clock animations */ interface ClockAnimations { /** Runs an enter animation (if any) */ @@ -264,9 +268,7 @@ enum class ClockTickRate(val value: Int) { } /** Some data about a clock design */ -data class ClockMetadata( - val clockId: ClockId, -) +data class ClockMetadata(val clockId: ClockId) data class ClockPickerConfig( val id: String, @@ -283,10 +285,46 @@ data class ClockPickerConfig( /** True if the clock will react to tone changes in the seed color */ val isReactiveToTone: Boolean = true, - /** True if the clock is capable of chagning style in reaction to touches */ + /** True if the clock is capable of changing style in reaction to touches */ val isReactiveToTouch: Boolean = false, + + /** Font axes that can be modified on this clock */ + val axes: List = listOf(), +) + +/** Represents an Axis that can be modified */ +data class ClockReactiveAxis( + /** Axis key, not user renderable */ + val key: String, + + /** Intended mode of user interaction */ + val type: AxisType, + + /** Maximum value the axis supports */ + val maxValue: Float, + + /** Minimum value the axis supports */ + val minValue: Float, + + /** Current value the axis is set to */ + val currentValue: Float, + + /** User-renderable name of the axis */ + val name: String, + + /** Description of the axis */ + val description: String, ) +/** Axis user interaction modes */ +enum class AxisType { + /** Boolean toggle. Swaps between minValue & maxValue */ + Toggle, + + /** Continuous slider between minValue & maxValue */ + Slider, +} + /** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */ data class ClockConfig( val id: String, @@ -300,7 +338,8 @@ data class ClockConfig( /** Transition to AOD should move smartspace like large clock instead of small clock */ val useAlternateSmartspaceAODTransition: Boolean = false, - @Deprecated("TODO(b/352049256): Remove") + /** Use ClockPickerConfig.isReactiveToTone instead */ + @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone") val isReactiveToTone: Boolean = true, /** True if the clock is large frame clock, which will use weather in compose. */ @@ -331,6 +370,7 @@ data class ClockFaceConfig( data class ClockSettings( val clockId: ClockId? = null, val seedColor: Int? = null, + val axes: List? = null, ) { // Exclude metadata from equality checks var metadata: JSONObject = JSONObject() @@ -345,6 +385,8 @@ data class ClockSettings( return "" } + // TODO(b/364673977): Serialize axes + return JSONObject() .put(KEY_CLOCK_ID, setting.clockId) .put(KEY_SEED_COLOR, setting.seedColor) @@ -357,11 +399,13 @@ data class ClockSettings( return null } + // TODO(b/364673977): Deserialize axes + val json = JSONObject(jsonStr) val result = ClockSettings( if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, - if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null + if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null, ) if (!json.isNull(KEY_METADATA)) { result.metadata = json.getJSONObject(KEY_METADATA) diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml index 84f7a5133593564ce2f678f5c11fef4c6526ba80..6c8db91d49bbc5377c451570b22763304e6b2042 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml @@ -28,8 +28,4 @@ 100dp 18sp - - - 704dp - 1208dp diff --git a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml deleted file mode 100644 index a15532f7aed264c8e69965c4bf259b81437abee9..0000000000000000000000000000000000000000 --- a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 32bcca1cb23dee6c182c275072e26c1700c1027e..1f4dea91db01a7371d673b2765635dd975fa54d8 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -63,10 +63,12 @@ + @@ -75,6 +77,8 @@ + "Voeg by nota" "Sluit skakel in" "%1$s (%2$d)" - - + "Skakels kan nie vanaf jou ander profiele bygevoeg word nie" "Skermopnemer" "Verwerk tans skermopname" "Deurlopende kennisgewing vir \'n skermopnamesessie" @@ -1180,6 +1179,10 @@ "%1$d%%" "Luidsprekers en skerms" "Voorgestelde toestelle" + + + + "Stop jou gedeelde sessie om media na ’n ander toestel toe te skuif" "Stop" "Hoe uitsaai werk" @@ -1290,6 +1293,8 @@ "Voeg by" "Bestuur gebruikers" "Sleep na verdeelde skerm word nie vir hierdie kennisgewing gesteun nie" + + "Wi‑fi onbeskikbaar" "Prioriteitmodus" "Wekker gestel" @@ -1346,6 +1351,8 @@ "Pasmaak sluitskerm" "Ontsluit om sluitskerm te pasmaak" "Wi-fi is nie beskikbaar nie" + + "Kamera is geblokkeer" "Kamera en mikrofoon is geblokkeer" "Mikrofoon is geblokkeer" @@ -1401,7 +1408,8 @@ "Leer raakpaneelgebare, kortpadsleutels en meer" "Teruggebaar" "Tuisgebaar" - "Handelingsleutel" + + "Klaar" "Gaan terug" "Swiep enige plek op die raakpaneel links of regs met drie vingers om terug te gaan.\n\nJy kan ook die kortpadsleutelhandeling + Esc hiervoor gebruik." @@ -1411,6 +1419,14 @@ "Swiep enige tyd van die onderkant van jou skerm af op met drie vingers om na jou tuisskerm toe te gaan." "Mooi so!" "Jy het die Gaan na Tuisskerm-gebaar voltooi." + + + + + + + + "Handelingsleutel" "Druk die handelingsleutel op jou sleutelbord om toegang tot jou apps te kry." "Geluk!" @@ -1434,22 +1450,19 @@ "Swiep op en hou met drie vingers. Tik om meer gebare te leer." "Gebruik jou sleutelbord om alle apps te bekyk" "Druk enige tyd die handelingsleutel. Tik om meer gebare te leer." - "Ekstra donker is nou deel van die helderheidbalk" - "Jy kan nou die skerm ekstra donker maak deur die helderheidvlak vanaf die bokant van jou skerm nog laer te maak.\n\nDit werk die beste wanneer jy in ’n donker omgewing is." - "Verwyder kortpad vir ekstra donker" - "Kortpad vir ekstra donker is verwyder. Gebruik die gewone helderheidbalk om jou helderheid te verlaag." - - - - - - - + - + - + - + + "Konnektiwiteit" + "Toeganklikheid" + "Nutsdienste" + "Privaatheid" + "Verskaf deur apps" + "Vertoon" + "Onbekend" diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 286acea48dd3c4e67573d013bc5baa19172f8ff3..77da382904c4408d8809d0f6483cb37a6067d30b 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "ድምፅ ማውጫዎች እና ማሳያዎች" "የተጠቆሙ መሣሪያዎች" + + + + "ሚዲያን ወደ ሌላ መሣሪያ ለማንቀሳቀስ የተጋራውን ክፍለ ጊዜዎን ያቁሙ" "አቁም" "ማሰራጨት እንዴት እንደሚሠራ" @@ -1289,6 +1293,8 @@ "አክል" "ተጠቃሚዎችን ያስተዳድሩ" "ይህ ማሳወቂያ ወደ የተከፈለ ማያ ገፅ መጎተትን አይደግፍም" + + "Wi‑Fi አይገኝም" "የቅድሚያ ሁነታ" "ማንቂያ ተቀናብሯል" @@ -1345,6 +1351,8 @@ "ማያ ገፅ ቁልፍን አብጅ" "የማያ ገጽ ቁልፍን ለማበጀት ይክፈቱ" "Wi-Fi አይገኝም" + + "ካሜራ ታግዷል" "ካሜራ እና ማይክሮፎን ታግደዋል" "ማይክሮፎን ታግዷል" @@ -1400,7 +1408,8 @@ "የመዳሰሻ ሰሌዳ ምልክቶችን፣ የቁልፍ ሰሌዳ አቋራጮችን እና ሌሎችን ይወቁ" "የተመለስ ምልክት" "የቤት ምልክት" - "የተግባር ቁልፍ" + + "ተከናውኗል" "ወደኋላ ተመለስ" "ወደኋላ ለመመለስ የመዳሰሻ ሰሌዳው ላይ የትኛውም ቦታ በሦስት ጣቶች ወደግራ ወይም ወደቀኝ ያንሸራትቱ።\n\nእንዲሁም የቁልፍ ሰሌዳ አቋራጭ + ESC ለዚህ መጠቀም ይችላሉ።" @@ -1410,6 +1419,14 @@ "በማንኛውም ጊዜ ወደ መነሻ ማያ ገፅዎ ለመሄድ ከማያ ገፅዎ ታች በሦስት ጣቶች ወደላይ ያሸብልሉ።" "አሪፍ!" "ወደ መነሻ ሂድ ምልክትን አጠናቅቀዋል።" + + + + + + + + "የተግባር ቁልፍ" "መተግበሪያዎችዎን ለመድረስ በቁልፍ ሰሌዳዎ ላይ የእርምጃ ቁልፉን ይጫኑ።" "እንኳን ደስ አለዎት!" @@ -1433,22 +1450,19 @@ "ሦስት ጣቶችን በመጠቀም ወደላይ ያንሸራትቱ እና ይያዙ። ምልክቶችን የበለጠ ለማወቅ መታ ያድርጉ።" "ሁሉንም መተግበሪያዎች ለማየት የቁልፍ ሰሌዳዎን ይጠቀሙ" "በማንኛውም ጊዜ የተግባር ቁልፍን ይጫኑ። ምልክቶችን የበለጠ ለማወቅ መታ ያድርጉ።" - "ተጨማሪ ደብዛዛ አሁን የብሩህነት አሞሌ ክፍል ነው" - "አሁን ከማያ ገፅዎ በላይ የብሩህነት ደረጃውን ይበልጥ በመቀነስ ማያ ገፁን ተጨማሪ ደብዛዛ ማድረግ ይችላሉ።\n\nይህ በጨለማ አካባቢ ውስጥ ሲሆኑ በተሻለ ይሠራል።" - "ተጨማሪ ደብዛዛ አቋራጭን አስወግድ" - "የተጨማሪ ደብዛዛ አቋራጭን ያስወግዱ። የእርስዎን ብሩሃማነት ለመቀነስ መደበኛ የብሩሃማነት አሞሌውን ይጠቀሙ።" - - - - - - - + - + - + - + + "ግንኙነት" + "ተደራሽነት" + "መገልገያዎች" + "ግላዊነት" + "በመተግበሪያዎች የቀረበ" + "ማሳያ" + "ያልታወቀ" diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 5b844f238028102fe9f1915b89e3548fe6655615..64b0f0b221f89049e238a5054789d73aa5a64648 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -105,8 +105,7 @@ "إضافة إلى الملاحظة" "تضمين الرابط" "‫%1$s(%2$d)" - - + "لا يمكن إضافة روابط من ملفات شخصية أخرى" "مسجّل الشاشة" "جارٍ معالجة تسجيل الشاشة" "إشعار مستمر لجلسة تسجيل شاشة" @@ -1180,6 +1179,10 @@ "%%%1$d" "مكبّرات الصوت والشاشات" "الأجهزة المقترَحة" + + + + "أوقِف الجلسة المشتركة لنقل الوسائط إلى جهاز آخر." "إيقاف" "كيفية عمل البث" @@ -1290,6 +1293,8 @@ "إضافة" "إدارة المستخدمين" "لا يتيح هذا الإشعار إمكانية السحب لتقسيم الشاشة." + + "‏شبكة Wi‑Fi غير متاحة" "وضع الأولوية" "تم ضبط المنبه." @@ -1346,6 +1351,8 @@ "تخصيص شاشة القفل" "الفتح لتخصيص شاشة القفل" "‏لا يتوفّر اتصال Wi-Fi." + + "استخدام الكاميرا محظور." "استخدام الكاميرا والميكروفون محظور." "استخدام الميكروفون محظور." @@ -1401,7 +1408,8 @@ "تعرَّف على إيماءات لوحة اللمس واختصارات لوحة المفاتيح والمزيد" "إيماءة الرجوع" "إيماءة الانتقال إلى الشاشة الرئيسية" - "مفتاح الإجراء" + + "تم" "رجوع" "‏للرجوع، مرِّر سريعًا لليمين أو لليسار باستخدام ثلاثة أصابع في أي مكان على لوحة اللمس.\n\nيمكنك أيضًا الرجوع باستخدام اختصار لوحة المفاتيح \"مفتاح الإجراء + ESC\"." @@ -1411,6 +1419,14 @@ "للانتقال إلى الشاشة الرئيسية في أي وقت، مرِّر سريعًا من أسفل الشاشة إلى أعلاها بثلاثة أصابع." "أحسنت" "لقد أكملت التدريب على إيماءة الانتقال إلى الشاشة الرئيسية." + + + + + + + + "مفتاح الإجراء" "للوصول إلى التطبيقات، اضغط على مفتاح الإجراء في لوحة المفاتيح." "تهانينا!" @@ -1434,22 +1450,19 @@ "مرِّر سريعًا للأعلى مع استمرار الضغط باستخدام 3 أصابع. انقر للتعرّف على المزيد من الإيماءات." "استخدِم لوحة المفاتيح لعرض جميع التطبيقات" "اضغط على مفتاح الإجراء في أي وقت. انقر للتعرّف على المزيد من الإيماءات." - "ميزة \"زيادة تعتيم الشاشة\" أصبحت الآن ضمن شريط مستوى السطوع" - "يمكنك الآن زيادة تعتيم الشاشة عن طريق خفض مستوى السطوع بشكل أكبر من أعلى الشاشة.\n\nيُعد هذا الخيار مناسبًا عندما تكون في مكان مظلم." - "إزالة اختصار \"زيادة تعتيم الشاشة\"" - "تمت إزالة اختصار \"زيادة تعتيم الشاشة\". لخفض مستوى سطوع شاشتك، استخدِم شريط مستوى السطوع العادي." - - - - - - - + - + - + - + + "إمكانية الاتصال" + "تسهيل الاستخدام" + "خدمات عامة" + "الخصوصية" + "مقدَّمة من التطبيقات" + "العرض" + "غير معروفة" diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 06f28f11f3a4b75a706dc5c95c43caa69e661ee5..6e3ba230fd6665f220676ac4d75b7ff955bb8adb 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "স্পীকাৰ আৰু ডিছপ্লে’" "পৰামৰ্শ হিচাপে পোৱা ডিভাইচ" + + + + "মিডিয়া অন্য ডিভাইচলৈ স্থানান্তৰ কৰিবলৈ আপোনাৰ শ্বেয়াৰ কৰা ছেশ্বনটো বন্ধ কৰক" "বন্ধ কৰক" "সম্প্ৰচাৰ কৰাটোৱে কেনেকৈ কাম কৰে" @@ -1289,6 +1293,7 @@ "যোগ দিয়ক" "পৰিচালনা কৰক" "এই জাননীটোৱে বিভাজিত স্ক্ৰীনলৈ টানি আনি এৰাৰ সুবিধাটো সমৰ্থন নকৰে" + "অৱস্থান সক্ৰিয় হৈ আছে" "ৱাই-ফাই উপলব্ধ নহয়" "অগ্ৰাধিকাৰ ম’ড" "এলাৰ্ম ছেট কৰা হ’ল" @@ -1345,6 +1350,7 @@ "লক স্ক্ৰীন কাষ্টমাইজ কৰক" "লক স্ক্ৰীন কাষ্টমাইজ কৰিবলৈ আনলক কৰক" "ৱাই-ফাই উপলব্ধ নহয়" + "অৱস্থান সক্ৰিয় হৈ আছে" "কেমেৰা অৱৰোধ কৰা আছে" "কেমেৰা আৰু মাইক্ৰ’ফ’ন অৱৰোধ কৰা আছে" "মাইক্ৰ’ফ’ন অৱৰোধ কৰা আছে" @@ -1400,7 +1406,7 @@ "টাচ্চপেডৰ নিৰ্দেশ, কীব’ৰ্ডৰ শ্বৰ্টকাট আৰু অধিকৰ বিষয়ে জানক" "উভতি যাওক নিৰ্দেশ" "গৃহ স্ক্ৰীনলৈ যোৱাৰ নিৰ্দেশ" - "কাৰ্য কী" + "শেহতীয়া এপ্‌সমূহ চাওক" "হ’ল" "উভতি যাওক" "উভতি যাবলৈ, টাচ্চপেডৰ যিকোনো স্থানত তিনিটা আঙুলি ব্যৱহাৰ কৰি বাওঁ বা সোঁফালে ছোৱাইপ কৰক।\n\nইয়াৰ বাবে আপুনি কীব’ৰ্ড শ্বৰ্টকাট কাৰ্য + ESC ব্যৱহাৰ কৰিবও পাৰে।" @@ -1410,6 +1416,10 @@ "যিকোনো সময়তে আপোনাৰ গৃহ স্ক্ৰীনলৈ যাবলৈ, আপোনাৰ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ তিনিটা আঙুলিৰে ছোৱাইপ কৰক।" "সুন্দৰ!" "আপুনি গৃহ স্ক্ৰীনলৈ উভতি যোৱাৰ নিৰ্দেশটো সম্পূৰ্ণ কৰিলে।" + "শেহতীয়া এপ্‌সমূহ চাওক" + "আপোনাৰ টাচ্চপেডত তিনিটা আঙুলি ব্যৱহাৰ কৰি ওপৰলৈ ছোৱাইপ কৰি কিছু সময় ধৰি ৰাখক।" + "বঢ়িয়া!" + "আপুনি শেহতীয়া এপ্ চোৱাৰ নিৰ্দেশনাটো সম্পূৰ্ণ কৰিছে।" "কাৰ্য কী" "আপোনাৰ এপ্‌সমূহ এক্সেছ কৰিবলৈ আপোনাৰ কীব’ৰ্ডৰ কাৰ্য কীটোত টিপক।" "অভিনন্দন!" @@ -1433,22 +1443,15 @@ "তিনিটা আঙুলি ব্যৱহাৰ কৰি ওপৰলৈ ছোৱাইপ কৰি ধৰি ৰাখক। অধিক নিৰ্দেশ শিকিবলৈ টিপক।" "আটাইবোৰ এপ্‌ চাবলৈ আপোনাৰ কীব’ৰ্ড ব্যৱহাৰ কৰক" "যিকোনো সময়তে কাৰ্য কীটোত টিপক। অধিক নিৰ্দেশ শিকিবলৈ টিপক।" - "এক্সট্ৰা ডিম এতিয়া উজ্জ্বলতা বাৰৰ অংশ" - "আপুনি এতিয়া আপোনাৰ স্ক্ৰীনৰ একেবাৰে ওপৰৰ পৰা উজ্জ্বলতাৰ স্তৰ আৰু অধিক হ্ৰাস কৰি স্ক্ৰীনখন এক্সট্ৰা ডিম কৰিব পাৰে।\n\nআপুনি অন্ধকাৰ পৰিৱেশত থাকিলে এই সুবিধাটোৱে আটাইতকৈ ভাল কাম কৰে।" - "এক্সট্ৰা ডিম শ্বৰ্টকাট আঁতৰাওক" - "এক্সট্ৰা ডিম শ্বৰ্টকাট আঁতৰোৱা হৈছে। আপোনাৰ উজ্জ্বলতা হ্ৰাস কৰিবলৈ, নিয়মীয়া উজ্জ্বলতা বাৰ ব্যৱহাৰ কৰক।" - - - - - - - - - - - - - - + "এক্সট্ৰা ডিম এতিয়া উজ্জ্বলতা শ্লাইডাৰৰ অংশ" + "আপুনি এতিয়া উজ্জ্বলতাৰ স্তৰ আৰু অধিক হ্ৰাস কৰি স্ক্ৰীনখন এক্সট্ৰা ডিম কৰিব পাৰে।\n\nযিহেতু এতিয়া এই সুবিধাটো উজ্জ্বলতাৰ শ্লাইডাৰৰ অংশ, এক্সট্ৰা ডিমৰ শ্বৰ্টকাট আঁতৰাই থকা হৈছে।" + "এক্সট্ৰা ডিমৰ শ্বৰ্টকাট আঁতৰাওক" + "এক্সট্ৰা ডিম শ্বৰ্টকাট আঁতৰোৱা হৈছে" + "সংযোগ" + "সাধ্য সুবিধা" + "সহায়ক সঁজুলি" + "গোপনীয়তা" + "এপে প্ৰদান কৰা" + "ডিছপ্লে’" + "অজ্ঞাত" diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 24ab6aecaff09279ffd7370c3d49f43bbc45c100..33f1e74e5764c9f27425a8625d17a3dbb50fe1af 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -105,8 +105,7 @@ "Qeydə əlavə edin" "Keçid daxil edin" "%1$s(%2$d)" - - + "Başqa profillərdən link əlavə etmək mümkün deyil" "Ekran yazıcısı" "Ekran çəkilişi emal edilir" "Ekranın video çəkimi ərzində silinməyən bildiriş" @@ -1180,6 +1179,10 @@ "%1$d%%" "Dinamiklər & Displeylər" "Təklif olunan Cihazlar" + + + + "Medianı başqa cihaza köçürmək üçün paylaşılan sessiyanı dayandırın" "Dayandırın" "Yayım necə işləyir" @@ -1290,6 +1293,7 @@ "Əlavə edin" "İstifadəçiləri idarə edin" "Bu bildiriş bölünmüş ekrana sürüşdürməyi dəstəkləmir" + "Məkan aktivdir" "Wi‑Fi əlçatan deyil" "Prioritet rejimi" "Siqnal ayarlanıb" @@ -1346,6 +1350,7 @@ "Kilid ekranını fərdiləşdirin" "Kilid ekranını fərdiləşdirmək üçün kiliddən çıxarın" "Wi-Fi əlçatan deyil" + "Məkan aktivdir" "Kamera bloklanıb" "Kamera və mikrofon bloklanıb" "Mikrofon bloklanıb" @@ -1401,7 +1406,7 @@ "Taçped jestləri, klaviatura qısayolları və s. haqqında öyrənin" "Geri jesti" "Əsas ekran jesti" - "Əməliyyat düyməsi" + "Son tətbiqlərə baxın" "Hazırdır" "Geri qayıdın" "Geri getmək üçün taçpeddə istənilən yerdə üç barmaqla sola və ya sağa çəkin.\n\nBunun üçün Action + ESC klaviatura qısayolundan da istifadə edə bilərsiniz." @@ -1411,6 +1416,10 @@ "İstənilən vaxt ana ekrana keçmək üçün ekranın aşağısından üç barmağınızla yuxarı çəkin." "Əla!" "Əsas ekrana keçid jestini tamamladınız." + "Son tətbiqlərə baxın" + "Taçpeddə üç barmaq ilə yuxarı çəkib saxlayın." + "Əla!" + "Son tətbiqlərə baxmaq jestini tamamladınız." "Fəaliyyət açarı" "Tətbiqlərə daxil olmaq üçün klaviaturada fəaliyyət açarını basın." "Təbriklər!" @@ -1434,22 +1443,15 @@ "Üç barmaq ilə yuxarı çəkib saxlayın. Daha çox jest öyrənmək üçün toxunun." "Bütün tətbiqlərə baxmaq üçün klaviatura istifadə edin" "İstənilən vaxt fəaliyyət açarını basın. Daha çox jest öyrənmək üçün toxunun." - "Əlavə qaraltma artıq parlaqlıq panelinin bir hissəsidir" - "İndi ekranın yuxarısında parlaqlıq səviyyəsini daha da aşağı salaraq ekranı əlavə qaralda bilərsiniz.\n\nTünd mühitdə olduqda nəticə yaxşı olur." - "Əlavə qaraltma qısayolunu silin" - "Əlavə qaraltma qısayolu silindi. Parlaqlığı aşağı salmaq üçün adi parlaqlıq panelindən istifadə edin." - - - - - - - - - - - - - - + "Əlavə qaraltma artıq parlaqlıq slayderinin bir hissəsidir" + "İndi ekranın yuxarısında parlaqlıq səviyyəsini daha da aşağı salaraq ekranı əlavə qaralda bilərsiniz.\n\nBu funksiya artıq parlaqlıq slayderinin bir hissəsi olduğundan əlavə qaraltma qısayolları silinir." + "Əlavə qaraltma qısayollarını silin" + "Əlavə qaraltma qısayolları silindi" + "Bağlantı" + "Xüsusi imkanlar" + "Kommunal xidmətlər" + "Məxfilik" + "Tətbiqlər tərəfindən təmin edilir" + "Displey" + "Naməlum" diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 5efa223dcd9a23b6d04ce02f6a9d650c6c4dd85a..4c25b4b64e6996b3219784632a16f44e86a6ae2a 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Zvučnici i ekrani" "Predloženi uređaji" + + + + "Zaustavite deljenu sesiju da biste premestili medijski sadržaj na drugi uređaj" "Zaustavi" "Kako funkcioniše emitovanje" @@ -1289,6 +1293,8 @@ "Dodaj" "Upravljaj korisnicima" "Ovo obaveštenje ne podržava prevlačenje na podeljeni ekran" + + "WiFi nije dostupan" "Prioritetni režim" "Alarm je podešen" @@ -1345,6 +1351,8 @@ "Prilagodi zaključani ekran" "Otključajte da biste prilagodili zaključani ekran" "WiFi nije dostupan" + + "Kamera je blokirana" "Kamera i mikrofon su blokirani" "Mikrofon je blokiran" @@ -1400,7 +1408,8 @@ "Naučite pokrete za tačped, tasterske prečice i drugo" "Pokret za vraćanje" "Pokret za početnu stranicu" - "Taster radnji" + + "Gotovo" "Nazad" "Da biste se vratili, prevucite ulevo sa tri prsta bilo gde na tačpedu.\n\nMožete da koristite i tastersku prečicu Alt + ESC za ovo." @@ -1410,6 +1419,14 @@ "Da biste otišli na početni ekran u bilo kom trenutku, prevucite nagore od dna ekrana pomoću tri prsta." "Svaka čast!" "Dovršili ste pokret za povratak na početnu stranicu." + + + + + + + + "Taster radnji" "Da biste pristupili aplikacijama, pritisnite taster radnji na tastaturi." "Čestitamo!" @@ -1433,22 +1450,19 @@ "Prevucite nagore i zadržite sa tri prsta. Dodirnite da biste videli više pokreta." "Koristite tastaturu da biste pregledali sve aplikacije" "Pritisnite taster radnji u bilo kom trenutku. Dodirnite da biste videli više pokreta." - "Dodatno zatamnjivanje je sada deo trake za osvetljenost" - "Sada možete dodatno da zatamnite ekran smanjivanjem nivoa osvetljenosti pri vrhu ekrana. \n\nOvo najbolje funkcioniše kada ste u tamnom okruženju." - "Ukloni prečicu za dodatno zatamnjivanje" - "Uklonjena je prečica za dodatno zatamnjivanje. Da biste smanjili osvetljenost, koristite uobičajenu traku za osvetljenost." - - - - - - - + - + - + - + + "Povezivanje" + "Pristupačnost" + "Uslužne aplikacije" + "Privatnost" + "Obezbeđuju aplikacije" + "Ekran" + "Nepoznato" diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 435d8339f943e8c48678a8e857f354a116a34242..4a79ceedff9aaab356207e3d28b367dca83df4cf 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Дынамікі і дысплэі" "Прылады, якія падтрымліваюцца" + + + + "Каб перамясціць медыяфайлы на іншую прыладу, спыніце абагулены сеанс" "Спыніць" "Як адбываецца трансляцыя" @@ -1289,6 +1293,8 @@ "Дадаць" "Кіраванне карыстальнікамі" "Гэта апавяшчэнне нельга перацягнуць на падзелены экран." + + "Сетка Wi‑Fi недаступная" "Прыярытэтны рэжым" "Будзільнік зададзены" @@ -1345,6 +1351,8 @@ "Наладзіць экран блакіроўкі" "Разблакіруйце, каб наладзіць экран блакіроўкі" "Сетка Wi-Fi недаступная" + + "Камера заблакіравана" "Камера і мікрафон заблакіраваны" "Мікрафон заблакіраваны" @@ -1400,7 +1408,8 @@ "Азнаёмцеся з жэстамі для сэнсарнай панэлі, спалучэннямі клавіш і г. д." "Жэст для вяртання на папярэдні экран" "Жэст для вяртання на галоўны экран" - "Клавіша дзеяння" + + "Гатова" "Назад" "Каб вярнуцца, правядзіце трыма пальцамі ўлева ці ўправа ў любым месцы сэнсарнай панэлі.\n\nТаксама можна выкарыстоўваць спалучэнне \"клавіша дзеяння + ESC\"." @@ -1410,6 +1419,14 @@ "Каб у любы момант перайсці на галоўны экран, правядзіце па экране трыма пальцамі знізу ўверх." "Выдатна!" "Вы навучыліся рабіць жэст для пераходу на галоўны экран." + + + + + + + + "Клавіша дзеяння" "Каб атрымаць доступ да праграм, націсніце клавішу дзеяння на клавіятуры." "Віншуем!" @@ -1433,22 +1450,19 @@ "Правядзіце трыма пальцамі ўверх і затрымайце пальцы. Націсніце, каб азнаёміцца з іншымі жэстамі." "Выкарыстайце клавіятуру для прагляду ўсіх праграм" "Можна націснуць на клавішу дзеяння ў любы момант. Націсніце, каб азнаёміцца з іншымі жэстамі." - "Цяпер кіраваць дадатковым памяншэннем яркасці можна на панэлі яркасці" - "Цяпер вы можаце дадаткова зацямніць экран, яшчэ больш панізіўшы ўзровень яркасці праз налады ўверсе экрана.\n\nГэта функцыя працуе лепш за ўсё ў цёмным асяроддзі." - "Выдаліць хуткую каманду для дадатковага памяншэння яркасці" - "Хуткая каманда для дадатковага памяншэння яркасці выдалена. Каб паменшыць яркасць, выкарыстоўвайце звычайную панэль яркасці." - - - - - - - + - + - + - + + "Магчымасць падключэння" + "Спецыяльныя магчымасці" + "Утыліты" + "Прыватнасць" + "Забяспечваюцца праграмамі" + "Экран" + "Невядома" diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index adbc13ab590f235f7ca16db40ef2ca4edb1d5322..148bc1dc56f2a053e985ab579df2699d7193511e 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -105,8 +105,7 @@ "Добавяне към бележката" "Включване на връзката" "%1$s (%2$d)" - - + "Не могат да се добавят връзки от други потребителски профили" "Запис на екрана" "Записът на екрана се обработва" "Текущо известие за сесия за записване на екрана" @@ -1180,6 +1179,10 @@ "%1$d%%" "Високоговорители и екрани" "Предложени устройства" + + + + "Спиране на споделената ви сесия с цел преместване на мултимедията на друго устройство" "Спиране" "Как работи предаването" @@ -1290,6 +1293,8 @@ "Добавяне" "Потребители" "Това известие не поддържа плъзгане за разделяне на екрана" + + "Wi‑Fi не е налице" "Приоритетен режим" "Будилникът е зададен" @@ -1346,6 +1351,8 @@ "Персонализ. на заключения екран" "Отключете, за да персонализирате заключения екран" "Wi-Fi не е налице" + + "Достъпът до камерата е блокиран" "Достъпът до камерата и микрофона е блокиран" "Достъпът до микрофона е блокиран" @@ -1401,7 +1408,8 @@ "Научете за жестовете със сензорния панел, клавишните комбинации и др." "Жест за връщане назад" "Жест за преминаване към началния екран" - "Клавиш за действия" + + "Готово" "Назад" "За да се върнете назад, прекарайте три пръста наляво или надясно по сензорния панел.\n\nЗа целта можете също да използвате комбинацията с клавиша за действия + ESC." @@ -1411,6 +1419,14 @@ "За да преминете към началния екран по всяко време, прекарайте три пръста нагоре от долната част на екрана." "Чудесно!" "Изпълнихте жеста за преминаване към началния екран." + + + + + + + + "Клавиш за действия" "За да осъществите достъп до приложенията, натиснете клавиша за действия на клавиатурата си." "Поздравления!" @@ -1434,22 +1450,19 @@ "Плъзнете нагоре с три пръста и задръжте. Докоснете, за да научите повече жестове." "Използвайте клавиатурата, за да прегледате всички приложения" "Натиснете клавиша за действия по всяко време. Докоснете, за да научите повече жестове." - "Функцията за допълнителнително затъмняване вече е част от лентата за яркостта" - "Вече можете да затъмнявате екрана допълнително, като намалявате яркостта още повече от лентата в горната му част.\n\nТова е най-полезно, когато сте на тъмно място." - "Премахване на прекия път за допълнително затъмняване" - "Прекият път за допълнително затъмняване е премахнат. За да намалите яркостта, използвайте обикновената лента за управлението ѝ." - - - - - - - + - + - + - + + "Свързаност" + "Достъпност" + "Помощни услуги" + "Поверителност" + "Предоставено от приложения" + "Екран" + "Неизвестно" diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 2aa7786774f97d5651e112c1068ac120c0c66bc2..e7fde1643351ad46b96e844d2dd330e336f9a481 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -105,8 +105,7 @@ "নোটে যোগ করুন" "লিঙ্ক যোগ করুন" "%1$s (%2$d)" - - + "অন্যান্য প্রোফাইল থেকে লিঙ্ক যোগ করা যাবে না" "স্ক্রিন রেকর্ডার" "স্ক্রিন রেকর্ডিং প্রসেস হচ্ছে" "স্ক্রিন রেকর্ডিং সেশন চলার বিজ্ঞপ্তি" @@ -1180,6 +1179,10 @@ "%1$d%%" "স্পিকার ও ডিসপ্লে" "সাজেস্ট করা ডিভাইস" + + + + "অন্য ডিভাইসে মিডিয়া সরাতে আপনার শেয়ার করা সেশন বন্ধ করুন" "বন্ধ করুন" "ব্রডকাস্ট কীভাবে কাজ করে" @@ -1290,6 +1293,7 @@ "যোগ করুন" "ব্যবহারকারীদের ম্যানেজ করুন" "\'স্প্লিটস্ক্রিন\' মোডে এই বিজ্ঞপ্তি টেনে আনা যাবে না" + "লোকেশন অ্যাক্টিভ আছে" "ওয়াই-ফাই উপলভ্য নেই" "প্রায়োরিটি মোড" "অ্যালার্ম সেট করা হয়েছে" @@ -1346,6 +1350,7 @@ "লক স্ক্রিন কাস্টমাইজ করুন" "লক স্ক্রিন কাস্টমাইজ করতে আনলক করুন" "ওয়াই-ফাই উপলভ্য নয়" + "লোকেশন অ্যাক্টিভ আছে" "ক্যামেরার অ্যাক্সেস ব্লক করা আছে" "ক্যামেরা এবং মাইক্রোফোনের অ্যাক্সেস ব্লক করা আছে" "মাইক্রোফোনের অ্যাক্সেস ব্লক করা আছে" @@ -1401,7 +1406,7 @@ "টাচপ্যাড জেসচার, কীবোর্ড শর্টকাট এবং আরও অনেক কিছু সম্পর্কে জানুন" "ফিরে যাওয়ার জেসচার" "হোমপেজে যাওয়ার জেসচার" - "অ্যাকশন কী" + "সম্প্রতি ব্যবহার করা হয়েছে এমন অ্যাপ দেখুন" "হয়ে গেছে" "ফিরে যান" "ফিরে যেতে, টাচপ্যাডে যেকোনও জায়গায় তিনটি আঙুল দিয়ে বাঁদিক বা ডানদিকে সোয়াইপ করুন।\n\nএছাড়া, এটির জন্য আপনি কীবোর্ড শর্টকাট অ্যাকশন + ESC বোতাম প্রেস করতে পারবেন।" @@ -1411,6 +1416,10 @@ "যেকোনও সময়ে আপনার হোম স্ক্রিনে যেতে, আপনার স্ক্রিনের একদম নিচের থেকে তিনটি আঙুল দিয়ে উপরের দিকে সোয়াইপ করুন।" "সাবাস!" "জেসচার ব্যবহার করে কীভাবে হোমে ফিরে যাওয়া যায় সেই সম্পর্কে আপনি জেনেছেন।" + "সম্প্রতি ব্যবহার করা হয়েছে এমন অ্যাপ দেখুন" + "আপনার টাচপ্যাডে তিনটি আঙুল ব্যবহার করে উপরের দিকে সোয়াইপ করে ধরে রাখুন।" + "অসাধারণ!" + "সম্প্রতি ব্যবহার করা হয়েছে এমন অ্যাপের জেসচার দেখা সম্পূর্ণ করেছেন।" "অ্যাকশন কী" "আপনার অ্যাপ অ্যাক্সেস করতে, কীবোর্ডে অ্যাকশন কী প্রেস করুন" "অভিনন্দন!" @@ -1434,22 +1443,15 @@ "তিনটি আঙুল ব্যবহার করে উপরের দিকে সোয়াইপ করে ধরে রাখুন। আরও জেসচার সম্পর্কে জানতে ট্যাপ করুন।" "সব অ্যাপ দেখতে আপনার কীবোর্ড ব্যবহার করুন" "যেকোনও সময় অ্যাকশন কী প্রেস করুন। আরও জেসচার সম্পর্কে জানতে ট্যাপ করুন।" - "\'অতিরিক্ত কম ব্রাইটনেস\' ফিচার এখন ব্রাইটনেস বারের একটি অংশ" - "আপনি এখন স্ক্রিনের উপর থেকে ব্রাইটনেস লেভেল কমিয়েও, স্ক্রিন অতিরিক্ত কম ব্রাইট করতে পারবেন।\n\nআপনি অন্ধকার পরিবেশে থাকলে এটি সবথেকে ভালো কাজ করে।" - "\'অতিরিক্ত কম ব্রাইটনেস\' ফিচারের শর্টকাট সরান" - "\'অতিরিক্ত কম ব্রাইটনেস\' ফিচারের শর্টকাট সরানো হয়েছে। আপনার ব্রাইটনেস কম করতে, সাধারণ ব্রাইটনেস বার ব্যবহার করুন।" - - - - - - - - - - - - - - + "\'অতিরিক্ত কম ব্রাইটনেস\' ফিচার এখন ব্রাইটনেস স্লাইডারের একটি অংশ" + "আপনি ব্রাইটনেস লেভেল কমিয়েও, স্ক্রিন অতিরিক্ত কম ব্রাইট করতে পারবেন।\n\nএই ফিচার এখন ব্রাইটনেস স্লাইডারের অংশ, তাই \'অতিরিক্ত কম ব্রাইটনেস\' ফিচারের শর্টকাট সরানো হচ্ছে।" + "\'অতিরিক্ত কম ব্রাইটনেস\' ফিচারের শর্টকাট সরান" + "\'অতিরিক্ত কম ব্রাইটনেস\' ফিচারের শর্টকাট সরানো হয়েছে" + "কানেক্টিভিটি" + "অ্যাক্সেসিবিলিটি" + "উপযোগিতা" + "গোপনীয়তা" + "অ্যাপের তরফ থেকে দেওয়া" + "ডিসপ্লে" + "অজানা" diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 4802af846eda29c3b5894d43bced02aad0a3d90e..b687ccd6126cbe1e6c524ff4a63bec22d4ddc3df 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -105,8 +105,7 @@ "Dodaj u bilješku" "Uključi link" "%1$s (%2$d)⁠" - - + "Nije moguće dodati linkove s drugih profila" "Snimač ekrana" "Obrađivanje snimka ekrana" "Obavještenje za sesiju snimanja ekrana je u toku" @@ -1180,6 +1179,10 @@ "%1$d%%" "Zvučnici i ekrani" "Predloženi uređaji" + + + + "Zaustavite dijeljenu sesiju da premjestite medij na drugi uređaj" "Zaustavi" "Kako funkcionira emitiranje" @@ -1290,6 +1293,8 @@ "Dodaj" "Upravljajte korisnicima" "Ovo obavještenje ne podržava prevlačenje na podijeljeni ekran" + + "WiFi je nedostupan" "Način rada Prioriteti" "Alarm je postavljen" @@ -1346,6 +1351,8 @@ "Prilagodi zaključani ekran" "Otključajte da prilagodite zaključani ekran" "WiFi mreža nije dostupna" + + "Kamera je blokirana" "Kamera i mikrofon su blokirani" "Mikrofon je blokiran" @@ -1401,7 +1408,8 @@ "Saznajte više o pokretima na dodirnoj podlozi, prečicama tastature i drugim opcijama" "Pokret za povratak" "Pokret za povratak na početni ekran" - "Tipka radnji" + + "Gotovo" "Nazad" "Da se vratite, prevucite ulijevo ili udesno s tri prsta bilo gdje na dodirnoj podlozi.\n\nZa ovo možete koristiti i radnju za prečicu i Esc na tastaturi." @@ -1411,6 +1419,14 @@ "Da odete na početni ekran bilo kada, prevucite s dna ekrana nagore s tri prsta." "Lijepo!" "Savladali ste pokret za odlazak na početni ekran." + + + + + + + + "Tipka radnji" "Da pristupite aplikacijama, pritisnite tipku radnji na tastaturi." "Čestitamo!" @@ -1434,22 +1450,19 @@ "Prevucite nagore i zadržite s tri prsta. Dodirnite da saznate za više pokreta." "Koristite tastaturu da pregledate sve aplikacije" "Pritisnite tipku radnji bilo kada. Dodirnite da saznate za više pokreta." - "Dodatno zatamnjenje je sada dio trake za osvijetljenost" - "Sada možete dodatno zatamniti ekran daljnjim smanjenjem nivoa osvijetljenosti s vrha ekrana.\n\nOvo najbolje funkcionira u tamnom okruženju." - "Ukloni prečicu za dodatno zatamnjenje" - "Prečica za dodatno zatamnjenje je uklonjena. Da smanjite osvijetljenost, koristite običnu traku za osvijetljenost." - - - - - - - + - + - + - + + "Povezivost" + "Pristupačnost" + "Uslužni programi" + "Privatnost" + "Pružaju aplikacije" + "Prikaz" + "Nepoznato" diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 10fedd8ce7a434b59308a6468edbcf46d2727a88..5b764a86a002b6d4861780d8af3ed07d18ac2150 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -105,8 +105,7 @@ "Afegeix a una nota" "Inclou l\'enllaç" "%1$s (%2$d)" - - + "No es poden afegir enllaços des d\'altres perfils" "Gravació de pantalla" "Processant gravació de pantalla" "Notificació en curs d\'una sessió de gravació de la pantalla" @@ -1180,6 +1179,10 @@ "%1$d%%" "Altaveus i pantalles" "Dispositius suggerits" + + + + "Atura la sessió compartida per moure contingut multimèdia a un altre dispositiu" "Atura" "Com funciona l\'emissió" @@ -1290,6 +1293,8 @@ "Afegeix" "Gestiona usuaris" "Aquesta notificació no es pot arrossegar a la pantalla dividida" + + "Wi‑Fi no disponible" "Mode Prioritat" "Alarma definida" @@ -1346,6 +1351,8 @@ "Personalitza pantalla de bloqueig" "Desbloqueja per personalitzar la pantalla de bloqueig" "No hi ha cap Wi‑Fi disponible" + + "La càmera està bloquejada" "La càmera i el micròfon estan bloquejats" "El micròfon està bloquejat" @@ -1401,7 +1408,8 @@ "Aprèn els gestos del ratolí tàctil, les tecles de drecera i més" "Gest Enrere" "Gest Inici" - "Tecla d\'acció" + + "Fet" "Torna" "Per tornar enrere, llisca cap a l\'esquerra o cap a la dreta amb tres dits en qualsevol lloc del ratolí tàctil.\n\nTambé pots utilitzar les tecles d\'accions de drecera+Esc." @@ -1411,6 +1419,14 @@ "Per anar a la pantalla d\'inici en qualsevol moment, fes lliscar tres dits cap amunt des de la part inferior de la pantalla." "Molt bé!" "Has completat el gest per anar a la pantalla d\'inici." + + + + + + + + "Tecla d\'acció" "Per accedir a les aplicacions, prem la tecla d\'acció al teclat." "Enhorabona!" @@ -1434,22 +1450,19 @@ "Llisca cap amunt amb tres dits i mantén premut. Toca per aprendre més gestos." "Utilitza el teclat per veure totes les aplicacions" "Prem la tecla d\'acció en qualsevol moment. Toca per aprendre més gestos." - "Atenuació extra ara forma part de la barra de brillantor" - "Ara pots atenuar encara més la pantalla abaixant-ne el nivell de brillantor des de la part superior.\n\nFunciona millor si et trobes en un lloc fosc." - "Suprimeix la drecera d\'atenuació extra" - "S\'ha suprimit la drecera d\'atenuació extra. Per abaixar la brillantor, utilitza la barra de brillantor normal." - - - - - - - + - + - + - + + "Connectivitat" + "Accessibilitat" + "Utilitats" + "Privadesa" + "Proporcionat per aplicacions" + "Pantalla" + "Desconegut" diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index f995b294a477cc4d198cc02fb731b5175fe8f420..f609e2a0c79c7eaa3bafa86b65c8bfcc1610e20b 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -318,11 +318,11 @@ "Vstup" "Naslouchátka" "Zapínání…" - "Automatické otáčení" + "Autom. otáčení" "Automatické otáčení obrazovky" "Poloha" - "Spořič obrazovky" - "Přístup k fotoaparátu" + "Spořič" + "Fotoaparát" "Přístup k mikrofonu" "Dostupné" "Blokováno" @@ -442,7 +442,7 @@ "Vypnuto" "Nastavit" "Spravovat v nastavení" - "{count,plural, =0{Žádné aktivní režimy}=1{Režim {mode} je aktivní}few{# režimy jsou aktivní}many{# režimu je aktivních}other{# režimů je aktivních}}" + "{count,plural, =0{Žádné aktivní}=1{Režim {mode} je aktivní}few{# režimy jsou aktivní}many{# režimu je aktivních}other{# režimů je aktivních}}" "Nebudou vás rušit zvuky ani vibrace s výjimkou budíků, upozornění, událostí a volajících, které zadáte. Nadále uslyšíte veškerý obsah, který si sami pustíte (např. hudba, videa nebo hry)." "Nebudou vás rušit zvuky ani vibrace s výjimkou budíků. Nadále uslyšíte veškerý obsah, který si sami pustíte (např. hudba, videa nebo hry)." "Přizpůsobit" @@ -1179,6 +1179,10 @@ "%1$d %%" "Reproduktory a displeje" "Navrhovaná zařízení" + + + + "Ukončí sdílenou relaci a bude možné přesunout médium do jiného zařízení" "Zastavit" "Jak vysílání funguje" @@ -1234,7 +1238,7 @@ "Dostupné" "Problém s načtením měřiče baterie" "Klepnutím zobrazíte další informace" - "Budík nenastaven" + "Žádný" "zadejte zámek obrazovky" "Dotkněte se snímače otisků prstů. Vypínač je kratší tlačítko na boku telefonu." "Snímač otisků prstů" @@ -1289,6 +1293,8 @@ "Přidat" "Spravovat uživatele" "Toto oznámení nepodporuje přetažení na rozdělenou obrazovku" + + "Síť Wi‑Fi není k dispozici" "Prioritní režim" "Je nastaven budík" @@ -1345,6 +1351,8 @@ "Přizpůsobit obrazovku uzamčení" "Obrazovku uzamčení upravíte, když zařízení odemknete" "Síť Wi-Fi není dostupná" + + "Kamera je blokována" "Kamera a mikrofon jsou blokovány" "Mikrofon je blokován" @@ -1400,7 +1408,8 @@ "Naučte se gesta touchpadu, klávesové zkratky a další" "Gesto zpět" "Gesto domů" - "Akční klávesa" + + "Hotovo" "Zpět" "Pokud se chcete vrátit zpět, stačí kdekoli na touchpadu přejet třemi prsty doleva nebo doprava.\n\nMůžete také použít klávesovou zkratku Akce + ESC." @@ -1410,6 +1419,14 @@ "Na plochu přejdete kdykoli přejetím třemi prsty ze spodní části obrazovky nahoru." "Skvělé!" "Dokončili jste gesto pro přechod na plochu." + + + + + + + + "Akční klávesa" "Přístup k aplikacím získáte stisknutím akční klávesy na klávesnici." "Gratulujeme!" @@ -1433,22 +1450,19 @@ "Přejeďte třemi prsty nahoru a podržte je. Další gesta zjistíte klepnutím." "Zobrazení všech aplikací pomocí klávesnice" "Kdykoli stiskněte akční klávesu. Další gesta zjistíte klepnutím." - "Na sloupci jasu lze nově nastavit velmi tmavou obrazovku" - "Obrazovku můžete v horní části nastavit jako velmi tmavou tím, že ještě víc snížíte jas.\n\nNejlépe to funguje ve tmavém prostředí." - "Odstranit zkratku pro velmi tmavou obrazovku" - "Zkratka pro velmi tmavou obrazovku byla odstraněna. Jas můžete snížit pomocí standardního sloupce jasu." - - - - - - - + - + - + - + + "Připojení" + "Přístupnost" + "Nástroje" + "Ochrana soukromí" + "Poskytováno aplikacemi" + "Displej" + "Neznámé" diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 3b74a6db4bddc0a9cad52943c28195948096d468..5d429af446e5c6407e74835fa915b7904ff04b3f 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -105,8 +105,7 @@ "Føj til note" "Inkluder link" "%1$s (%2$d)" - - + "Der kan ikke tilføjes links fra andre profiler" "Skærmoptagelse" "Behandler skærmoptagelse" "Konstant notifikation om skærmoptagelse" @@ -1180,6 +1179,10 @@ "%1$d %%" "Højttalere og skærme" "Foreslåede enheder" + + + + "Stop din delte session for at flytte medier til en anden enhed" "Stop" "Sådan fungerer udsendelser" @@ -1290,6 +1293,8 @@ "Tilføj" "Administrer brugere" "Denne notifikation kan ikke trækkes til en opdelt skærm" + + "Ingen tilgængelig Wi-Fi-forbindelse" "Tilstanden Prioritet" "Alarmen er indstillet" @@ -1346,6 +1351,8 @@ "Tilpas låseskærm" "Lås op for at tilpasse låseskærmen" "Wi-Fi er ikke tilgængeligt" + + "Kameraet er blokeret" "Der er blokeret for kameraet og mikrofonen" "Mikrofonen er blokeret" @@ -1401,7 +1408,8 @@ "Se bevægelser på touchpladen, tastaturgenveje m.m." "Bevægelse for at gå tilbage" "Bevægelse for at gå til startskærm" - "Handlingstast" + + "Udfør" "Gå tilbage" "Du kan gå tilbage ved at stryge mod venstre eller højre med tre fingre et vilkårligt sted på touchpladen.\n\nDu kan også bruge tastaturgenvejen Alt + Esc." @@ -1411,6 +1419,14 @@ "Du kan til enhver tid stryge opad med tre fingre fra bunden af skærmen, hvis du vil gå til startskærmen." "Sådan!" "Du har fuldført bevægelsen for Gå til startskærmen." + + + + + + + + "Handlingstast" "Du kan tilgå alle dine apps ved at trykke på handlingstasten på dit tastatur." "Tillykke!" @@ -1434,22 +1450,19 @@ "Stryg opad, og hold tre fingre nede. Tryk for at lære flere bevægelser." "Brug dit tastatur til at se alle apps" "Tryk på handlingstasten når som helst. Tryk for at lære flere bevægelser." - "Ekstra dæmpet belysning er nu en del af lysstyrkebjælken" - "Du kan nu dæmpe skærmens belysning ekstra meget ved at reducere lysstyrken endnu mere fra toppen af skærmen.\n\nDette fungerer bedst i mørke omgivelser." - "Fjern genvejen til ekstra dæmpet belysning" - "Genvejen til ekstra dæmpet belysning er fjernet. Brug den almindelige lysstyrkebjælke til at reducere lysstyrken." - - - - - - - + - + - + - + + "Forbindelse" + "Hjælpefunktioner" + "Værktøjer" + "Privatliv" + "Fra apps" + "Skærm" + "Ukendt" diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index fa7ad567e0b9ae44b6210c1d5db07184a38598b6..7d5fed9187319232179f2ec617f4cf25f18ed7a0 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -105,8 +105,7 @@ "Zu Notiz hinzufügen" "Link einschließen" "%1$s (%2$d)" - - + "Es dürfen keine Links aus anderen Profilen hinzugefügt werden" "Bildschirmaufzeichnung" "Bildschirmaufzeichnung…" "Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung" @@ -577,8 +576,8 @@ "Jetzt starten" "Keine Benachrichtigungen" "Keine neuen Benachrichtigungen" - "Verringern von Lautstärke und Vibration bei Benachrichtigungen ist an" - "Wenn du in kurzer Zeit zu viele Benachrichtigungen erhältst, wird automatisch für bis zu 2 Minuten die Lautstärke des Geräts verringert und Benachrichtigungen werden reduziert." + "„Benachrichtigungen reduzieren” ist aktiviert" + "Wenn du zu viele Benachrichtigungen auf einmal erhältst, wird die Lautstärke automatisch bis zu 2 min lang verringert und Benachrichtigungen werden minimiert." "Deaktivieren" "Für ältere Benachrichtigungen entsperren" "Dieses Gerät wird von deinen Eltern verwaltet" @@ -1180,6 +1179,10 @@ "%1$d %%" "Lautsprecher & Displays" "Vorgeschlagene Geräte" + + + + "Beende die Freigabesitzung zur Übertragung von Medien auf das andere Gerät" "Beenden" "Funktionsweise von Nachrichten an alle" @@ -1290,6 +1293,8 @@ "Hinzufügen" "Nutzer verwalten" "Diese Benachrichtigung lässt sich nicht auf einen Splitscreen ziehen" + + "WLAN nicht verfügbar" "Prioritätsmodus" "Wecker gestellt" @@ -1346,6 +1351,8 @@ "Sperrbildschirm personalisieren" "Zum Anpassen des Sperrbildschirms entsperren" "Kein WLAN verfügbar" + + "Kamera blockiert" "Kamera und Mikrofon blockiert" "Mikrofon blockiert" @@ -1401,7 +1408,8 @@ "Informationen zu Touchpad-Gesten, Tastenkombinationen und mehr" "Touch-Geste „Zurück“" "Touch-Geste „Startbildschirm“" - "Aktionstaste" + + "Fertig" "Zurück" "Wenn du zurückgehen möchtest, wische an einer beliebigen Stelle des Touchpads mit drei Fingern nach links oder rechts.\n\nDu kannst stattdessen auch die Tastenkombination „Aktion“ + „ESC“ verwenden." @@ -1411,6 +1419,14 @@ "Du kannst jederzeit zum Startbildschirm gehen, indem du mit drei Fingern vom unteren Displayrand nach oben wischst." "Sehr gut!" "Du hast den Schritt für die „Startbildschirm“-Geste abgeschlossen." + + + + + + + + "Aktionstaste" "Wenn du auf deine Apps zugreifen möchtest, drücke auf der Tastatur die Aktionstaste." "Glückwunsch!" @@ -1434,22 +1450,19 @@ "Wische mit 3 Fingern nach oben und halte das Touchpad gedrückt. Tippe für mehr Infos zu Touch-Gesten." "Über die Tastatur alle Apps aufrufen" "Du kannst jederzeit die Aktionstaste drücken. Tippe für mehr Infos zu Touch-Gesten." - "„Extradunkel“ jetzt auf Helligkeitsleiste verfügbar" - "Du kannst das Display jetzt extradunkel machen, indem du die Helligkeit vom oberen Displayrand aus noch weiter senkst.\n\nDas funktioniert in dunklen Umgebungen am besten." - "Verknüpfung für „Extradunkel“ entfernen" - "Verknüpfung für „Extradunkel“ wurde entfernt. Wenn du die Helligkeit reduzieren möchtest, verwende einfach die normale Helligkeitsleiste." - - - - - - - + - + - + - + + "Konnektivität" + "Bedienungshilfen" + "Dienstprogramme" + "Datenschutz" + "Von Apps bereitgestellt" + "Display" + "Unbekannt" diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 26f1c5666c9cbf84ad15e1b67824db8eb23d0889..ab65136250fe65f6627aa30b08884fdc3c0e2c0f 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -105,8 +105,7 @@ "Προσθήκη σε σημείωση" "Συμπερίληψη συνδέσμου" "%1$s (%2$d)" - - + "Δεν είναι δυνατή η προσθήκη συνδέσμων από άλλα προφίλ" "Εγγραφή οθόνης" "Επεξεργασία εγγραφής οθόνης" "Ειδοποίηση σε εξέλιξη για μια περίοδο λειτουργίας εγγραφής οθόνης" @@ -577,7 +576,7 @@ "Έναρξη τώρα" "Δεν υπάρχουν ειδοποιήσεις" "Δεν υπάρχουν νέες ειδοποιήσεις" - "Η περίοδος cooldown ειδοποιήσεων είναι ενεργή" + "Η ρύθμιση cooldown ειδοποιήσεων είναι ενεργή" "Αυτόματη μείωση έντασης ήχου συσκευής και ειδοποιήσεων για έως 2 λεπτά όταν λαμβάνετε πολλές ειδοποιήσεις ταυτόχρονα." "Απενεργοποίηση" "Ξεκλειδώστε για εμφάνιση παλαιότ. ειδοπ." @@ -1180,6 +1179,10 @@ "%1$d%%" "Ηχεία και οθόνες" "Προτεινόμενες συσκευές" + + + + "Διακόψτε την κοινόχρηστη περίοδο λειτουργίας για να μεταφέρετε μέσα σε άλλη συσκευή" "Διακοπή" "Πώς λειτουργεί η μετάδοση" @@ -1290,6 +1293,8 @@ "Προσθήκη" "Διαχ. χρηστών" "Αυτή η ειδοποίηση δεν υποστηρίζει τη μεταφορά με σύρσιμο για τον διαχωρισμό οθόνης" + + "Το Wi‑Fi δεν είναι διαθέσιμο" "Λειτουργία προτεραιότητας" "Το ξυπνητήρι ρυθμίστηκε" @@ -1346,6 +1351,8 @@ "Προσαρμογή οθόνης κλειδώματος" "Ξεκλειδώστε για προσαρμογή της οθόνης κλειδώματος" "Δεν υπάρχει διαθέσιμο δίκτυο Wi-Fi" + + "Η κάμερα έχει αποκλειστεί" "Η κάμερα και το μικρόφωνο έχουν αποκλειστεί" "Το μικρόφωνο έχει αποκλειστεί" @@ -1401,7 +1408,8 @@ "Μάθετε κινήσεις επιφάνειας αφής, συντομεύσεις πληκτρολογίου και άλλα" "Κίνηση επιστροφής" "Κίνηση μετάβασης στην αρχική οθόνη" - "Πλήκτρο ενέργειας" + + "Τέλος" "Επιστροφή" "Για να επιστρέψετε, σύρετε προς τα αριστερά ή προς τα δεξιά χρησιμοποιώντας τρία δάχτυλα σε οποιοδήποτε σημείο της επιφάνειας αφής.\n\nΜπορείτε επίσης να χρησιμοποιήσετε τη συντόμευση πληκτρολογίου Action + ESC." @@ -1411,6 +1419,14 @@ "Για μετάβαση στην αρχική οθόνη ανά πάσα στιγμή, σύρετε προς τα επάνω με τρία δάχτυλα από το κάτω μέρος της οθόνης." "Ωραία!" "Ολοκληρώσατε την κίνηση μετάβασης στην αρχική οθόνη." + + + + + + + + "Πλήκτρο ενέργειας" "Για να αποκτήσετε πρόσβαση στις εφαρμογές σας, πατήστε το πλήκτρο ενέργειας στο πληκτρολόγιό σας." "Συγχαρητήρια!" @@ -1434,22 +1450,19 @@ "Σύρετε προς τα πάνω με τρία δάχτυλα και μην τα σηκώσετε. Πατήστε για να δείτε περισσότερες κινήσεις." "Χρήση του πληκτρολογίου για προβολή όλων των εφαρμογών" "Πιέστε το πλήκτρο ενέργειας ανά πάσα στιγμή. Πατήστε για να μάθετε περισσότερες κινήσεις." - "Η επιπλέον μείωση φωτεινότητας είναι τώρα μέρος της γραμμής φωτεινότητας" - "Τώρα μπορείτε να μειώσετε επιπλέον τη φωτεινότητα της οθόνης, χαμηλώνοντας το επίπεδο φωτεινότητας ακόμα περισσότερο από το επάνω μέρος της οθόνης.\n\nΑυτό λειτουργεί καλύτερα όταν βρίσκεστε σε σκοτεινό περιβάλλον." - "Κατάργηση συντόμευσης επιπλέον μείωσης φωτεινότητας" - "Η συντόμευση της επιπλέον μείωσης φωτεινότητας καταργήθηκε. Για να χαμηλώσετε τη φωτεινότητα, χρησιμοποιήστε την κανονική γραμμή φωτεινότητας." - - - - - - - + - + - + - + + "Συνδεσιμότητα" + "Προσβασιμότητα" + "Βοηθητικά προγράμματα" + "Απόρρητο" + "Παρέχεται από εφαρμογές" + "Προβολή" + "Άγνωστο" diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 24922ac29c7e587b111540099878b36e766d9504..3f1a40c1a1254010c01438fceda2325e8dfbbc30 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -105,8 +105,7 @@ "Add to note" "Include link" "%1$s (%2$d)⁠" - - + "Links can\'t be added from other profiles" "Screen recorder" "Processing screen recording" "Ongoing notification for a screen record session" @@ -1180,6 +1179,10 @@ "%1$d%%" "Speakers and displays" "Suggested devices" + + + + "Stop your shared session to move media to another device" "Stop" "How broadcasting works" @@ -1290,6 +1293,8 @@ "Add" "Manage users" "This notification does not support dragging to split screen" + + "Wi‑Fi unavailable" "Priority mode" "Alarm set" @@ -1346,6 +1351,8 @@ "Customise lock screen" "Unlock to customise lock screen" "Wi-Fi not available" + + "Camera is blocked" "Camera and microphone blocked" "Microphone is blocked" @@ -1401,7 +1408,8 @@ "Learn touchpad gestures, keyboards shortcuts and more" "Back gesture" "Home gesture" - "Action key" + + "Done" "Go back" "To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut Action + Esc for this." @@ -1411,6 +1419,14 @@ "To go to your home screen at any time, swipe up with three fingers from the bottom of your screen." "Nice!" "You completed the go home gesture." + + + + + + + + "Action key" "To access your apps, press the action key on your keyboard." "Congratulations!" @@ -1434,22 +1450,19 @@ "Swipe up and hold using three fingers. Tap to learn more gestures." "Use your keyboard to view all apps" "Press the action key at any time. Tap to learn more gestures." - "Extra dim is now part of the brightness bar" - "You can now make the screen extra dim by lowering the brightness level even further from the top of your screen.\n\nThis works best when you\'re in a dark environment." - "Remove extra dim shortcut" - "Extra dim shortcut removed. To lower your brightness, use the regular brightness bar." - - - - - - - + - + - + - + + "Connectivity" + "Accessibility" + "Utilities" + "Privacy" + "Provided by apps" + "Display" + "Unknown" diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 88f175393e0c2067978257f3a96bf063ba95569d..4ed330e399ff6a5eab68ed85c63d704b171f6a85 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Speakers & Displays" "Suggested Devices" + + + + "Stop your shared session to move media to another device" "Stop" "How broadcasting works" @@ -1289,6 +1293,7 @@ "Add" "Manage users" "This notification does not support dragging to split screen" + "Location active" "Wi‑Fi unavailable" "Priority mode" "Alarm set" @@ -1345,6 +1350,7 @@ "Customize lock screen" "Unlock to customize lock screen" "Wi-Fi not available" + "Location active" "Camera blocked" "Camera and microphone blocked" "Microphone blocked" @@ -1400,7 +1406,7 @@ "Learn touchpad gestures, keyboards shortcuts, and more" "Back gesture" "Home gesture" - "Action key" + "View recent apps" "Done" "Go back" "To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut Action + ESC for this." @@ -1410,6 +1416,10 @@ "To go to your home screen at any time, swipe up with three fingers from the bottom of your screen." "Nice!" "You completed the go home gesture." + "View recent apps" + "Swipe up and hold using three fingers on your touchpad." + "Great job!" + "You completed the view recent apps gesture." "Action key" "To access your apps, press the action key on your keyboard." "Congratulations!" @@ -1433,10 +1443,10 @@ "Swipe up and hold using three fingers. Tap to learn more gestures." "Use your keyboard to view all apps" "Press the action key at any time. Tap to learn more gestures." - "Extra dim is now part of the brightness bar" - "You can now make the screen extra dim by lowering the brightness level even further from the top of your screen.\n\nThis works best when you\'re in a dark environment." - "Remove extra dim shortcut" - "Extra dim shortcut removed. To lower your brightness, use the regular brightness bar." + "Extra dim is now part of the brightness slider" + "You can now make the screen extra dim by lowering the brightness level even further.\n\nSince this feature is now part of the brightness slider, extra dim shortcuts are being removed." + "Remove extra dim shortcuts" + "Extra dim shortcuts removed" "Connectivity" "Accessibility" "Utilities" diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 24922ac29c7e587b111540099878b36e766d9504..3f1a40c1a1254010c01438fceda2325e8dfbbc30 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -105,8 +105,7 @@ "Add to note" "Include link" "%1$s (%2$d)⁠" - - + "Links can\'t be added from other profiles" "Screen recorder" "Processing screen recording" "Ongoing notification for a screen record session" @@ -1180,6 +1179,10 @@ "%1$d%%" "Speakers and displays" "Suggested devices" + + + + "Stop your shared session to move media to another device" "Stop" "How broadcasting works" @@ -1290,6 +1293,8 @@ "Add" "Manage users" "This notification does not support dragging to split screen" + + "Wi‑Fi unavailable" "Priority mode" "Alarm set" @@ -1346,6 +1351,8 @@ "Customise lock screen" "Unlock to customise lock screen" "Wi-Fi not available" + + "Camera is blocked" "Camera and microphone blocked" "Microphone is blocked" @@ -1401,7 +1408,8 @@ "Learn touchpad gestures, keyboards shortcuts and more" "Back gesture" "Home gesture" - "Action key" + + "Done" "Go back" "To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut Action + Esc for this." @@ -1411,6 +1419,14 @@ "To go to your home screen at any time, swipe up with three fingers from the bottom of your screen." "Nice!" "You completed the go home gesture." + + + + + + + + "Action key" "To access your apps, press the action key on your keyboard." "Congratulations!" @@ -1434,22 +1450,19 @@ "Swipe up and hold using three fingers. Tap to learn more gestures." "Use your keyboard to view all apps" "Press the action key at any time. Tap to learn more gestures." - "Extra dim is now part of the brightness bar" - "You can now make the screen extra dim by lowering the brightness level even further from the top of your screen.\n\nThis works best when you\'re in a dark environment." - "Remove extra dim shortcut" - "Extra dim shortcut removed. To lower your brightness, use the regular brightness bar." - - - - - - - + - + - + - + + "Connectivity" + "Accessibility" + "Utilities" + "Privacy" + "Provided by apps" + "Display" + "Unknown" diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 24922ac29c7e587b111540099878b36e766d9504..3f1a40c1a1254010c01438fceda2325e8dfbbc30 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -105,8 +105,7 @@ "Add to note" "Include link" "%1$s (%2$d)⁠" - - + "Links can\'t be added from other profiles" "Screen recorder" "Processing screen recording" "Ongoing notification for a screen record session" @@ -1180,6 +1179,10 @@ "%1$d%%" "Speakers and displays" "Suggested devices" + + + + "Stop your shared session to move media to another device" "Stop" "How broadcasting works" @@ -1290,6 +1293,8 @@ "Add" "Manage users" "This notification does not support dragging to split screen" + + "Wi‑Fi unavailable" "Priority mode" "Alarm set" @@ -1346,6 +1351,8 @@ "Customise lock screen" "Unlock to customise lock screen" "Wi-Fi not available" + + "Camera is blocked" "Camera and microphone blocked" "Microphone is blocked" @@ -1401,7 +1408,8 @@ "Learn touchpad gestures, keyboards shortcuts and more" "Back gesture" "Home gesture" - "Action key" + + "Done" "Go back" "To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut Action + Esc for this." @@ -1411,6 +1419,14 @@ "To go to your home screen at any time, swipe up with three fingers from the bottom of your screen." "Nice!" "You completed the go home gesture." + + + + + + + + "Action key" "To access your apps, press the action key on your keyboard." "Congratulations!" @@ -1434,22 +1450,19 @@ "Swipe up and hold using three fingers. Tap to learn more gestures." "Use your keyboard to view all apps" "Press the action key at any time. Tap to learn more gestures." - "Extra dim is now part of the brightness bar" - "You can now make the screen extra dim by lowering the brightness level even further from the top of your screen.\n\nThis works best when you\'re in a dark environment." - "Remove extra dim shortcut" - "Extra dim shortcut removed. To lower your brightness, use the regular brightness bar." - - - - - - - + - + - + - + + "Connectivity" + "Accessibility" + "Utilities" + "Privacy" + "Provided by apps" + "Display" + "Unknown" diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index e8b32060b03ff109976affe7b673fae8a4caabed..fea4ebf4b774a7d49e6086c367c3c222c235ce59 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -1179,6 +1179,10 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎%1$d‎‏‎‎‏‏‏‎%%‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎Speakers & Displays‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‎‎Suggested Devices‎‏‎‎‏‎" + + + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‏‎Stop your shared session to move media to another device‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎Stop‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎How broadcasting works‎‏‎‎‏‎" @@ -1289,6 +1293,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎Add‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎Manage users‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎‏‎This notification does not support dragging to split screen‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎Location active‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎Wi‑Fi unavailable‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎Priority mode‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎Alarm set‎‏‎‎‏‎" @@ -1345,6 +1350,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎Customize lock screen‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‏‎Unlock to customize lock screen‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‎Wi-Fi not available‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‎‎‎Location active‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎Camera blocked‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‎‎Camera and microphone blocked‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎Microphone blocked‎‏‎‎‏‎" @@ -1400,7 +1406,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‏‎Learn touchpad gestures, keyboards shortcuts, and more‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‎Back gesture‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎Home gesture‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎Action key‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎View recent apps‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‏‎‏‏‏‏‎Done‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‏‏‎Go back‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎To go back, swipe left or right using three fingers anywhere on the touchpad.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎You can also use the keyboard shortcut Action + ESC for this.‎‏‎‎‏‎" @@ -1410,6 +1416,10 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎To go to your home screen at any time, swipe up with three fingers from the bottom of your screen.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎Nice!‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎You completed the go home gesture.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎View recent apps‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎Swipe up and hold using three fingers on your touchpad.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎Great job!‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‏‎You completed the view recent apps gesture.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎Action key‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎To access your apps, press the action key on your keyboard.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎‎‏‎‏‎Congratulations!‎‏‎‎‏‎" @@ -1433,10 +1443,10 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎Swipe up and hold using three fingers. Tap to learn more gestures.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‎Use your keyboard to view all apps‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‏‎Press the action key at any time. Tap to learn more gestures.‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‎Extra dim is now part of the brightness bar‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎You can now make the screen extra dim by lowering the brightness level even further from the top of your screen.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This works best when you\'re in a dark environment.‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎Remove extra dim shortcut‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎Extra dim shortcut removed. To lower your brightness, use the regular brightness bar.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎Extra dim is now part of the brightness slider‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎You can now make the screen extra dim by lowering the brightness level even further.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Since this feature is now part of the brightness slider, extra dim shortcuts are being removed.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎Remove extra dim shortcuts‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎Extra dim shortcuts removed‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎‎‎Connectivity‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‎Accessibility‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎Utilities‎‏‎‎‏‎" diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 42c35109be8a43eed374785d4b13def532275ea2..faf6ff86acce43ad54952ad3d91c8f03433de42e 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -105,8 +105,7 @@ "Agregar a la nota" "Incluir vínculo" "%1$s (%2$d)" - - + "No se pueden agregar vínculos desde otros perfiles" "Grabadora de pantalla" "Procesando grabación pantalla" "Notificación constante para una sesión de grabación de pantalla" @@ -1180,6 +1179,10 @@ "%1$d%%" "Bocinas y pantallas" "Dispositivos sugeridos" + + + + "Detiene la sesión de uso compartido para transferir contenido multimedia a otro dispositivo" "Detener" "Cómo funciona la transmisión" @@ -1290,6 +1293,8 @@ "Agregar" "Administrar usuarios" "Esta notificación no admite arrastrar entre pantallas divididas" + + "La red Wi-Fi no está disponible" "Modo prioridad" "Se estableció la alarma" @@ -1346,6 +1351,8 @@ "Personalizar pantalla de bloqueo" "Desbloquea para personalizar la pantalla de bloqueo" "Wi-Fi no disponible" + + "La cámara está bloqueada" "La cámara y el micrófono están bloqueados" "El micrófono está bloqueado" @@ -1393,21 +1400,16 @@ "Ícono de contraer" "Ícono de expandir" "o" - - - - - - - - - - - - + "Navega con el teclado" + "Aprende combinaciones de teclas" + "Navega con el panel táctil" + "Aprende los gestos del panel táctil" + "Navega con el teclado y el panel táctil" + "Aprende sobre los gestos del panel táctil, las combinaciones de teclas y mucho más" "Gesto atrás" "Gesto para ir a la pantalla principal" - "Tecla de acción" + + "Listo" "Atrás" "Para volver, desliza tres dedos hacia la derecha o la izquierda en cualquier lugar del panel táctil.\n\nPara completar esta acción, también puedes usar la combinación de teclas Action + ESC." @@ -1417,6 +1419,14 @@ "Para ir a la pantalla principal en cualquier momento, desliza hacia arriba desde la parte inferior de la pantalla con tres dedos." "¡Muy bien!" "Completaste el gesto para ir al inicio." + + + + + + + + "Tecla de acción" "Para acceder a las apps, presiona la tecla de acción en el teclado." "¡Felicitaciones!" @@ -1440,22 +1450,19 @@ "Desliza hacia arriba con tres dedos y mantenlos presionados. Presiona para aprender más gestos." "Usa el teclado para ver todas las apps" "Presiona la tecla de acción en cualquier momento. Presiona para aprender más gestos." - "La atenuación extra ahora es parte de la barra de brillo" - "Ahora puedes bajar el nivel del brillo desde la parte superior de la pantalla para atenuarla aún más.\n\nEsto funciona mejor si estás en un ambiente oscuro." - "Quitar la combinación de teclas de atenuación extra" - "Se quitó el atajo de atenuación extra. Para bajar el brillo, usa la barra de brillo normal." - - - - - - - + - + - + - + + "Conectividad" + "Accesibilidad" + "Utilidades" + "Privacidad" + "Proporcionado por apps" + "Pantalla" + "Desconocido" diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 6595258bc8e8595789564b7ee0efcb1ecb48654c..df9af2ab9a498032ba7e9b1694e9d54310b7892f 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -1179,6 +1179,10 @@ "%1$d %%" "Altavoces y pantallas" "Dispositivos sugeridos" + + + + "Detén tu sesión compartida para transferir el contenido multimedia a otro dispositivo" "Detener" "Cómo funciona la emisión" @@ -1289,6 +1293,8 @@ "Añadir" "Gestionar usuarios" "Esta notificación no se puede arrastrar a la pantalla dividida" + + "Wi‑Fi no disponible" "Modo prioritario" "Alarma añadida" @@ -1345,6 +1351,8 @@ "Personalizar pantalla de bloqueo" "Desbloquea para personalizar la pantalla de bloqueo" "Red Wi-Fi no disponible" + + "Cámara bloqueada" "Cámara y micrófono bloqueados" "Micrófono bloqueado" @@ -1392,21 +1400,16 @@ "Icono de contraer" "Icono de desplegar" "o" - - - - - - - - - - - - + "Desplázate con el teclado" + "Aprende combinaciones de teclas" + "Desplázate con el panel táctil" + "Aprende gestos del panel táctil" + "Desplázate con el teclado y el panel táctil" + "Aprende gestos del panel táctil, combinaciones de teclas y más" "Gesto para volver" "Gesto para ir al inicio" - "Tecla de acción" + + "Hecho" "Atrás" "Para volver, desliza con tres dedos hacia la izquierda o la derecha en cualquier parte del panel táctil.\n\nTambién puedes hacerlo con la combinación de teclas asignada + Esc." @@ -1416,6 +1419,14 @@ "Para ir a la pantalla de inicio en cualquier momento, desliza hacia arriba con tres dedos desde la parte inferior de la pantalla." "¡Muy bien!" "Has completado el gesto para ir a la pantalla de inicio." + + + + + + + + "Tecla de acción" "Para acceder a tus aplicaciones, pulsa la tecla de acción de tu teclado." "¡Enhorabuena!" @@ -1439,22 +1450,19 @@ "Desliza hacia arriba y mantén pulsado con tres dedos. Toca para aprender a usar más gestos." "Usa el teclado para ver todas las aplicaciones" "Pulsa la tecla de acción en cualquier momento. Toca para aprender a usar más gestos." - "La atenuación extra ahora forma parte de la barra de brillo" - "Ahora puedes atenuar aún más tu pantalla reduciendo el nivel de brillo desde la parte superior.\n\nFunciona mejor cuando estás en un lugar con poca luz." - "Eliminar acceso directo a la atenuación extra" - "Acceso directo a la atenuación extra eliminado. Para reducir el brillo, usa la barra de brillo normal." - - - - - - - + - + - + - + + "Conectividad" + "Accesibilidad" + "Utilidades" + "Privacidad" + "Proporcionado por aplicaciones" + "Pantalla" + "Desconocido" diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 7042ac1368455037f88ef0c1de134826d26d62e6..5c2e4db0a0ece32f1493487d1df20c3e434adeae 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -105,8 +105,7 @@ "Lisa märkmesse" "Kaasa link" "%1$s (%2$d)" - - + "Teistelt profiilidelt ei saa linke lisada" "Ekraanisalvesti" "Ekraanisalvestuse töötlemine" "Pooleli märguanne ekraanikuva salvestamise seansi puhul" @@ -1180,6 +1179,10 @@ "%1$d%%" "Kõlarid ja ekraanid" "Soovitatud seadmed" + + + + "Peatage jagatud seanss, et meedia teise seadmesse teisaldada" "Peata" "Kuidas ülekandmine toimib?" @@ -1290,6 +1293,7 @@ "Lisa" "Kasutajate haldamine" "See märguanne ei toeta jagatud ekraanikuvale lohistamist." + "Aktiivne koht" "WiFi pole saadaval" "Režiim Prioriteetne" "Alarm on määratud" @@ -1346,6 +1350,7 @@ "Kohanda lukustuskuva" "Lukustuskuva kohandamiseks avage" "WiFi pole saadaval" + "Aktiivne koht" "Kaamera on blokeeritud" "Kaamera ja mikrofon on blokeeritud" "Mikrofon on blokeeritud" @@ -1401,7 +1406,7 @@ "Õppige puuteplaadi liigutusi, klaviatuuri otseteid ja palju muud" "Tagasiliikumisliigutus" "Avakuvale liikumise liigutus" - "Toiminguklahv" + "Hiljutiste rakenduste vaatamine" "Valmis" "Tagasi" "Tagasiliikumiseks pühkige puuteplaadil kolme sõrmega vasakule või paremale.\n\nSamuti saate selle jaoks kasutada klaviatuuri otseteed toiminguklahv + paoklahv." @@ -1411,6 +1416,10 @@ "Mis tahes ajal avakuvale liikumiseks pühkige kolme sõrmega ekraanikuva allosast üles." "Hästi tehtud!" "Tegite avakuvale minemise liigutuse." + "Hiljutiste rakenduste vaatamine" + "Pühkige üles ja hoidke kolme sõrmega puuteplaadil." + "Väga hea!" + "Lõpetasite hiljutiste rakenduste vaatamise liigutuse." "Toiminguklahv" "Rakendustele juurdepääsemiseks vajutage klaviatuuril toiminguklahvi." "Õnnitleme!" @@ -1434,22 +1443,15 @@ "Pühkige kolme sõrmega üles ja hoidke sõrmi plaadil. Puudutage žestide kohta lisateabe saamiseks." "Klaviatuuri kasutamine kõigi rakenduste kuvamiseks" "Vajutage soovitud ajal toiminguklahvi. Puudutage žestide kohta lisateabe saamiseks." - "Funktsioon „Eriti tume“ on nüüd osa ereduse reguleerimise ribast" - "Nüüd saate muuta ekraani eriti tumedaks, vähendades ereduse taset ekraani ülaosast veelgi rohkem.\n\nSee toimib kõige paremini hämaras keskkonnas." - "Eemalda funktsiooni „Eriti tume“ otsetee" - "Funktsiooni „Eriti tume“ otsetee eemaldati. Kasutage ereduse vähendamiseks tavapärast ereduse reguleerimise riba." - - - - - - - - - - - - - - + "Funktsioon „Eriti tume“ on nüüd osa ereduse liugurist" + "Nüüd saate muuta ekraani eriti tumedaks, vähendades ereduse taset veelgi rohkem.\n\nKuna see funktsioon kuulub nüüd ereduse liugurisse, eemaldatakse funktsiooni „Eriti tume“ otseteed." + "Eemalda funktsiooni „Eriti tume“ otseteed" + "Funktsiooni „Eriti tume“ otseteed eemaldati" + "Ühenduvus" + "Juurdepääsetavus" + "Utiliidid" + "Privaatsus" + "Rakendustelt" + "Kuva" + "Teadmata" diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index e51e37530751df499c2684dbe9aaaf63cb0a325e..5221cebfd9f258da26468fd6b1920f6bbde87927 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -105,8 +105,7 @@ "Gehitu oharrean" "Sartu esteka" "%1$s (%2$d)" - - + "Ezin da beste profiletako estekarik gehitu" "Pantaila-grabagailua" "Pantaila-grabaketa prozesatzen" "Pantailaren grabaketa-saioaren jakinarazpen jarraitua" @@ -443,7 +442,7 @@ "Desaktibatuta" "Konfiguratu" "Kudeatu ezarpenetan" - "{count,plural, =0{Ez dago modurik aktibo}=1{{mode} aktibo dago}other{# modu aktibo daude}}" + "{count,plural, =0{Ez dago modurik aktibo}=1{\"{mode}\" aktibo dago}other{# modu aktibo daude}}" "Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaera eta abisuen tonuak, eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak." "Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak joko ditu. Hala ere, zuk erreproduzitutako guztia entzun ahal izango duzu, besteak beste, musika, bideoak eta jokoak." "Pertsonalizatu" @@ -1180,6 +1179,10 @@ "%% %1$d" "Bozgorailuak eta pantailak" "Iradokitako gailuak" + + + + "Gelditu partekatutako saioa multimedia-edukia beste gailu batera eramateko" "Gelditu" "Nola funtzionatzen dute iragarpenek?" @@ -1290,6 +1293,7 @@ "Gehitu" "Kudeatu erabiltzaileak" "Jakinarazpen hau ezin da arrastatu pantaila zatitura" + "Kokapena aktibatuta dago" "Wifi-konexioa ez dago erabilgarri" "Lehentasun modua" "Alarma ezarrita dago" @@ -1346,6 +1350,7 @@ "Pertsonalizatu pantaila blokeatua" "Desblokeatu eta pertsonalizatu pantaila blokeatua" "Wifi-konexioa ez dago erabilgarri" + "Kokapena aktibatuta dago" "Kamera blokeatuta dago" "Kamera eta mikrofonoa blokeatuta daude" "Mikrofonoa blokeatuta dago" @@ -1401,7 +1406,7 @@ "Ikasi ukipen-paneleko keinuak, lasterbideak eta abar" "Atzera egiteko keinua" "Orri nagusira joateko keinua" - "Ekintza-tekla" + "Ikusi azkenaldiko aplikazioak" "Eginda" "Egin atzera" "Atzera egiteko, pasatu 3 hatz ezkerrera edo eskuinera ukipen-panelean.\n\nEkintza + Ihes lasterbidea ere erabil dezakezu horretarako." @@ -1411,6 +1416,10 @@ "Orri nagusira joateko, pasatu 3 hatz pantailaren behealdetik gora." "Ederki!" "Ikasi duzu hasierako pantailara joateko keinua." + "Ikusi azkenaldiko aplikazioak" + "Pasatu 3 hatz gora eta eduki sakatuta ukipen-panelean." + "Bikain!" + "Osatu duzu azkenaldiko aplikazioak ikusteko keinua." "Ekintza-tekla" "Aplikazioak atzitzeko, sakatu teklatuko ekintza-tekla." "Zorionak!" @@ -1434,22 +1443,15 @@ "Pasatu 3 hatz gora eta eduki sakatuta. Sakatu keinu gehiago ikasteko." "Erabili teklatua aplikazio guztiak ikusteko" "Sakatu ekintza-tekla edonoiz. Sakatu keinu gehiago ikasteko." - "Orain, argitasun-barran agertzen da Are ilunago" - "Orain, pantaila are ilunago jar dezakezu, pantailaren goialdetik argitasun-maila are gehiago jaitsita.\n\nIngurune ilun batean zaudenean funtzionatzen du ondoen." - "Kendu Are ilunago eginbidearen lasterbidea" - "Kendu da Are ilunago eginbidearen lasterbidea. Argitasuna murrizteko, erabili argitasun-barra arrunta." - - - - - - - - - - - - - - + "Orain, argitasunaren graduatzailean agertzen da Are ilunago" + "Orain, pantaila are ilunago jar dezakezu, argitasun-maila are gehiago jaitsita.\n\nOrain eginbide hori argitasunaren graduatzailean agertzen denez, Are ilunago eginbidearen lasterbideak kendu egingo dira." + "Kendu Are ilunago eginbidearen lasterbideak" + "Kendu dira Are ilunago eginbidearen lasterbideak" + "Konexioa" + "Erabilerraztasuna" + "Zerbitzu-aplikazioak" + "Pribatutasuna" + "Aplikazioenak" + "Pantaila" + "Ezezagunak" diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 13d403b1d274ec18c797b3a2594201ab0c9505e2..0696752c0a1db25d28da6e29002d6378e61dd01c 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "بلندگوها و نمایشگرها" "دستگاه‌های پیشنهادی" + + + + "جلسه مشترک برای انتقال رسانه به دستگاهی دیگر متوقف می‌شود" "توقف" "همه‌فرتستی چطور کار می‌کند" @@ -1289,6 +1293,8 @@ "افزودن" "مدیریت کاربران" "این اعلان از عملکرد کشیدن به صفحهٔ دونیمه پشتیبانی نمی‌کند" + + "‏Wi‑Fi دردسترس نیست" "حالت اولویت" "زنگ ساعت تنظیم شد" @@ -1345,6 +1351,8 @@ "سفارشی‌سازی صفحه قفل" "برای سفارشی‌سازی صفحه قفل، قفل را باز کنید" "‏Wi-Fi دردسترس نیست" + + "دوربین مسدود شده است" "دوربین و میکروفون مسدود شده‌اند" "میکروفون مسدود شده است" @@ -1400,7 +1408,8 @@ "آشنایی با اشاره‌های صفحه لمسی، میان‌برهای صفحه‌کلید، و موارد دیگر" "اشاره برگشت" "اشاره صفحه اصلی" - "دکمه کنش" + + "تمام" "برگشتن" "برای برگشتن، در هر جایی از صفحه لمسی، با سه انگشت تند به‌چپ یا راست بکشید.\n\nبرای این کار می‌توانید از میان‌بر صفحه‌کلید «کنش + گریز» هم استفاده کنید." @@ -1410,6 +1419,14 @@ "برای رفتن به صفحه اصلی در هرزمانی، با سه انگشت از پایین صفحه‌نمایش تند به‌بالا بکشید." "آفرین!" "اشاره رفتن به صفحه اصلی را تکمیل کردید." + + + + + + + + "دکمه کنش" "برای دسترسی به برنامه‌هایتان، دکمه کنش در صفحه‌کلید را فشار دهید." "تبریک!" @@ -1433,22 +1450,19 @@ "با سه انگشت تند به‌بالا بکشید و نگه دارید. برای آشنایی با اشاره‌های بیشتر، تک‌ضرب بزنید." "برای مشاهده همه برنامه‌ها، از صفحه‌کلید استفاده کنید" "در هرزمانی دکمه کنش را فشار دهید. برای آشنایی با اشاره‌های بیشتر، تک‌ضرب بزنید." - "«بسیار کم‌نور» اکنون بخشی از نوار روشنایی است" - "ازاین‌پس می‌توانید با پایین‌تر آوردن سطح روشنایی از بالای صفحه‌نمایش، صفحه‌نمایش را بسیار کم‌نور کنید.\n\nاین ویژگی زمانی بهترین عملکرد را دارد که در محیطی تاریک باشید." - "حذف میان‌بر «بسیار کم‌نور»" - "میان‌بر «بسیار کم‌نور» حذف شد. برای کم کردن روشنایی، از نوار معمول روشنایی استفاده کنید." - - - - - - - + - + - + - + + "اتصال‌پذیری" + "دسترس‌پذیری" + "برنامه‌های کمکی" + "حریم خصوصی" + "ارائه‌شده از برنامه‌ها" + "نمایشگر" + "نامشخص" diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index ab31329cf26c4ba87e10c60266cf4dd2ee6d9d56..527d3c963967c5dc95752590a33251bfb567018e 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -105,8 +105,7 @@ "Lisää muistiinpanoon" "Lisää linkki" "%1$s (%2$d)" - - + "Linkkejä ei voi lisätä muista profiileista" "Näytön tallentaja" "Näytön tallennusta käsitellään" "Pysyvä ilmoitus näytön tallentamisesta" @@ -1180,6 +1179,10 @@ "%1$d %%" "Kaiuttimet ja näytöt" "Ehdotetut laitteet" + + + + "Lopeta jaettu istunto, jotta voit siirtyä mediaan toisella laitteella" "Lopeta" "Miten lähetys toimii" @@ -1290,6 +1293,8 @@ "Lisää" "Ylläpidä käyttäjiä" "Ilmoitus ei tue jaetulle näytölle vetämistä" + + "Wi‑Fi ei ole saatavilla" "Tärkeät-tila" "Hälytys asetettu" @@ -1346,6 +1351,8 @@ "Muokkaa lukitusnäyttöä" "Voit muokata lukitusnäyttöä, kun avaat lukituksen" "Wi-Fi-yhteys ei ole käytettävissä" + + "Kamera estetty" "Kamera ja mikrofoni estetty" "Mikrofoni estetty" @@ -1401,7 +1408,8 @@ "Opettele kosketuslevyn eleitä, pikanäppäimiä ja muuta" "Takaisin-ele" "Etusivu-ele" - "Toimintonäppäin" + + "Valmis" "Takaisin" "Jos haluat siirtyä takaisin, pyyhkäise kosketuslevyllä vasemmalle tai oikealle kolmella sormella.\n\nVoit myös käyttää pikanäppäinyhdistelmää toimintonäppäin + ESC." @@ -1411,6 +1419,14 @@ "Voit siirtyä aloitusnäytölle milloin tahansa pyyhkäisemällä ylös näytön alareunasta kolmella sormella." "Hienoa!" "Olet oppinut aloitusnäytölle palaamiseleen." + + + + + + + + "Toimintonäppäin" "Voit käyttää sovelluksia painamalla näppäimistön toimintonäppäintä." "Onnittelut!" @@ -1434,22 +1450,19 @@ "Pyyhkäise ylös ja pidä kosketuslevyä painettuna kolmella sormella. Lue lisää eleistä napauttamalla." "Kaikkien sovellusten näkeminen näppäimistön avulla" "Voit painaa toimintonäppäintä milloin tahansa. Lue lisää eleistä napauttamalla." - "Erittäin himmeä on nyt osa kirkkauspalkkia" - "Voit nyt tehdä näytöstä erittäin himmeän vähentämällä kirkkautta vieläkin enemmän näytön yläreunasta.\n\nTämä toimii parhaiten pimeässä ympäristössä." - "Poista erittäin himmeä ‑pikakomento" - "Erittäin himmeä ‑pikakomento poistettu. Voit vähentää kirkkautta tavallisesta kirkkauspalkista." - - - - - - - + - + - + - + + "Yhteydet" + "Saavutettavuus" + "Apusovellukset" + "Yksityisyys" + "Sovellusten tarjoama" + "Näyttö" + "Tuntematon" diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index ecb17f9ca1edf5ba8b97fefcdaf33d718dc4d58d..f7753eca8c639dbfd9123d1676fc49735284a700 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -105,8 +105,7 @@ "Ajouter à une note" "Inclure le lien" "%1$s (%2$d)" - - + "Des liens ne peuvent pas être ajoutés à partir d\'autres profils" "Enregistreur d\'écran" "Trait. de l\'enregist. d\'écran…" "Notification en cours pour une session d\'enregistrement d\'écran" @@ -578,7 +577,7 @@ "Aucune notification" "Aucune nouvelle notification" "L\'atténuation des notifications est activée" - "Le volume de votre appareil est réduit pendant deux minutes si vous recevez trop de notifications." + "Les alertes et le volume de l\'appareil sont réduits automatiquement pendant 2 minutes maximum quand vous recevez trop de notifications à la fois." "Désactiver" "Déverr. pour voir les anciennes notif." "Cet appareil est géré par ton parent" @@ -1180,6 +1179,10 @@ "%1$d %%" "Haut-parleurs et écrans" "Appareils suggérés" + + + + "Arrêtez votre session partagée pour déplacer des contenus multimédias vers un autre appareil" "Arrêter" "Fonctionnement de la diffusion" @@ -1290,6 +1293,8 @@ "Ajouter" "Gérer utilisateurs" "Cette notification ne prend pas en charge l\'écran partagé par glissement" + + "Wi-Fi non disponible" "Mode priorité" "L\'alarme a été réglée" @@ -1346,6 +1351,8 @@ "Personn. l\'écran de verrouillage" "Déverrouiller pour personnaliser l\'écran de verrouillage" "Wi-Fi non accessible" + + "Appareil photo bloqué" "Appareil photo et microphone bloqués" "Microphone bloqué" @@ -1401,7 +1408,8 @@ "Apprenez les gestes du pavé tactile, les raccourcis-clavier et bien plus encore" "Geste de retour" "Geste d\'accès à l\'écran d\'accueil" - "Touche d\'action" + + "OK" "Retour" "Pour revenir en arrière, balayez vers la gauche ou la droite en utilisant trois doigts n\'importe où sur le pavé tactile.\n\nVous pouvez également utiliser le raccourci clavier Action+Échap." @@ -1411,6 +1419,14 @@ "Pour accéder à votre écran d\'accueil à tout moment, balayez l\'écran du bas vers le haut avec trois doigts." "Bien!" "Vous avez appris le geste de retour à l\'écran d\'accueil." + + + + + + + + "Touche d\'action" "Pour accéder à vos applis, appuyez sur la touche d\'action de votre clavier." "Félicitations!" @@ -1434,22 +1450,19 @@ "Balayez l\'écran vers le haut avec trois doigts et maintenez-les en place. Touchez pour apprendre d\'autres gestes." "Utiliser votre clavier pour afficher toutes les applis" "Appuyez sur la touche d\'action à tout moment. Touchez pour apprendre d\'autres gestes." - "La réduction supplémentaire de la luminosité fait désormais partie de la barre de luminosité" - "Vous pouvez désormais rendre l\'écran encore plus sombre en réduisant davantage le niveau de luminosité à partir du haut de l\'écran.\n\nCela fonctionne mieux lorsque vous êtes dans un environnement sombre." - "Retirer le raccourci de réduction supplémentaire de la luminosité" - "Le raccourci de réduction supplémentaire de la luminosité à été retiré. Pour réduire la luminosité, utilisez la barre de luminosité habituelle." - - - - - - - + - + - + - + + "Connectivité" + "Accessibilité" + "Utilitaires" + "Confidentialité" + "Fournies par des applis" + "Affichage" + "Inconnu" diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index c023f1b0f42e5245234bff5fdfcfa0cb8393bc19..8b5214eb5798c9bc2a751bb0291396818e4d4c61 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -105,8 +105,7 @@ "Ajouter à la note" "Inclure le lien" "%1$s (%2$d)" - - + "Impossible d\'ajouter des liens depuis d\'autres profils" "Enregistreur d\'écran" "Enregistrement de l\'écran…" "Notification en cours pour une session d\'enregistrement de l\'écran" @@ -1180,6 +1179,10 @@ "%1$d %%" "Enceintes et écrans" "Appareils suggérés" + + + + "Arrêter votre session partagée pour déplacer des contenus multimédias vers un autre appareil" "Arrêter" "Fonctionnement des annonces" @@ -1290,6 +1293,8 @@ "Ajouter" "Gérer utilisateurs" "Impossible de faire glisser cette notification vers l\'écran partagé" + + "Wi‑Fi non disponible" "Mode Prioritaire" "Alarme réglée" @@ -1346,6 +1351,8 @@ "Personnaliser écran verrouillage" "Déverrouillez pour personnaliser l\'écran de verrouillage" "Wi-Fi non disponible" + + "Caméra bloquée" "Caméra et micro bloqués" "Micro bloqué" @@ -1393,21 +1400,16 @@ "Icône Réduire" "Icône Développer" "ou" - - - - - - - - - - - - + "Naviguer à l\'aide du clavier" + "Découvrir les raccourcis clavier" + "Naviguer à l\'aide de votre pavé tactile" + "Découvrir les gestes au pavé tactile" + "Naviguer à l\'aide de votre clavier et de votre pavé tactile" + "Découvrir les gestes au pavé tactile, les raccourcis clavier et plus encore" "Geste Retour" "Geste Accueil" - "Touche d\'action" + + "OK" "Retour" "Pour revenir en arrière, balayez vers la gauche ou vers la droite avec trois doigts n\'importe où sur le pavé tactile.\n\nVous pouvez aussi utiliser le raccourci clavier Action+Échap pour cela." @@ -1417,6 +1419,14 @@ "Pour accéder à l\'écran d\'accueil à tout moment, balayez l\'écran du bas vers le haut avec trois doigts." "Bravo !" "Vous avez appris le geste pour revenir à l\'écran d\'accueil." + + + + + + + + "Touche d\'action" "Pour accéder à vos applis, appuyez sur la touche d\'action de votre clavier." "Félicitations !" @@ -1440,22 +1450,19 @@ "Balayez vers le haut en utilisant trois doigts et maintenez. Appuyez pour apprendre d\'autres gestes." "Utilisez votre clavier pour afficher toutes les applis" "Appuyez sur la touche d\'action à tout moment. Appuyez pour apprendre d\'autres gestes." - "Luminosité ultra-réduite fait désormais partie de la barre de luminosité" - "Désormais, vous pouvez rendre l\'écran encore plus sombre en abaissant davantage le niveau de luminosité en haut de l\'écran.\n\nCela fonctionne mieux lorsque vous êtes dans un environnement sombre." - "Supprimer le raccourci Luminosité ultra-réduite" - "Raccourci Luminosité ultra-réduite supprimé. Pour diminuer la luminosité, utilisez la barre de luminosité habituelle." - - - - - - - + - + - + - + + "Connectivité" + "Accessibilité" + "Utilitaires" + "Confidentialité" + "Fournis par des applis" + "Écran" + "Inconnu" diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index b525bb8f1234a7e8e9a10835a7288a8a6aacbaf4..799e577708e878920fb479fece89fb2f19f60cff 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -105,8 +105,7 @@ "Engadir a unha nota" "Incluír ligazón" "%1$s (%2$d)" - - + "Non se poden engadir ligazóns desde outros perfís" "Gravadora da pantalla" "Procesando gravación pantalla" "Notificación de actividade en curso sobre unha sesión de gravación de pantalla" @@ -578,7 +577,7 @@ "Non hai notificacións" "Non hai notificacións novas" "A opción Amainar notificacións está activada" - "Cando recibas moitas notificacións, o volume e as alertas reduciranse automaticamente durante ata dous minutos." + "Ao recibir moitas notificacións, o volume e as alertas redúcense automaticamente ata dous minutos." "Desactivar" "Desbloquea para ver máis notificacións" "O teu pai ou nai xestiona este dispositivo" @@ -1180,6 +1179,10 @@ "%1$d %%" "Altofalantes e pantallas" "Dispositivos suxeridos" + + + + "Detén a sesión de uso compartido para mover contido multimedia a outro dispositivo" "Deter" "Como funcionan as difusións?" @@ -1290,6 +1293,7 @@ "Engadir" "Usuarios" "Esta notificación non pode arrastrarse á pantalla dividida" + "Localización activa" "A wifi non está dispoñible" "Modo de prioridade" "Alarma definida" @@ -1346,6 +1350,7 @@ "Personalizar pantalla de bloqueo" "Para personalizar a pantalla de bloqueo, primeiro desbloquea o dispositivo" "Wifi non dispoñible" + "Localización activa" "A cámara está bloqueada" "A cámara e o micrófono están bloqueados" "O micrófono está bloqueado" @@ -1401,7 +1406,7 @@ "Aprende a usar os xestos do panel táctil, atallos de teclado e moito máis" "Xesto para volver" "Xesto para ir ao inicio" - "Tecla de acción" + "Consultar aplicacións recentes" "Feito" "Volver" "Para retroceder, pasa tres dedos cara á esquerda ou cara á dereita en calquera parte do panel táctil.\n\nTamén podes usar o atallo de teclado Acción + Escape." @@ -1411,6 +1416,10 @@ "Para ir á pantalla de inicio, pasa tres dedos cara arriba desde a parte inferior da pantalla." "Excelente!" "Completaches o xesto de ir ao inicio." + "Consultar aplicacións recentes" + "Pasa tres dedos cara arriba e mantenos premidos no panel táctil." + "Moi ben!" + "Completaches o titorial do xesto de consultar aplicacións recentes." "Tecla de acción" "Para acceder ás aplicacións, preme a tecla de acción do teclado." "Parabéns!" @@ -1434,22 +1443,15 @@ "Pasa tres dedos cara arriba e mantenos premidos. Toca para obter máis información sobre os xestos." "Usa o teclado para ver todas as aplicacións" "Preme a tecla de acción cando queiras. Toca para obter máis información sobre os xestos." - "A atenuación extra agora está incluída na barra de brillo" - "Agora podes aumentar a atenuación da pantalla: só tes que baixar o nivel de brillo aínda máis desde a parte superior.\n\nEsta opción funciona mellor se estás nun ambiente escuro." - "Quitar atallo de atenuación extra" - "Quitouse o atallo de atenuación extra. Para reducir o brillo, usa a barra de brillo normal." - - - - - - - - - - - - - - + "A atenuación extra agora está incluída no control desprazable do brillo" + "Agora podes aumentar a atenuación da pantalla: só tes que baixar o nivel de brillo aínda máis.\n\nComo agora esta opción está incluída no control desprazable do brillo, quitaranse os atallos de atenuación extra." + "Quitar atallos de atenuación extra" + "Quitáronse os atallos de atenuación extra" + "Conectividade" + "Accesibilidade" + "Utilidades" + "Privacidade" + "Provenientes de aplicacións" + "Visualización" + "Categoría descoñecida" diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 9afbf8e4f8579a1c283a9ef1918255acfe524f29..a0732d73812a93132997c1e32d2385aed6551d02 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "સ્પીકર અને ડિસ્પ્લે" "સૂચવેલા ડિવાઇસ" + + + + "મીડિયાને બીજા ડિવાઇસ પર ખસેડવા માટે તમારું શેર કરેલું સત્ર રોકો" "રોકો" "બ્રોડકાસ્ટ પ્રક્રિયાની કામ કરવાની રીત" @@ -1289,6 +1293,7 @@ "ઉમેરો" "વપરાશકર્તાઓને મેનેજ કરો" "આ નોટિફિકેશન તેને વિભાજિત સ્ક્રીનમાં ખેંચવાની સુવિધાને સપોર્ટ કરતું નથી" + "લોકેશન સક્રિય" "વાઇ-ફાઇ ઉપલબ્ધ નથી" "પ્રાધાન્યતા મોડ" "અલાર્મ સેટ" @@ -1345,6 +1350,7 @@ "લૉક સ્ક્રીન કસ્ટમાઇઝ કરો" "લૉક સ્ક્રીનને કસ્ટમાઇઝ કરવા માટે અનલૉક કરો" "વાઇ-ફાઇ ઉપલબ્ધ નથી" + "લોકેશન સક્રિય" "કૅમેરા બ્લૉક કરેલો છે" "કૅમેરા અને માઇક્રોફોન બ્લૉક કરેલા છે" "માઇક્રોફોન બ્લૉક કરેલો છે" @@ -1400,7 +1406,7 @@ "ટચપૅડના સંકેતો અને કીબોર્ડના શૉર્ટકટ જેવું બીજું ઘણું જાણો" "પાછળ જવાનો સંકેત" "હોમ સ્ક્રીન પર જવાનો સંકેત" - "ઍક્શન કી" + "તાજેતરની ઍપ જુઓ" "થઈ ગયું" "પાછા જાઓ" "પાછા જવા માટે, ટચપૅડ પર ગમે ત્યાં ત્રણ આંગળી વડે ડાબે અથવા જમણે સ્વાઇપ કરો.\n\nઆના માટે તમે કીબોર્ડ શૉર્ટકટ Action + ESCનો ઉપયોગ કરી શકો છો." @@ -1410,6 +1416,10 @@ "કોઈપણ સમયે તમારી હોમ સ્ક્રીન પર જવા માટે, ત્રણ આંગળી વડે તમારી સ્ક્રીનની સૌથી નીચેની બાજુએથી ઉપરની તરફ સ્વાઇપ કરો." "સરસ!" "તમે હોમ સ્ક્રીન પર જવાનો સંકેત પૂર્ણ કર્યો છે." + "તાજેતરની ઍપ જુઓ" + "તમારા ટચપૅડ પર ત્રણ આંગળીઓનો ઉપયોગ કરીને ઉપર સ્વાઇપ કરો અને દબાવી રાખો." + "ખૂબ સરસ કામ!" + "તમે \"તાજેતરની ઍપ જુઓ\" સંકેત પૂર્ણ કર્યો." "ઍક્શન કી" "તમારી ઍપ ઍક્સેસ કરવા માટે, તમારા કીબોર્ડ પરની ઍક્શન કી દબાવો." "અભિનંદન!" @@ -1433,22 +1443,15 @@ "ત્રણ આંગળીઓનો ઉપયોગ કરીને ઉપર સ્વાઇપ કરો અને દબાવી રાખો. સંકેતો વિશે વધુ જાણવા માટે ટૅપ કરો." "બધી ઍપ જોવા માટે તમારા કીબોર્ડનો ઉપયોગ કરો" "કોઈપણ સમયે ઍક્શન કી દબાવો. સંકેતો વિશે વધુ જાણવા માટે ટૅપ કરો." - "બ્રાઇટનેસ બાર હવે એક્સ્ટ્રા ડિમનો ભાગ છે" - "તમે હવે તમારી સ્ક્રીનના સૌથી ઉપરના ભાગમાંથી બ્રાઇટનેસ લેવલને હજી પણ ઘટાડીને સ્ક્રીનને એક્સ્ટ્રા ડિમ બનાવી શકો છો.\n\nતમે ડાર્ક વાતાવરણમાં હો, ત્યારે આ શ્રેષ્ઠ રીતે કામ કરે છે." - "એક્સ્ટ્રા ડિમ શૉર્ટકટ કાઢી નાખો" - "એક્સ્ટ્રા ડિમ શૉર્ટકટ કાઢી નાખ્યો. તમારી બ્રાઇટનેસ ઘટાડવા માટે, નિયમિત બ્રાઇટનેસ બારનો ઉપયોગ કરો." - - - - - - - - - - - - - - + "બ્રાઇટનેસ સ્લાઇડર હવે એક્સ્ટ્રા ડિમનો ભાગ છે" + "તમે હવે બ્રાઇટનેસ લેવલને હજી પણ ઘટાડીને સ્ક્રીનને એક્સ્ટ્રા ડિમ બનાવી શકો છો.\n\nઆ સુવિધા હવે બ્રાઇટનેસ સ્લાઇડરનો ભાગ હોવાથી એક્સ્ટ્રા ડિમ શૉર્ટકટને કાઢી નાખવામાં આવી રહ્યાં છે." + "એક્સ્ટ્રા ડિમ શૉર્ટકટ કાઢી નાખો" + "એક્સ્ટ્રા ડિમ શૉર્ટકટ કાઢી નાખ્યા" + "કનેક્ટિવિટી" + "ઍક્સેસિબિલિટી" + "યુટિલિટી" + "પ્રાઇવસી" + "ઍપ દ્વારા પ્રદાન કરવામાં આવેલી" + "ડિસ્પ્લે" + "અજાણ" diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 2b28b7a12522d8a1b5ff297d68d035dbcfc18b66..3984f7176252507b0a7358fea91106f9f945c8bd 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -253,7 +253,7 @@ "VPN चालू." "%d प्रति‍शत बैटरी." "बैटरी %1$d प्रतिशत चार्ज है, जो कि %2$s चल जाएगी" - "बैटरी चार्ज हो रही है, %d प्रतिशत." + "बैटरी चार्ज हो रही है, %d%%." "बैटरी %d प्रतिशत चार्ज हुई. बैटरी खराब होने से बचाने के लिए, चार्जिंग रोक दी गई है." "बैटरी %1$d प्रतिशत चार्ज हुई, जो कि %2$s चल जाएगी. बैटरी खराब होने से बचाने के लिए, चार्जिंग रोक दी गई है." "पूरी सूचनाएं देखें" @@ -1179,6 +1179,10 @@ "%1$d%%" "स्पीकर और डिसप्ले" "सुझाए गए डिवाइस" + + + + "मीडिया को किसी दूसरे डिवाइस में ट्रांसफ़र करने के लिए, अपने शेयर किए गए सेशन को बंद करें" "बंद करें" "ब्रॉडकास्ट करने की सुविधा कैसे काम करती है" @@ -1289,6 +1293,7 @@ "जोड़ें" "उपयोगकर्ताओं को मैनेज करें" "इस सूचना को स्प्लिट स्क्रीन मोड में, खींचा और छोड़ा नहीं जा सकता" + "जगह की जानकारी की सेटिंग चालू है" "वाई-फ़ाई उपलब्ध नहीं है" "प्राथमिकता मोड" "अलार्म सेट किया गया" @@ -1345,6 +1350,7 @@ "लॉक स्क्रीन को पसंद के मुताबिक बनाएं" "लॉक स्क्रीन को पसंद के मुताबिक बनाने के लिए अनलॉक करें" "वाई-फ़ाई उपलब्ध नहीं है" + "जगह की जानकारी की सेटिंग चालू है" "कैमरे का ऐक्सेस नहीं है" "कैमरे और माइक्रोफ़ोन का ऐक्सेस नहीं है" "माइक्रोफ़ोन का ऐक्सेस नहीं है" @@ -1400,7 +1406,7 @@ "टचपैड पर हाथ के जेस्चर, कीबोर्ड शॉर्टकट वगैरह के बारे में जानें" "पीछे जाने का जेस्चर" "होम स्क्रीन पर जाने का जेस्चर" - "ऐक्शन बटन" + "हाल ही में इस्तेमाल किए गए ऐप्लिकेशन देखें" "हो गया" "वापस जाएं" "वापस जाने के लिए, टचपैड पर कहीं भी तीन उंगलियों से दाईं या बाईं ओर स्वाइप करें.\n\nइसके अलावा, ऐसा करने के लिए Action + ESC बटन का भी इस्तेमाल किया जा सकता है." @@ -1410,6 +1416,10 @@ "किसी भी समय फ़ोन की होम स्क्रीन पर जाने के लिए, तीन उंगलियों से फ़ोन पर सबसे नीचे से ऊपर की ओर स्वाइप करें." "बढ़िया!" "आपने जान लिया कि हाथ का जेस्चर इस्तेमाल करके, होम स्क्रीन पर कैसे जाएं." + "हाल ही में इस्तेमाल किए गए ऐप्लिकेशन देखें" + "अपने टचपैड पर तीन उंगलियों से ऊपर की ओर स्वाइप करें और फिर होल्ड करें." + "बहुत बढ़िया!" + "आपने हाल ही में इस्तेमाल किए गए ऐप्लिकेशन देखने के लिए, हाथ के जेस्चर के बारे में जान लिया है." "ऐक्शन बटन" "अपने ऐप्लिकेशन ऐक्सेस करने के लिए, कीबोर्ड पर ऐक्शन बटन दबाएं." "बधाई हो!" @@ -1433,22 +1443,15 @@ "तीन उंगलियों से ऊपर की ओर स्वाइप करें और दबाकर रखें. जेस्चर की ज़्यादा जानकारी पाने के लिए टैप करें." "सभी ऐप्लिकेशन देखने के लिए, कीबोर्ड का इस्तेमाल करें" "किसी भी समय ऐक्शन बटन दबाएं. हाथ के जेस्चर के बारे में ज़्यादा जानने के लिए टैप करें." - "स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा, अब ब्राइटनेस बार का हिस्सा है" - "अब स्क्रीन के सबसे ऊपरी हिस्से से, स्क्रीन की रोशनी सामान्य लेवल से और कम की जा सकती है.\n\nयह सुविधा, अंधेरे वाली जगह पर बेहतर तरीके से काम करती है." - "स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा का शॉर्टकट हटाएं" - "स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा का शॉर्टकट हटा दिया गया. स्क्रीन की रोशनी कम करने के लिए, ब्राइटनेस बार का इस्तेमाल करें." - - - - - - - - - - - - - - + "स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा, अब ब्राइटनेस स्लाइडर का हिस्सा है" + "अब ब्राइटनेस लेवल घटाकर, स्क्रीन की रोशनी को सामान्य लेवल से और कम किया जा सकता है.\n\nयह सुविधा ब्राइटनेस स्लाइडर का हिस्सा है. इसलिए, स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा वाले शॉर्टकट हटा दिए गए हैं." + "स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा वाले शॉर्टकट हटाएं" + "स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा वाले शॉर्टकट हटा दिए गए हैं" + "कनेक्टिविटी" + "सुलभता" + "काम की सेवाएं" + "निजता" + "ऐप्लिकेशन से मिली जानकारी" + "डिसप्ले" + "कोई जानकारी नहीं है" diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 34bccbd6229e8db071ca614fcfd5cb9d5bbbeb16..545d1d1af64fb4618e34b2c1dbfb7c194f2399ea 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -105,8 +105,7 @@ "Dodaj bilješci" "Uključi vezu" "%1$s (%2$d)" - - + "Veze se ne mogu dodati s drugih profila" "Snimač zaslona" "Obrada snimanja zaslona" "Tekuća obavijest za sesiju snimanja zaslona" @@ -1180,6 +1179,10 @@ "%1$d%%" "Zvučnici i zasloni" "Predloženi uređaji" + + + + "Zaustavite dijeljenu sesiju da biste premjestili medij na drugi uređaj" "Zaustavi" "Kako emitiranje funkcionira" @@ -1290,6 +1293,8 @@ "Dodaj" "Upravljanje korisnicima" "Ova obavijest ne podržava povlačenje na podijeljeni zaslon." + + "Wi‑Fi nije dostupan" "Prioritetni način rada" "Alarm je postavljen" @@ -1346,6 +1351,8 @@ "Prilagodite zaključavanje zaslona" "Otključajte da biste prilagodili zaključani zaslon" "Wi-Fi nije dostupan" + + "Kamera je blokirana" "Blokirani su kamera i mikrofon" "Mikrofon je blokiran" @@ -1401,7 +1408,8 @@ "Saznajte više o pokretima za dodirnu podlogu, tipkovnim prečacima i ostalom" "Pokret za povratak" "Pokret za otvaranje početnog zaslona" - "Tipka za radnju" + + "Gotovo" "Natrag" "Da biste se vratili natrag, s tri prsta prijeđite ulijevo ili udesno bilo gdje na dodirnoj podlozi.\n\nZa to možete upotrijebiti i tipku za radnju tipkovnog prečaca + ESC." @@ -1411,6 +1419,14 @@ "Da biste u bilo kojem trenutku otvorili početni zaslon, trima prstima prijeđite prema gore od dna zaslona." "Odlično!" "Izvršili ste pokret za otvaranje početnog zaslona." + + + + + + + + "Tipka za radnju" "Da biste pristupili svojim aplikacijama, pritisnite tipku za radnje na tipkovnici." "Čestitamo!" @@ -1434,22 +1450,19 @@ "Prijeđite prema gore trima prstima i zadržite pritisak. Dodirnite da biste naučili više pokreta." "Upotrijebite tipkovnicu za prikaz svih aplikacija" "Pritisnite tipku za radnju u bilo kojem trenutku. Dodirnite da biste naučili više pokreta." - "Dodatno zatamnjenje sada je dio trake za svjetlinu" - "Zaslon možete dodatno zatamniti daljnjim smanjivanjem razine svjetline na vrhu zaslona.\n\nTo najbolje funkcionira kada ste u tamnom okruženju." - "Ukloni prečac za dodatno zatamnjenje" - "Prečac za dodatno zatamnjenje je uklonjen. Da biste smanjili svjetlinu, upotrijebite regularnu traku za svjetlinu." - - - - - - - + - + - + - + + "Povezivost" + "Pristupačnost" + "Uslužni programi" + "Privatnost" + "Pružaju aplikacije" + "Prikaz" + "Nepoznato" diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index cc2f66e1a04a816f9072f0653a3b73d55d636258..e0df7c4aa1a43013215f93f69ade7125990a6291 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -105,8 +105,7 @@ "Hozzáadás jegyzethez" "Linkkel együtt" "%1$s (%2$d)" - - + "Más profilokból nem lehet linkeket hozzáadni" "Képernyőrögzítő" "Képernyőrögzítés feldolgozása" "Folyamatban lévő értesítés képernyőrögzítési munkamenethez" @@ -1180,6 +1179,10 @@ "%1$d%%" "Hangfalak és kijelzők" "Javasolt eszközök" + + + + "Állítsa le a megosztott munkamenetet, ha át szeretné helyezni a médiát egy másik eszközre" "Leállítás" "A közvetítés működése" @@ -1290,6 +1293,8 @@ "Hozzáadás" "Kezelés" "Az értesítés nem támogatja az osztott képernyőre való áthúzást." + + "A Wi‑Fi nem áll rendelkezésre" "Prioritás mód" "Ébresztő beállítva" @@ -1346,6 +1351,8 @@ "Lezárási képernyő testreszabása" "Oldja fel a zárolást a lezárási képernyő testreszabásához" "Nem áll rendelkezésre Wi-Fi" + + "Kamera letiltva" "Kamera és mikrofon letiltva" "Mikrofon letiltva" @@ -1401,7 +1408,8 @@ "Érintőpad-kézmozdulatok, billentyűparancsok és egyebek megismerése" "Vissza kézmozdulat" "Kezdőképernyő kézmozdulat" - "Műveletbillentyű" + + "Kész" "Vissza" "A visszalépéshez csúsztasson három ujjal balra vagy a jobbra az érintőpadon.\n\nEnnek végrehajtásához használhatja az Action + Esc billentyűparancsot is." @@ -1411,6 +1419,14 @@ "Ha bármikor vissza szeretne térni a kezdőképernyőre, csúsztassa gyorsan felfelé három ujját a képernyő aljáról." "Remek!" "Teljesítette a kezdőképernyőre lépés kézmozdulatát." + + + + + + + + "Műveletbillentyű" "Az alkalmazásokhoz való hozzáféréshez nyomja meg a billentyűzet műveletbillentyűjét." "Gratulálunk!" @@ -1434,22 +1450,19 @@ "Gyúsztason felfelé három ujjal, és tartsa lenyomva az ujjait. Koppintson a további kézmozdulatokért." "A billentyűzet használatával valamennyi alkalmazás megtekinthető" "A műveletbillentyű bármikor használható. Koppintson a további kézmozdulatokért." - "Az extrasötét funkció mostantól része a fényerő-beállítási sávnak" - "A képernyő tetején mostantól extrasötétre állíthatja a képernyőt, amivel a korábbinál még jobban csökkentheti a fényerőt.\n\nA funkció sötét környezetben használható a legjobban." - "Az extrasötét funkció gyorsparancsának eltávolítása" - "Eltávolította az extrasötét funkció gyorsparancsát. A fényerő csökkentéséhez használja a fényerő-beállítási sávot." - - - - - - - + - + - + - + + "Kapcsolódás" + "Kisegítő lehetőségek" + "Segédprogramok" + "Adatvédelem" + "Alkalmazás által biztosított" + "Kijelző" + "Ismeretlen" diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index c017263b28103f2a950a90c55be9e28b3e76ab5b..db3553a785753bbab3064d253f9dbae9ee94733a 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -105,8 +105,7 @@ "Ավելացնել նշմանը" "Ներառել հղումը" "%1$s (%2$d)" - - + "Այլ պրոֆիլներից հնարավոր չէ հղումներ ավելացնել" "Էկրանի տեսագրում" "Էկրանի տեսագրության մշակում" "Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում" @@ -578,7 +577,7 @@ "Ծանուցումներ չկան" "Նոր ծանուցումներ չկան" "Ծանուցումների ձայնի իջեցումը միացված է" - "Սարքի և ծանուցումների ձայնն ավտոմատ իջեցվում է մինչև 2 րոպե, երբ շատ ծանուցումներ եք ստանում։" + "Սարքի և ծանուցումների ձայնն ավտոմատ իջեցվում է մինչև 2 րոպեով, երբ շատ ծանուցումներ եք ստանում։" "Անջատել" "Ապակողպեք՝ տեսնելու հին ծանուցումները" "Այս սարքը կառավարում է ձեր ծնողը" @@ -1180,6 +1179,10 @@ "%1$d%%" "Բարձրախոսներ և էկրաններ" "Առաջարկվող սարքեր" + + + + "Կանգնեցրեք ընդհանուր աշխատաշրջանը՝ մուլտիմեդիա բովանդակությունն այլ սարք տեղափոխելու համար" "Կանգնեցնել" "Ինչպես է աշխատում հեռարձակումը" @@ -1290,6 +1293,8 @@ "Ավելացնել" "Կառավարել" "Այս ծանուցումը հնարավոր չէ քաշել տրոհված էկրանի մեկ հատվածից մյուսը" + + "Wi‑Fi-ը հասանելի չէ" "Առաջնահերթության ռեժիմ" "Զարթուցիչը դրված է" @@ -1346,6 +1351,8 @@ "Անհատականացնել կողպէկրանը" "Ապակողպեք սարքը՝ կողպէկրանը կարգավորելու համար" "Wi-Fi ցանց հասանելի չէ" + + "Տեսախցիկն արգելափակված է" "Տեսախցիկն ու խոսափողը արգելափակված են" "Խոսափողն արգելափակված է" @@ -1401,7 +1408,8 @@ "Սովորեք օգտագործել հպահարթակի ժեստերը, ստեղնային դյուրանցումները և ավելին" "«Հետ» ժեստ" "Հիմնական էկրան անցնելու ժեստ" - "Գործողության ստեղն" + + "Պատրաստ է" "Հետ գնալ" "Հետ գնալու համար հպահարթակի վրա երեք մատով սահեցրեք ձախ կամ աջ։\n\nԻնչպես նաև կարող եք օգտագործել ստեղնային դյուրանցման գործողությունը + Esc։" @@ -1411,6 +1419,14 @@ "Հիմնական էկրան վերադառնալու համար երեք մատը էկրանի ներքևից սահեցրեք վերև։" "Գերազանց է" "Դուք սովորեցիք հիմնական էկրան անցնելու ժեստը։" + + + + + + + + "Գործողության ստեղն" "Բոլոր հավելվածներն օգտագործելու համար սեղմեք գործողության ստեղնը ստեղնաշարի վրա" "Շնորհավո՛ր" @@ -1434,22 +1450,19 @@ "Երեք մատը սահեցրեք վերև և սեղմած պահեք։ Հպեք՝ ավելի շատ ժեստերի ծանոթանալու համար։" "Օգտագործեք ձեր ստեղնաշարը՝ բոլոր հավելվածները դիտելու համար" "Ցանկացած ժամանակ սեղմեք գործողության ստեղնը։ Հպեք՝ ավելի շատ ժեստերի ծանոթանալու համար։" - "Հավելյալ խամրեցումն այժմ պայծառության գոտում է" - "Էկրանը հավելյալ խամրեցնելու համար բացեք կարգավորումները էկրանի վերևի մասից։\n\nԽորհուրդ ենք տալիս օգտագործել այս գործառույթը, երբ շուրջը մութ է։" - "Հեռացնել հավելյալ խամրեցման դյուրանցումը" - "Հավելյալ խամրեցման դյուրանցումը հեռացվեց։ Պայծառության մակարդակը նվազեցնելու համար օգտագործեք պայծառության գոտին։" - - - - - - - + - + - + - + + "Կապ" + "Հատուկ գործառույթներ" + "Կոմունալ ծառայություններ" + "Գաղտնիություն" + "Տրամադրվել են հավելվածների կողմից" + "Էկրան" + "Անհայտ" diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index a3ea9b26c49e23b5fef5d66174884f884e72eaca..97b0337cb080e67592b5e9a37bbc73e245b90e19 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -105,8 +105,7 @@ "Tambahkan ke catatan" "Sertakan link" "%1$s (%2$d)" - - + "Link tidak dapat ditambahkan dari profil lain" "Perekam Layar" "Memproses perekaman layar" "Notifikasi yang sedang berjalan untuk sesi rekaman layar" @@ -578,7 +577,7 @@ "Tidak ada notifikasi" "Tidak ada notifikasi baru" "Pengurangan suara dan getaran notifikasi aktif" - "Saat Anda menerima terlalu banyak notifikasi sekaligus, volume dan notifikasi perangkat akan otomatis dikurangi hingga selama 2 menit." + "Saat Anda menerima terlalu banyak notifikasi sekaligus, volume dan getaran perangkat akan otomatis dikurangi hingga selama 2 menit." "Nonaktifkan" "Buka kunci untuk melihat notifikasi lama" "Perangkat ini dikelola oleh orang tuamu" @@ -1180,6 +1179,10 @@ "%1$d%%" "Speaker & Layar" "Perangkat yang Disarankan" + + + + "Menghentikan sesi berbagi Anda untuk memindahkan media ke perangkat lain" "Berhenti" "Cara kerja siaran" @@ -1290,6 +1293,8 @@ "Tambahkan" "Kelola pengguna" "Notifikasi ini tidak mendukung fitur tarik ke layar terpisah" + + "Wi‑Fi tidak tersedia" "Mode prioritas" "Alarm disetel" @@ -1346,6 +1351,8 @@ "Sesuaikan layar kunci" "Buka kunci untuk menyesuaikan layar kunci" "Wi-Fi tidak tersedia" + + "Kamera diblokir" "Kamera dan mikrofon diblokir" "Mikrofon diblokir" @@ -1401,7 +1408,8 @@ "Pelajari gestur touchpad, pintasan keyboard, dan lainnya" "Gestur kembali" "Gestur layar utama" - "Tombol tindakan" + + "Selesai" "Kembali" "Untuk kembali, geser ke kiri atau kanan menggunakan tiga jari di touchpad.\n\nAnda juga dapat menggunakan pintasan keyboard Action + ECS untuk melakukannya." @@ -1411,6 +1419,14 @@ "Untuk membuka layar utama kapan saja, geser ke atas menggunakan tiga jari dari bawah layar Anda." "Bagus!" "Anda telah menyelesaikan gestur buka layar utama." + + + + + + + + "Tombol tindakan" "Untuk mengakses aplikasi, tekan tombol tindakan di keyboard." "Selamat!" @@ -1434,22 +1450,19 @@ "Geser ke atas dan tahan menggunakan tiga jari. Ketuk untuk mempelajari gestur lainnya." "Gunakan keyboard untuk melihat semua aplikasi" "Tekan tombol tindakan kapan saja. Ketuk untuk mempelajari gestur lainnya." - "Ekstra redup kini menjadi bagian dari panel kecerahan" - "Anda kini dapat membuat layar menjadi ekstra redup dengan menurunkan tingkat kecerahan lebih banyak lagi dari bagian atas layar.\n\nFitur ini berfungsi optimal saat Anda berada di tempat yang gelap." - "Hapus pintasan ekstra redup" - "Pintasan ekstra redup dihapus. Untuk menurunkan kecerahan, gunakan panel kecerahan biasa." - - - - - - - + - + - + - + + "Konektivitas" + "Aksesibilitas" + "Utilitas" + "Privasi" + "Disediakan oleh aplikasi" + "Tampilan" + "Tidak diketahui" diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 2468bcc9f2c1c0699e7130d8323431c479c968f6..7df9b2a9a181e6962cdff18cf30eff9826df00de 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Hátalarar og skjáir" "Tillögur að tækjum" + + + + "Stöðvaðu sameiginlega lotu til að flytja efni yfir í annað tæki" "Stöðva" "Svona virkar útsending" @@ -1289,6 +1293,7 @@ "Bæta við" "Stjórna notendum" "Þessi tilkynning styður ekki að draga yfir á skiptan skjá." + "Staðsetning virk" "WiFi ekki tiltækt" "Forgangsstilling" "Vekjari stilltur" @@ -1345,6 +1350,7 @@ "Sérsníða lásskjá" "Taktu úr lás til að sérsníða lásskjá" "Wi-Fi er ekki til staðar" + "Staðsetning virk" "Lokað fyrir myndavél" "Lokað fyrir myndavél og hljóðnema" "Lokað fyrir hljóðnema" @@ -1400,7 +1406,7 @@ "Kynntu þér bendingar á snertifleti, flýtilykla og fleira" "Bending til að fara til baka" "Bending til að fara á upphafsskjá" - "Aðgerðalykill" + "Sjá nýleg forrit" "Lokið" "Til baka" "Strjúktu til vinstri eða hægri með þremur fingrum hvar sem er á snertifletinum til að fara til baka.\n\nÞú getur einnig notað flýtileiðaraðgerðina + ESC til að gera þetta." @@ -1410,6 +1416,10 @@ "Strjúktu upp frá neðri brún skjásins með þremur fingrum til að opna heimaskjáinn." "Flott!" "Þú laukst við að kynna þér bendinguna „heim“." + "Sjá nýleg forrit" + "Strjúktu upp og haltu þremur fingrum inni á snertifletinum." + "Vel gert!" + "Þú framkvæmdir bendinguna til að sjá nýleg forrit." "Aðgerðalykill" "Ýttu á aðgerðalykilinn á lyklaborðinu til að opna forritin þín." "Til hamingju!" @@ -1433,22 +1443,15 @@ "Strjúktu upp og haltu með þremur fingrum. Ýttu til að læra fleiri bendingar." "Notaðu lyklaborðið til að sjá öll forrit" "Ýttu hvenær sem er á aðgerðalykilinn. Ýttu til að læra fleiri bendingar." - "Nú er stillingin „mjög dökkt“ hluti af birtustigsstikunni" - "Nú geturðu gert skjáinn mjög dökkan með því að lækka birtustigið enn frekar efst á skjánum.\n\nÞetta virkar best þegar umhverfi þitt er mjög dimmt." - "Fjarlægja flýtilykil á mjög dökka stillingu" - "Flýtilykill á mjög dökka stillingu fjarlægður. Notaðu hefðbundnu birtustigsstikuna til að lækka birtustigið." - - - - - - - - - - - - - - + "Nú er stillingin „Mjög dökkt“ hluti af birtusleðanum" + "Nú geturðu gert skjáinn mjög dökkan með því að lækka birtustigið enn frekar.\n\nÞar sem þessi eiginleiki er nú hluti af birtusleðanum verða flýtilyklar fyrir mjög dökka stillingu fjarlægðir." + "Fjarlægja flýtilykla fyrir mjög dökka stillingu" + "Flýtilyklar fyrir mjög dökka stillingu verða fjarlægðir" + "Tengigeta" + "Aðgengileiki" + "Aukabúnaður" + "Persónuvernd" + "Frá forritum" + "Skjár" + "Óþekkt" diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 5ea4486f35afe1bc999e1cd399ecf8b5b52c9411..f1bbc0a732ff2a1282ad80fa8adac9aa9c59ae40 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -105,8 +105,7 @@ "Aggiungi alla nota" "Includi link" "%1$s (%2$d)" - - + "Impossibile aggiungere link da altri profili" "Registrazione dello schermo" "Elaborazione registrazione…" "Notifica costante per una sessione di registrazione dello schermo" @@ -578,7 +577,7 @@ "Nessuna notifica" "Nessuna nuova notifica" "Attenuazione delle notifiche attivata" - "Volume e avvisi sono ridotti automaticamente fino a 2 minuti quando ricevi troppe notifiche insieme." + "Volume e avvisi vengono ridotti automaticamente per un massimo di 2 minuti quando ricevi troppe notifiche contemporaneamente." "Disattiva" "Sblocca per vedere le notifiche meno recenti" "Questo dispositivo è gestito dai tuoi genitori" @@ -1180,6 +1179,10 @@ "%1$d%%" "Speaker e display" "Dispositivi consigliati" + + + + "Interrompi la sessione condivisa per spostare i contenuti multimediali su un altro dispositivo" "Interrompi" "Come funziona la trasmissione" @@ -1290,6 +1293,8 @@ "Aggiungi" "Gestisci utenti" "Non è possibile trascinare questa notifica tra le due parti dello schermo diviso" + + "Wi‑Fi non disponibile" "Modalità Priorità" "Sveglia impostata" @@ -1346,6 +1351,8 @@ "Personalizza schermata di blocco" "Sblocca per personalizzare la schermata di blocco" "Wi-Fi non disponibile" + + "Videocamera bloccata" "Videocamera e microfono bloccati" "Microfono bloccato" @@ -1393,21 +1400,16 @@ "Icona Comprimi" "Icona Espandi" "oppure" - - - - - - - - - - - - + "Naviga usando la tastiera" + "Informazioni sulle scorciatoie da tastiera" + "Naviga usando il touchpad" + "Impara i gesti con il touchpad" + "Naviga usando la tastiera e il touchpad" + "Scopri gesti con il touchpad, scorciatoie da tastiera e altro ancora" "Gesto Indietro" "Gesto Home" - "Tasto azione" + + "Fine" "Indietro" "Per tornare indietro, scorri verso sinistra o verso destra utilizzando tre dita in un punto qualsiasi del touchpad.\n\nPuoi usare anche la scorciatoia da tastiera Action + Esc per farlo." @@ -1417,6 +1419,14 @@ "Per andare alla schermata Home, scorri verso l\'alto con tre dita dalla parte inferiore dello schermo." "Bene!" "Hai completato il gesto Vai alla schermata Home." + + + + + + + + "Tasto azione" "Per accedere alle tue app, premi il tasto azione sulla tastiera." "Complimenti!" @@ -1440,22 +1450,19 @@ "Scorri verso l\'alto e tieni premuto con tre dita. Tocca per scoprire altri gesti." "Usa la tastiera per visualizzare tutte le app" "Premi il tasto azione in qualsiasi momento. Tocca per scoprire altri gesti." - "Ora l\'attenuazione extra è nella barra della luminosità" - "Ora puoi usare l\'attenuazione extra per lo schermo abbassando il livello di luminosità ancora di più dalla parte superiore della schermata.\n\nQuesta funzionalità opera in modo ottimale quando ti trovi in un ambiente buio." - "Rimuovi scorciatoia attenuazione extra" - "Scorciatoia attenuazione extra rimossa. Per diminuire la luminosità, usa la normale barra della luminosità." - - - - - - - + - + - + - + + "Connettività" + "Accessibilità" + "Utilità" + "Privacy" + "Forniti dalle app" + "Display" + "Sconosciuti" diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 5fd5ca6fcaf3b60c55372b2c8c87da777e2238bc..2cf4f37f926b39a09247b6e42c6761ad34cf1e52 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -105,8 +105,7 @@ "הוספה לפתק" "הכנסת הקישור" "%1$s (%2$d)⁠" - - + "לא ניתן להוסיף קישורים מפרופילים אחרים" "מקליט המסך" "מתבצע עיבוד של הקלטת מסך" "התראה מתמשכת לסשן הקלטת מסך" @@ -441,7 +440,7 @@ "מצב מופעל" "פועל • %1$s" "מצב מושבת" - "הגדרה" + "להגדרה" "שינוי ב\'הגדרות\'" "{count,plural, =0{אין מצבים פעילים}=1{מצב פעיל אחד ({mode})}one{‫# מצבים פעילים}two{‫# מצבים פעילים}other{‫# מצבים פעילים}}" "כדי לא להפריע לך, המכשיר לא ירטוט ולא ישמיע שום צליל, חוץ מהתראות, תזכורות, אירועים ושיחות ממתקשרים מסוימים לבחירתך. המצב הזה לא ישפיע על צלילים שהם חלק מתוכן שבחרת להפעיל, כמו מוזיקה, סרטונים ומשחקים." @@ -577,7 +576,7 @@ "כן, אפשר להתחיל" "אין התראות" "אין התראות חדשות" - "הפוגת ההתראות מופעלת" + "הפוגת התראות מופעלת" "עוצמת הקול וההתראות במכשיר מופחתות אוטומטית למשך עד 2 דקות כשמתקבלות יותר מדי התראות בבת אחת." "השבתה" "יש לבטל את הנעילה כדי לראות התראות ישנות" @@ -1180,6 +1179,10 @@ "‎%1$d%%‎‎" "רמקולים ומסכים" "הצעות למכשירים" + + + + "עצירת הסשן המשותף כדי להעביר מדיה למכשיר אחר" "עצירה" "הסבר על שידורים" @@ -1290,6 +1293,8 @@ "הוספה" "ניהול משתמשים" "ההתראה הזו לא תומכת בגרירה למסך מפוצל" + + "‏Wi‑Fi לא זמין" "מצב עדיפות" "ההתראה מוגדרת" @@ -1346,6 +1351,8 @@ "התאמה אישית של מסך הנעילה" "כדי להתאים אישית את מסך הנעילה, יש לבטל את הנעילה" "‏ה-Wi-Fi לא זמין" + + "המצלמה חסומה" "המצלמה והמיקרופון חסומים" "המיקרופון חסום" @@ -1401,7 +1408,8 @@ "מידע על התנועות בלוח המגע, מקשי קיצור ועוד" "תנועת חזרה" "תנועת חזרה למסך הבית" - "מקש הפעולה" + + "סיום" "חזרה" "‏כדי לחזור אחורה, מחליקים שמאלה או ימינה עם שלוש אצבעות בכל מקום על לוח המגע.\n\nאפשר לבצע את הפעולה הזו גם באמצעות קיצור הדרך לפעולה + מקש ESC." @@ -1411,6 +1419,14 @@ "כדי לעבור למסך הבית בכל שלב, צריך להחליק למעלה עם שלוש אצבעות מהחלק התחתון של המסך." "איזה יופי!" "השלמת את תנועת המעבר למסך הבית." + + + + + + + + "מקש הפעולה" "כדי לגשת לאפליקציות, מקישים על מקש הפעולה במקלדת." "כל הכבוד!" @@ -1434,22 +1450,19 @@ "מחליקים למעלה ולוחצים לחיצה ארוכה עם שלוש אצבעות. ניתן להקיש כדי לקבל מידע נוסף על התנועות." "איך להשתמש במקלדת כדי לראות את כל האפליקציות" "בכל שלב אפשר ללחוץ על מקש הפעולה. ניתן להקיש כדי לקבל מידע נוסף על התנועות." - "התכונה \'מעומעם במיוחד\' נוספה לסרגל הבהירות" - "עכשיו אפשר להפוך את המסך למעומעם במיוחד באמצעות הפחתה נוספת של רמת הבהירות דרך החלק העליון במסך.\n\nהפעולה הזו עובדת הכי טוב בסביבה חשוכה." - "הסרה של קיצור הדרך לתכונה \'מעומעם במיוחד\'" - "קיצור הדרך לתכונה \'מעומעם במיוחד\' הוסר. כדי להפחית את הבהירות, אפשר להשתמש בסרגל הבהירות הרגיל." - - - - - - - + - + - + - + + "קישוריות" + "נגישות" + "כלי תחזוקה" + "פרטיות" + "מסופקים על ידי אפליקציות" + "מסך" + "לא ידוע" diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 7897f4826155d1ed6377a2ba5aff99a240b8d4a5..56491fda20bddae668d2bfc6aab1969b43538534 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -577,7 +577,7 @@ "通知はありません" "新しい通知はありません" "通知のクールダウンが ON になっています" - "一度に多くの通知が届いたときに、最大 2 分間自動的にデバイスの音量が小さくなりアラートも減ります。" + "一度に多くの通知が届いた場合に、最長 2 分間自動的にデバイスの音量が小さくなりアラートも減ります。" "OFF にする" "ロック解除して以前の通知を表示" "このデバイスは保護者によって管理されています" @@ -1179,6 +1179,10 @@ "%1$d%%" "スピーカーとディスプレイ" "デバイスの候補" + + + + "メディアを他のデバイスに移動する共有中のセッションを停止します。" "停止" "ブロードキャストの仕組み" @@ -1289,6 +1293,8 @@ "追加" "ユーザーの管理" "この通知は、分割画面へのドラッグをサポートしていません" + + "Wi-Fi を利用できません" "優先順位モード" "アラームを設定しました" @@ -1345,6 +1351,8 @@ "ロック画面のカスタマイズ" "ロック画面をカスタマイズするにはロックを解除してください" "Wi-Fi は利用できません" + + "カメラはブロックされています" "カメラとマイクはブロックされています" "マイクはブロックされています" @@ -1400,7 +1408,8 @@ "タッチパッド操作やキーボード ショートカットなどの詳細" "「戻る」ジェスチャー" "「ホーム」ジェスチャー" - "アクションキー" + + "完了" "戻る" "戻るには、3 本の指でタッチパッドを左右にスワイプします。\n\nキーボード ショートカットのアクション + ESC キーを使用して、この操作を行うこともできます。" @@ -1410,6 +1419,14 @@ "3 本の指で画面を下から上にスワイプすると、ホーム画面にいつでも移動できます。" "お疲れさまでした。" "「ホームに移動」操作を学習しました。" + + + + + + + + "アクションキー" "アプリにアクセスするには、キーボードのアクションキーを押します。" "お疲れさまでした。" @@ -1433,22 +1450,19 @@ "3 本の指で上にスワイプして長押しします。ジェスチャーの詳細を確認するにはタップしてください。" "キーボードを使用して、すべてのアプリを表示する" "アクションキーを押せばいつでも機能します。ジェスチャーの詳細を確認するにはタップしてください。" - "「さらに輝度を下げる」機能が明るさのバーの追加されました" - "画面の上部で明るさを大幅に低く設定することで、画面の輝度をさらに下げられるようになりました。\n\nこの設定は暗い場所での操作に最適です。" - "「さらに輝度を下げる」のショートカットを削除する" - "「さらに輝度を下げる」のショートカットを削除しました。明るさを下げるには、通常の明るさのバーを使用してください。" - - - - - - - + - + - + - + + "接続" + "ユーザー補助" + "ユーティリティ" + "プライバシー" + "アプリから提供" + "ディスプレイ" + "不明" diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 5550a63ad9a2bf47b5ec421e18c748a114daad70..d3583fa536ea382866c46a4ced941fd0a18de3dc 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "დინამიკები და დისპლეები" "შემოთავაზებული მოწყობილობები" + + + + "შეწყვიტეთ გაზიარებული სესია, რათა მულტიმედია სხვა მოწყობილობაზე გადაიტანოთ" "შეწყვეტა" "ტრანსლირების მუშაობის პრინციპი" @@ -1289,6 +1293,8 @@ "დამატება" "მართვა" "ამ შეტყობინების გადათრევა გაყოფილ ეკრანებს შორის არ არის მხარდაჭერილი." + + "Wi‑Fi მიუწვდომელია" "პრიორიტეტული რეჟიმი" "მაღვიძარა დაყენებულია" @@ -1345,6 +1351,8 @@ "ჩაკეთილი ეკრანის მორგება" "ჩაკეტილი ეკრანის მოსარგებად გაბლოკეთ" "Wi-Fi მიუწვდომელია" + + "კამერა დაბლოკილია" "კამერა და მიკროფონი დაბლოკილია" "მიკროფონი დაბლოკილია" @@ -1400,7 +1408,8 @@ "სენსორული პანელის ჟესტების, კლავიატურის მალსახმობების და სხვა ფუნქციების სწავლა" "უკან დაბრუნების ჟესტი" "მთავარ ეკრანზე გადასვლის ჟესტი" - "მოქმედების კლავიში" + + "მზადაა" "უკან დაბრუნება" "უკან დასაბრუნებლად სენსორულ პანელზე გადაფურცლეთ მარცხნივ ან მარჯვნივ სამი თითის გამოყენებით ნებისმიერ ადგილას.\n\nამისთვის თქვენ ასევე შეგიძლიათ გამოიყენოთ კლავიატურის მალსახმობის მოქმედება + ESC." @@ -1410,6 +1419,14 @@ "თქვენს მთავარ ეკრანზე ნებისმიერ დროს გადასასვლელად გადაფურცლეთ ეკრანის ქვემოდან ზემოთ სამი თითით." "მშვენიერია!" "თქვენ შეასრულეთ მთავარ ეკრანზე დაბრუნების ჟესტი." + + + + + + + + "მოქმედების კლავიში" "აპებზე წვდომისთვის დააჭირეთ მოქმედების კლავიშს თქვენს კლავიატურაზე." "გილოცავთ!" @@ -1433,22 +1450,19 @@ "სამი თითით გადაფურცლეთ ზემოთ და მოიცადეთ. შეეხეთ მეტი ჟესტის შესასწავლად." "ყველა აპის სანახავად გამოიყენეთ თქვენი კლავიატურა" "ნებისმიერ დროს დააჭირეთ მოქმედების კლავიშს. შეეხეთ მეტი ჟესტის შესასწავლად." - "დამატებითი დაბინდვის ფუქნცია ახლა განთავსებულია სიკაშკაშის პანელზე" - "ახლა თქვენ შეგიძლიათ დამატებით დაბინდოთ ეკრანი მის ზედა ნაწილში სიკაშკაშის დონის კიდევ უფრო შემცირების გზით.\n\nეს ყველაზე უკეთ ბნელ გარემოში ყოფნისას მუშაობს." - "დამატებითი დაბინდვის მალსახმობის ამოშლა" - "დამატებითი დაბინდვის მალსახმობი ამოშლილია. სიკაშკაშის შესამცირებლად გამოიყენეთ სიკაშკაშის ჩვეულებრივი პანელი." - - - - - - - + - + - + - + + "კავშირი" + "მარტივი წვდომა" + "ხელსაწყოები" + "კონფიდენციალურობა" + "მოწოდებულია აპების მიერ" + "ეკრანი" + "უცნობი" diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index d8b454218ac2e31ad05ebacffb1df83355333d20..3916063923661e4d4c943f2bce40dec44b3f0ca1 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -105,8 +105,7 @@ "Ескертпеге қосу" "Сілтеме қосу" "%1$s (%2$d)" - - + "Сілтемелерді басқа профильдерден қосу мүмкін емес." "Экран жазғыш" "Экран жазғыш бейнесін өңдеу" "Экранды бейнеге жазудың ағымдағы хабарландыруы" @@ -1180,6 +1179,10 @@ "%1$d%%" "Динамиктер мен дисплейлер" "Ұсынылған құрылғылар" + + + + "Мультимедиа файлын басқа құрылғыға жылжыту үшін ортақ сеансты тоқтатыңыз." "Тоқтату" "Тарату қалай жүзеге асады" @@ -1290,6 +1293,8 @@ "Қосу" "Параметрлер" "Бұл хабарландыруды бөлінген экранға сүйреп апару мүмкін емес." + + "Wi‑Fi қолжетімсіз" "Басымдық режимі" "Оятқыш орнатылды" @@ -1346,6 +1351,8 @@ "Құлып экранын бейімдеу" "Құлып экранын бейімдеу үшін құлыпты ашыңыз" "Wi-Fi қолжетімсіз." + + "Камера блокталған." "Камера мен микрофон блокталған." "Микрофон блокталған." @@ -1401,7 +1408,8 @@ "Сенсорлық тақта қимылдарын, перне тіркесімдерін және т.б. үйреніңіз." "Артқа қайтару қимылы" "Негізгі бетке қайтару қимылы" - "Әрекет пернесі" + + "Дайын" "Артқа" "Артқа қайту үшін сенсорлық тақтаның кез келген жерін үш саусақпен солға не оңға сырғытыңыз.\n\nСондай-ақ Action + ESC перне тіркесімін пайдалануға болады." @@ -1411,6 +1419,14 @@ "Негізгі экранға кез келген уақытта өту үшін экранның төменгі жағынан жоғары қарай үш саусағыңызбен сырғытыңыз." "Жақсы нәтиже!" "Негізгі экранға қайту қимылын аяқтадыңыз." + + + + + + + + "Әрекет пернесі" "Қолданбаларыңызға кіру үшін пернетақтадағы әрекет пернесін басыңыз." "Құттықтаймыз!" @@ -1434,22 +1450,19 @@ "Үш саусақпен жоғары сырғытып, басып тұрыңыз. Басқа қимылдарды үйрену үшін түртіңіз." "Барлық қолданбаны көру үшін пернетақтаны қолданыңыз" "Әрекет пернесін кез келген уақытта баса аласыз. Басқа қимылдарды үйрену үшін түртіңіз." - "Экранды қарайту функциясын енді жарықтық панелінің бөлшегі болады" - "Енді экранның жоғарғы бөлігінде жарықтық деңгейін түсіру арқылы экранды одан сайын қарайтуға болады.\n\nБұл мүмкіндіктің артықшылығын қараңғы жерде көруге болады." - "Экранды қарайту жылдам пәрменін өшіру" - "Экранды қарайту жылдам пәрмені өшірілді. Жарықтықты азайту үшін әдеттегі жарықтық панелін пайдаланыңыз." - - - - - - - + - + - + - + + "Қосылу мүмкіндігі" + "Арнайы мүмкіндіктер" + "Утилиталар" + "Құпиялық" + "Қолданбалар ұсынған" + "Дисплей" + "Белгісіз" diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index f2fc313a4bb243a6c01b1b633614767a050ec0ff..c72ee714ad9f093729f8c88edc01ac56613e54c9 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "ឧបករណ៍បំពងសំឡេង និងផ្ទាំងអេក្រង់" "ឧបករណ៍​ដែលបានណែនាំ" + + + + "បញ្ឈប់វគ្គដែលអ្នក​បានចែករំលែក ដើម្បីផ្លាស់ទីមេឌៀ​ទៅឧបករណ៍​ផ្សេងទៀត" "បញ្ឈប់" "របៀបដែលការផ្សាយដំណើរការ" @@ -1289,6 +1293,7 @@ "បញ្ចូល" "គ្រប់គ្រង​អ្នក​ប្រើប្រាស់" "ការជូនដំណឹងនេះមិនអាចឱ្យអូសដើម្បីបំបែកអេក្រង់បានទេ" + "ទីតាំងដែលសកម្ម" "Wi‑Fi ត្រូវបានបិទ" "មុខងារ​អាទិភាព" "រូបកំណត់​ម៉ោងរោទ៍" @@ -1345,6 +1350,7 @@ "ប្ដូរអេក្រង់ចាក់សោ​តាមបំណង" "ដោះសោ ដើម្បីប្ដូរអេក្រង់ចាក់សោតាមបំណង" "មិនមាន Wi-Fi ទេ" + "ទីតាំងដែលសកម្ម" "បាន​ទប់ស្កាត់​កាមេរ៉ា" "បានទប់ស្កាត់​កាមេរ៉ា និង​មីក្រូហ្វូន" "បាន​ទប់ស្កាត់​មីក្រូហ្វូន" @@ -1400,7 +1406,7 @@ "ស្វែងយល់អំពីចលនាផ្ទាំងប៉ះ ផ្លូវកាត់​ក្ដារ​ចុច និងអ្វីៗជាច្រើនទៀត" "ចលនាថយក្រោយ" "ចលនាទៅទំព័រដើម" - "គ្រាប់ចុចសកម្មភាព" + "មើលកម្មវិធីថ្មីៗ" "រួចរាល់" "ថយ​ក្រោយ" "ដើម្បីថយក្រោយ សូមអូសទៅឆ្វេង ឬស្ដាំដោយប្រើ​​ម្រាមដៃបីនៅត្រង់ណាក៏បានលើផ្ទាំងប៉ះ។\n\nអ្នកក៏អាចប្រើសកម្មភាពផ្លូវកាត់ក្ដារចុច + ESC សម្រាប់ការធ្វើបែបនេះ។" @@ -1410,6 +1416,10 @@ "ដើម្បីចូលទៅអេក្រង់ដើមរបស់អ្នកនៅពេលណាក៏បាន សូមអូសឡើងលើដោយប្រើម្រាមដៃបីពីផ្នែកខាងក្រោមនៃអេក្រង់របស់អ្នក។" "ល្អ!" "អ្នក​បានបញ្ចប់​ចលនា​ចូលទៅកាន់​ទំព័រដើម​ហើយ។" + "មើលកម្មវិធីថ្មីៗ" + "អូសឡើងលើ ហើយសង្កត់ឱ្យជាប់ដោយប្រើម្រាមដៃបីលើផ្ទាំងប៉ះរបស់អ្នក។" + "ធ្វើបានល្អ!" + "អ្នកបានបញ្ចប់ការមើលចលនាកម្មវិធីថ្មីៗ។" "គ្រាប់ចុចសកម្មភាព" "ដើម្បីចូលប្រើប្រាស់កម្មវិធីរបស់អ្នក សូមចុចគ្រាប់ចុចសកម្មភាពនៅលើក្ដារចុចរបស់អ្នក។" "សូមអបអរសាទរ!" @@ -1433,22 +1443,15 @@ "អូសឡើងលើ ហើយសង្កត់ឱ្យជាប់ដោយប្រើម្រាមដៃបី។ ចុច ដើម្បីស្វែងយល់បន្ថែមអំពីចលនា។" "ប្រើក្ដារចុចរបស់អ្នក ដើម្បីមើលកម្មវិធីទាំងអស់" "ចុចគ្រាប់ចុចសកម្មភាពនៅពេលណាក៏បាន។ ចុច ដើម្បីស្វែងយល់បន្ថែមអំពីចលនា។" - "ឥឡូវនេះ មុខងារងងឹតខ្លាំងក្លាយជាផ្នែកមួយនៃរបារពន្លឺ" - "ឥឡូវនេះ អ្នកអាចធ្វើឱ្យអេក្រង់ងងឹតខ្លាំងបានដោយបន្ថយកម្រិតពន្លឺបន្ថែមទៀតដោយចូលទៅកាន់ផ្នែកខាងលើនៃអេក្រង់របស់អ្នក។\n\nការធ្វើបែបនេះទទួលបានលទ្ធផលប្រសើរបំផុត ពេលអ្នកស្ថិតនៅកន្លែងងងឹត។" - "ដកផ្លូវ​កាត់មុខងារងងឹតខ្លាំងចេញ" - "ផ្លូវ​កាត់មុខងារងងឹតខ្លាំងត្រូវបានដកចេញ។ ដើម្បីបន្ថយពន្លឺរបស់អ្នក សូមប្រើរបារពន្លឺធម្មតា។" - - - - - - - - - - - - - - + "ឥឡូវនេះ មុខងារងងឹតខ្លាំងក្លាយជាផ្នែកមួយនៃគ្រាប់រំកិលពន្លឺ" + "ឥឡូវនេះ អ្នកអាចធ្វើឱ្យអេក្រង់ងងឹតខ្លាំងបានដោយបន្ថយកម្រិតពន្លឺបន្ថែមទៀត។\n\nដោយសារឥឡូវមុខងារនេះក្លាយជាផ្នែកមួយនៃគ្រាប់រំកិលពន្លឺ ផ្លូវ​កាត់មុខងារងងឹតខ្លាំងកំពុងត្រូវបានដកចេញ។" + "ដកផ្លូវ​កាត់មុខងារងងឹតខ្លាំងចេញ" + "ផ្លូវ​កាត់មុខងារងងឹតខ្លាំងត្រូវបានដកចេញ" + "ការតភ្ជាប់" + "ភាពងាយស្រួល" + "កម្មវិធី​សម្រួលដំណើរការ" + "ឯកជនភាព" + "ផ្ដល់ជូនដោយកម្មវិធី" + "ផ្ទាំងបង្ហាញ" + "មិនស្គាល់" diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 6f6333299129719c59a52f934bf86833ba92cc9c..f1873c61f11f61212f818d8aa28d5c145bf02d43 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -577,7 +577,7 @@ "ಯಾವುದೇ ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ" "ಯಾವುದೇ ಹೊಸ ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ" "ನೋಟಿಫಿಕೇಶನ್ ಕೂಲ್‌ಡೌನ್ ಆನ್ ಆಗಿದೆ" - "ನೀವು ಏಕಕಾಲದಲ್ಲಿ ತೀರಾ ಹೆಚ್ಚು ನೋಟಿಫಿಕೇಶನ್‌ಗಳನ್ನು ಪಡೆದಾಗ 2 ನಿಮಿಷಗಳವರೆಗೆ ನಿಮ್ಮ ಸಾಧನದ ವಾಲ್ಯೂಮ್ ಮತ್ತು ಅಲರ್ಟ್‌ಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕಡಿಮೆ ಮಾಡಲಾಗುತ್ತದೆ." + "ನೀವು ಏಕಕಾಲದಲ್ಲಿ ತೀರಾ ಹೆಚ್ಚು ನೋಟಿಫಿಕೇಶನ್‌‍‍ಗಳನ್ನು ಪಡೆದಾಗ 2 ನಿಮಿಷಗಳವರೆಗೆ ನಿಮ್ಮ ಸಾಧನದ ವಾಲ್ಯೂಮ್ ಮತ್ತು ಅಲರ್ಟ್‌‍‍ಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕಡಿಮೆ ಮಾಡಲಾಗುತ್ತದೆ." "ಆಫ್ ಮಾಡಿ" "ಹಳೆಯ ಅಧಿಸೂಚನೆಗಳನ್ನು ನೋಡಲು ಅನ್‌ಲಾಕ್ ಮಾಡಿ" "ಈ ಸಾಧನವನ್ನು ನಿಮ್ಮ ಪೋಷಕರು ನಿರ್ವಹಿಸುತ್ತಿದ್ದಾರೆ" @@ -1179,6 +1179,10 @@ "%1$d%%" "ಸ್ಪೀಕರ್‌ಗಳು ಮತ್ತು ಡಿಸ್‌ಪ್ಲೇಗಳು" "ಸೂಚಿಸಿದ ಸಾಧನಗಳು" + + + + "ಮೀಡಿಯಾವನ್ನು ಮತ್ತೊಂದು ಸಾಧನಕ್ಕೆ ಸರಿಸಲು ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸೆಶನ್ ಅನ್ನು ನಿಲ್ಲಿಸಿ" "ನಿಲ್ಲಿಸಿ" "ಪ್ರಸಾರವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ" @@ -1289,6 +1293,7 @@ "ಸೇರಿಸಿ" "ಬಳಕೆದಾರರನ್ನು ನಿರ್ವಹಿಸಿ" "ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಡ್ರ್ಯಾಗ್ ಮಾಡುವುದನ್ನು ಈ ನೋಟಿಫಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ" + "ಸ್ಥಳ ಸಕ್ರಿಯವಾಗಿದೆ" "ವೈ-ಫೈ ಲಭ್ಯವಿಲ್ಲ" "ಆದ್ಯತೆ ಮೋಡ್" "ಅಲಾರಾಂ ಹೊಂದಿಸಲಾಗಿದೆ" @@ -1345,6 +1350,7 @@ "ಲಾಕ್ ಸ್ಕ್ರೀನ್ ಕಸ್ಟಮೈಸ್ ಮಾಡಿ" "ಲಾಕ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ಅನ್‌ಲಾಕ್ ಮಾಡಿ" "ವೈ-ಫೈ ಲಭ್ಯವಿಲ್ಲ" + "ಸ್ಥಳ ಸಕ್ರಿಯವಾಗಿದೆ" "ಕ್ಯಾಮರಾವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" "ಕ್ಯಾಮರಾ ಮತ್ತು ಮೈಕ್ರೊಫೋನ್‌ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" "ಮೈಕ್ರೋಫೋನ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" @@ -1400,7 +1406,7 @@ "ಟಚ್‌ಪ್ಯಾಡ್ ಗೆಸ್ಚರ್‌ಗಳು, ಕೀಬೋರ್ಡ್‌ಗಳ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ತಿಳಿಯಿರಿ" "ಹಿಂಬದಿ ಗೆಸ್ಚರ್" "ಹೋಮ್ ಗೆಸ್ಚರ್" - "ಆ್ಯಕ್ಷನ್‌ ಕೀ" + "ಇತ್ತೀಚಿನ ಆ್ಯಪ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ" "ಮುಗಿದಿದೆ" "ಹಿಂತಿರುಗಿ" "ಹಿಂತಿರುಗಲು, ಟಚ್‌ಪ್ಯಾಡ್‌ನಲ್ಲಿ ಎಲ್ಲಿಯಾದರೂ ಮೂರು ಬೆರಳುಗಳನ್ನು ಬಳಸಿ ಎಡ ಅಥವಾ ಬಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ.\n\nಇದಕ್ಕಾಗಿ ನೀವು ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್ Action + ESC ಅನ್ನು ಸಹ ಬಳಸಬಹುದು." @@ -1410,6 +1416,10 @@ "ಯಾವುದೇ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಹೋಗಲು, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಕೆಳಗಿನಿಂದ ಮೂರು ಬೆರಳುಗಳಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ." "ಭೇಷ್!" "ನೀವು ಗೋ ಹೋಮ್ ಗೆಸ್ಚರ್ ಅನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ್ದೀರಿ." + "ಇತ್ತೀಚಿನ ಆ್ಯಪ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ" + "ನಿಮ್ಮ ಟಚ್‌ಪ್ಯಾಡ್‌ನಲ್ಲಿ ಮೂರು ಬೆರಳುಗಳನ್ನು ಬಳಸಿ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ." + "ಭೇಷ್‌!" + "ನೀವು ಇತ್ತೀಚಿನ ಆ್ಯಪ್‌ಗಳ ಗೆಸ್ಚರ್‌ ವೀಕ್ಷಣೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ್ದೀರಿ." "ಆ್ಯಕ್ಷನ್‌ ಕೀ" "ನಿಮ್ಮ ಆ್ಯಪ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು, ನಿಮ್ಮ ಕೀಬೋರ್ಡ್‌ನಲ್ಲಿರುವ ಆ್ಯಕ್ಷನ್‌ ಕೀಯನ್ನು ಒತ್ತಿರಿ." "ಅಭಿನಂದನೆಗಳು!" @@ -1433,22 +1443,15 @@ "ಮೂರು ಬೆರಳುಗಳಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಹಾಗೂ ಹೋಲ್ಡ್ ಮಾಡಿ. ಇನ್ನಷ್ಟು ಗೆಸ್ಚರ್‌ಗಳನ್ನು ತಿಳಿಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ." "ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಅನ್ನು ಬಳಸಿ" "ಯಾವಾಗ ಬೇಕಾದರೂ ಆ್ಯಕ್ಷನ್‌ ಕೀಯನ್ನು ಒತ್ತಿರಿ. ಇನ್ನಷ್ಟು ಗೆಸ್ಚರ್‌ಗಳನ್ನು ತಿಳಿಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ." - "ಇನ್ನಷ್ಟು ಮಬ್ಬು ಈಗ ಬ್ರೈಟ್‌ನೆಸ್ ಬಾರ್‌ನ ಭಾಗವಾಗಿದೆ" - "ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲ್ಭಾಗದಿಂದ ಬ್ರೈಟ್‌ನೆಸ್ ಮಟ್ಟವನ್ನು ಇನ್ನಷ್ಟು ಕಡಿಮೆ ಮಾಡುವ ಮೂಲಕ ನೀವು ಈಗ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಇನ್ನಷ್ಟು ಮಬ್ಬುಗೊಳಿಸಬಹುದು.\n\nನೀವು ಕತ್ತಲೆಯ ವಾತಾವರಣದಲ್ಲಿರುವಾಗ ಇದು ಉತ್ತಮವಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ." - "ಇನ್ನಷ್ಟು ಮಬ್ಬು ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಿ" - "ಇನ್ನಷ್ಟು ಮಬ್ಬು ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ. ನಿಮ್ಮ ಬ್ರೈಟ್‌ನೆಸ್ ಅನ್ನು ಕಡಿಮೆ ಮಾಡಲು, ಸಾಮಾನ್ಯ ಬ್ರೈಟ್‌ನೆಸ್ ಬಾರ್ ಬಳಸಿ." - - - - - - - - - - - - - - + "ಇನ್ನಷ್ಟು ಮಬ್ಬು ಈಗ ಬ್ರೈಟ್‌ನೆಸ್‌ ಸ್ಲೈಡರ್‌ನ ಭಾಗವಾಗಿದೆ" + "ನೀವು ಈಗ ಬ್ರೈಟ್‌ನೆಸ್‌ನ ಮಟ್ಟವನ್ನು ಇನ್ನಷ್ಟು ಕಡಿಮೆ ಮಾಡುವ ಮೂಲಕ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ಇನ್ನಷ್ಟು ಮಬ್ಬುಗೊಳಿಸಬಹುದು.\n\n ಈ ಫೀಚರ್‌ ಈಗ ಬ್ರೈಟ್‌ನೆಸ್ ಸ್ಲೈಡರ್‌ನ ಭಾಗವಾಗಿರುವುದರಿಂದ, ಇನ್ನಷ್ಟು ಮಬ್ಬಾದ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗುತ್ತಿದೆ." + "ಇನ್ನಷ್ಟು ಮಬ್ಬು ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಿ" + "ಇನ್ನಷ್ಟು ಮಬ್ಬು ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ" + "ಕನೆಕ್ಟಿವಿಟಿ" + "ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ" + "ಯುಟಿಲಿಟಿಗಳು" + "ಗೌಪ್ಯತೆ" + "ಆ್ಯಪ್‌ಗಳಿಂದ ಒದಗಿಸಲಾಗಿದೆ" + "ಡಿಸ್‌ಪ್ಲೇ" + "ಅಪರಿಚಿತ" diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 75bc3b26f61c0700c1a88f72f6b978cc1214229e..3e851a16cddf9105cf1e533b1356d707579b7194 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -105,8 +105,7 @@ "메모에 추가" "링크 포함" "%1$s(%2$d)" - - + "다른 프로필의 링크를 추가할 수 없습니다." "화면 녹화" "화면 녹화 처리 중" "화면 녹화 세션에 관한 지속적인 알림" @@ -1180,6 +1179,10 @@ "%1$d%%" "스피커 및 디스플레이" "추천 기기" + + + + "미디어를 다른 기기로 이동하려면 공유 세션을 중지하세요." "중지" "브로드캐스팅 작동 원리" @@ -1290,6 +1293,8 @@ "추가" "사용자 관리" "드래그하여 화면을 분할하는 기능이 지원되지 않는 알림입니다." + + "Wi‑Fi를 이용할 수 없습니다." "우선순위 모드입니다." "알람이 설정되었습니다." @@ -1346,6 +1351,8 @@ "잠금 화면 맞춤 설정" "잠금 화면 맞춤설정을 위해 잠금 해제" "Wi-Fi를 사용할 수 없음" + + "카메라 차단됨" "카메라 및 마이크 차단됨" "마이크 차단됨" @@ -1401,7 +1408,8 @@ "터치패드 동작, 단축키 등 알아보기" "뒤로 동작" "홈 동작" - "작업 키" + + "완료" "뒤로" "돌아가려면 세 손가락을 사용해 터치패드의 아무 곳이나 왼쪽 또는 오른쪽으로 스와이프합니다.\n\n키보드 단축키 Action + ESC를 사용할 수도 있습니다." @@ -1411,6 +1419,14 @@ "언제든지 홈 화면으로 이동하려면 세 손가락으로 화면 하단에서 위로 스와이프하세요." "좋습니다" "홈으로 이동 동작을 완료했습니다." + + + + + + + + "작업 키" "앱에 액세스하려면 키보드의 작업 키를 누르세요." "축하합니다" @@ -1434,22 +1450,19 @@ "세 손가락을 사용해 위로 스와이프한 다음 잠시 기다리세요. 더 많은 동작을 알아보려면 탭하세요." "키보드를 사용하여 모든 앱 보기" "언제든지 작업 키를 누릅니다. 더 많은 동작을 알아보려면 탭하세요." - "이제 \'더 어둡게\' 기능이 밝기 막대에 추가되었습니다" - "이제 화면 상단에서 밝기 수준을 더 낮춰 화면을 더 어둡게 만들 수 있습니다\n\n이 기능은 어두운 환경에서 가장 잘 작동합니다." - "\'더 어둡게\' 단축키 삭제" - "\'더 어둡게\' 단축키가 삭제되었습니다. 밝기를 낮추려면 일반 밝기 막대를 사용하세요." - - - - - - - + - + - + - + + "연결" + "접근성" + "유틸리티" + "개인 정보 보호" + "앱에서 제공" + "디스플레이" + "알 수 없음" diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 9bf427c0bfb9d7b97422367228e8ed59184d3d32..7148a5ba2377c8f6ded3d11113deeb3ac5601aa8 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -243,7 +243,7 @@ "Түзмөктүн чоо-жайын конфигурациялоо үчүн чыкылдатыңыз" "Бардык түзмөктөрдү көрүү үчүн чыкылдатыңыз" "Жаңы түзмөк кошуу үчүн чыкылдатыңыз" - "Батарея кубатынын деңгээли белгисиз." + "Батареянын деңгээли белгисиз." "%s менен туташкан." "%s менен туташты." "Интернет жок." @@ -577,7 +577,7 @@ "Билдирме жок" "Жаңы билдирмелер жок" "Билдирмелердин үнүн басаңдатуу күйүк" - "Бир убакта өтө көп билдирмелер келгенде, түзмөктүн үнү жана эскертүүлөрдүн саны 2 мүнөткө азайтылат." + "Өтө көп билдирме келсе, түзмөктүн үнү 2 мүнөткө басаңдап, эскертүүлөрдүн саны азаят." "Өчүрүү" "Билдирмелерди көрүү үчүн кулпуну ачыңыз" "Бул түзмөктү ата-энең башкарат" @@ -1179,6 +1179,10 @@ "%1$d%%" "Динамиктер жана дисплейлер" "Сунушталган түзмөктөр" + + + + "Медиафайлдарды башка түзмөккө жылдыруу үчүн жалпы сеансыңызды токтотуңуз" "Токтотуу" "Кабарлоо кантип иштейт" @@ -1289,6 +1293,8 @@ "Кошуу" "Колдонуучуларды башкаруу" "Бул билдирмени бөлүнгөн экранда сүйрөөгө болбойт." + + "Wi‑Fi жеткиликсиз" "Маанилүү сүйлөшүүлөр режими" "Ойготкуч коюлду" @@ -1345,6 +1351,8 @@ "Кулпу экранын тууралоо" "Кулпуланган экранды тууралоо үчүн кулпусун ачыңыз" "Wi-Fi жеткиликтүү эмес" + + "Камера бөгөттөлдү" "Камера менен микрофон бөгөттөлдү" "Микрофон бөгөттөлдү" @@ -1400,7 +1408,8 @@ "Сенсордук тактадагы жаңсоолор, ыкчам баскычтар жана башкалар жөнүндө билип алыңыз" "Артка кайтуу жаңсоосу" "Башкы бетке өтүү жаңсоосу" - "Аракет баскычы" + + "Бүттү" "Артка кайтуу" "Кайтуу үчүн сенсордук тактанын каалаган жерин үч манжаңыз менен солго же оңго сүрүңүз.\n\nОшондой эле Action + ESC баскычтарынын айкалышын колдоно аласыз." @@ -1410,6 +1419,14 @@ "Каалаган убакта башкы экранга өтүү үчүн экранды үч манжаңыз менен ылдыйдан жогору карай сүрүңүз." "Сонун!" "\"Башкы бетке өтүү\" жаңсоосун үйрөндүңүз." + + + + + + + + "Аракет баскычы" "Бардык колдонмолоруңузду көрүү үчүн баскычтобуңуздагы аракет баскычын басыңыз" "Куттуктайбыз!" @@ -1433,22 +1450,19 @@ "Үч манжаңыз менен өйдө сүрүп, кармап туруңуз. Башка жаңсоолорду үйрөнүү үчүн таптаңыз." "Бардык колдонмолорду көрүү үчүн баскычтобуңузду колдонуңуз" "Каалаганда аракет баскычын басыңыз. Башка жаңсоолорду үйрөнүү үчүн таптаңыз." - "Кошумча караңгылатуу эми жарыктык тилкесинде жайгашкан" - "Эми экраныңыздын өйдө жагынан жарыктыктын деңгээлин азайтып, экранды кошумча караңгылата аласыз.\n\nМуну караңгы жерде турганыңызда колдонуу сунушталат." - "Кошумча караңгылатуу ыкчам баскычын өчүрүү" - "Кошумча караңгылатуу ыкчам баскычы өчүрүлдү. Жарыктыкты азайтуу үчүн кадимки жарыктык тилкесин колдонуңуз." - - - - - - - + - + - + - + + "Байланыш" + "Атайын мүмкүнчүлүктөр" + "Утилиталар" + "Купуялык" + "Колдонмолор сунуштады" + "Экран" + "Белгисиз" diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index bc0c22d92373172440e13855aae25ebdc442c7a9..a5b621886ab68d3276341bd4c1fbc0d447c0ed59 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "ລຳໂພງ ແລະ ຈໍສະແດງຜົນ" "ອຸປະກອນທີ່ແນະນຳ" + + + + "ຢຸດເຊດຊັນທີ່ແບ່ງປັນຂອງທ່ານເພື່ອຍ້າຍມີເດຍໄປຫາອຸປະກອນອື່ນ" "ຢຸດ" "ການອອກອາກາດເຮັດວຽກແນວໃດ" @@ -1289,6 +1293,7 @@ "ເພີ່ມ" "ຈັດການຜູ້ໃຊ້" "ການແຈ້ງເຕືອນນີ້ບໍ່ຮອງຮັບການລາກເພື່ອແບ່ງໜ້າຈໍ" + "ສະຖານທີ່ທີ່ນຳໃຊ້ຢູ່" "ບໍ່ສາມາດໃຊ້ Wi‑Fi ໄດ້" "ໂໝດຄວາມສຳຄັນ" "ຕັ້ງໂມງປຸກແລ້ວ" @@ -1345,6 +1350,7 @@ "ປັບແຕ່ງໜ້າຈໍລັອກ" "ປົດລັອກເພື່ອປັບແຕ່ງໜ້າຈໍລັອກ" "Wi-Fi ບໍ່ພ້ອມໃຫ້ນຳໃຊ້" + "ສະຖານທີ່ທີ່ນຳໃຊ້ຢູ່" "ກ້ອງຖ່າຍຮູບຖືກບລັອກຢູ່" "ກ້ອງຖ່າຍຮູບ ແລະ ໄມໂຄຣໂຟນຖືກບລັອກຢູ່" "ໄມໂຄຣໂຟນຖືກບລັອກຢູ່" @@ -1400,7 +1406,7 @@ "ສຶກສາທ່າທາງຂອງແຜ່ນສຳຜັດ, ຄີລັດ ແລະ ອື່ນໆ" "ທ່າທາງສຳລັບກັບຄືນ" "ທ່າທາງສຳລັບໜ້າຫຼັກ" - "ປຸ່ມຄຳສັ່ງ" + "ເບິ່ງແອັບຫຼ້າສຸດ" "ແລ້ວໆ" "ກັບຄືນ" "ເພື່ອກັບຄືນ, ໃຫ້ໃຊ້ 3 ນິ້ວປັດຊ້າຍ ຫຼື ຂວາບ່ອນໃດກໍໄດ້ເທິງແຜ່ນສຳຜັດ.\n\nທ່ານຍັງສາມາດໃຊ້ຄຳສັ່ງຄີລັດ + ESC ສຳລັບການດຳເນີນການນີ້ໄດ້ນຳ." @@ -1410,6 +1416,10 @@ "ເພື່ອໄປຫາໜ້າຫຼັກຂອງທ່ານຕອນໃດກໍໄດ້, ໃຫ້ປັດຂຶ້ນດ້ວຍສາມນິ້ວຈາກລຸ່ມສຸດຂອງໜ້າຈໍຂອງທ່ານ." "ດີຫຼາຍ!" "ທ່ານໃຊ້ທ່າທາງໄປໜ້າຫຼັກສຳເລັດແລ້ວ." + "ເບິ່ງແອັບຫຼ້າສຸດ" + "ໃຊ້ 3 ນິ້ວປັດຂຶ້ນແລ້ວຄ້າງໄວ້ຢູ່ແຜ່ນສໍາຜັດຂອງທ່ານ." + "ດີຫຼາຍ!" + "ທ່ານເບິ່ງທ່າທາງຂອງແອັບຫຼ້າສຸດສຳເລັດແລ້ວ." "ປຸ່ມຄຳສັ່ງ" "ເພື່ອເຂົ້າເຖິງແອັບ, ໃຫ້ກົດປຸ່ມຄຳສັ່ງຢູ່ແປ້ນພິມຂອງທ່ານ." "ຂໍສະແດງຄວາມຍິນດີ!" @@ -1433,22 +1443,15 @@ "ໃຊ້ 3 ນິ້ວປັດຂຶ້ນ ແລ້ວຄ້າງໄວ້. ແຕະເພື່ອສຶກສາທ່າທາງເພີ່ມເຕີມ." "ໃຊ້ແປ້ນພິມຂອງທ່ານເພື່ອເບິ່ງແອັບທັງໝົດ" "ກົດປຸ່ມຄຳສັ່ງໄດ້ທຸກເວລາ. ແຕະເພື່ອສຶກສາທ່າທາງເພີ່ມເຕີມ." - "ຕອນນີ້ການຫຼຸດແສງເປັນພິເສດເປັນສ່ວນໜຶ່ງຂອງແຖບຄວາມສະຫວ່າງແລ້ວ" - "ຕອນນີ້ທ່ານສາມາດເຮັດໃຫ້ໜ້າຈໍມືດລົງເປັນພິເສດໄດ້ໂດຍການຫຼຸດລະດັບຄວາມສະຫວ່າງລົງໃຫ້ຫຼາຍຂຶ້ນຈາກເທິງສຸດຂອງໜ້າຈໍຂອງທ່ານ.\n\nຄຸນສົມບັດນີ້ຈະເຮັດວຽກໄດ້ດີທີ່ສຸດເມື່ອທ່ານຢູ່ໃນສະພາບແວດລ້ອມທີ່ມືດ." - "ລຶບທາງລັດທີ່ຫຼຸດແສງເປັນພິເສດອອກ" - "ລຶບທາງລັດທີ່ຫຼຸດແສງເປັນພິເສດອອກແລ້ວ. ເພື່ອຫຼຸດຄວາມສະຫວ່າງຂອງທ່ານລົງ, ໃຫ້ໃຊ້ແຖບຄວາມສະຫວ່າງປົກກະຕິ." - - - - - - - - - - - - - - + "ຕອນນີ້ການຫຼຸດແສງເປັນພິເສດເປັນສ່ວນໜຶ່ງຂອງແຖບເລື່ອນຄວາມສະຫວ່າງແລ້ວ" + "ຕອນນີ້ທ່ານສາມາດເຮັດໃຫ້ໜ້າຈໍມືດລົງເປັນພິເສດໄດ້ໂດຍການຫຼຸດລະດັບຄວາມສະຫວ່າງລົງໃຫ້ຫຼາຍຂຶ້ນ.\n\nເນື່ອງຈາກຕອນນີ້ຄຸນສົມບັດນີ້ເປັນສ່ວນໜຶ່ງຂອງແຖບເລື່ອນຄວາມສະຫວ່າງແລ້ວ, ທາງລັດທີ່ຫຼຸດແສງເປັນພິເສດຈຶ່ງຈະຖືກລຶບອອກ." + "ລຶບທາງລັດທີ່ຫຼຸດແສງເປັນພິເສດອອກ" + "ລຶບທາງລັດທີ່ຫຼຸດແສງເປັນພິເສດອອກແລ້ວ" + "ການເຊື່ອມຕໍ່" + "ການຊ່ວຍເຂົ້າເຖິງ" + "ບໍລິການສາທາລະນູປະໂພກ" + "ຄວາມເປັນສ່ວນຕົວ" + "ສະໜອງໃຫ້ໂດຍແອັບ" + "ການສະແດງຜົນ" + "ບໍ່ຮູ້ຈັກ" diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 91f8398c8e16fa651765a9d855f04da98326b0b8..e210a999709a0ab62dcf7aa3b64a1892f6b5d965 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -105,8 +105,7 @@ "Pridėti prie užrašo" "Įtraukti nuorodą" "„%1$s(%2$d)" - - + "Nuorodų negalima pridėti iš kitų profilių" "Ekrano vaizdo įrašytuvas" "Apdorojam. ekrano vaizdo įraš." "Šiuo metu rodomas ekrano įrašymo sesijos pranešimas" @@ -1180,6 +1179,10 @@ "%1$d %%" "Garsiakalbiai ir ekranai" "Siūlomi įrenginiai" + + + + "Sustabdyti bendrinamą seansą norint perkelti mediją į kitą įrenginį" "Sustabdyti" "Kaip veikia transliacija" @@ -1290,6 +1293,8 @@ "Pridėti" "Tvarkyti naudotojus" "Šio pranešimo vilkimas išskaidyto ekrano režimu nepalaikomas" + + "„Wi‑Fi“ ryšys nepasiekiamas" "Prioriteto režimas" "Signalas nustatytas" @@ -1346,6 +1351,8 @@ "Užrakinimo ekrano tinkinimas" "Atrakinę tinkinkite užrakinimo ekraną" "„Wi-Fi“ ryšys nepasiekiamas" + + "Fotoaparatas užblokuotas" "Fotoaparatas ir mikrofonas užblokuoti" "Mikrofonas užblokuotas" @@ -1401,7 +1408,8 @@ "Sužinokite jutiklinės dalies gestus, sparčiuosius klavišus ir kt." "Grįžimo atgal gestas" "Pagrindinio ekrano gestas" - "Veiksmų klavišas" + + "Atlikta" "Grįžti" "Jei norite grįžti, perbraukite kairėn arba dešinėn trimis pirštais bet kurioje jutiklinės dalies vietoje.\n\nTaip pat galite naudoti šį spartųjį klavišą: veiksmų klavišas + klavišas „Esc“." @@ -1411,6 +1419,14 @@ "Jei norite bet kada pasiekti pagrindinį ekraną, perbraukite aukštyn trim pirštais iš ekrano apačios." "Šaunu!" "Atlikote perėjimo į pagrindinį ekraną gestą." + + + + + + + + "Veiksmų klavišas" "Jei norite pasiekti programas, paspauskite klaviatūros veiksmų klavišą." "Sveikiname!" @@ -1434,22 +1450,19 @@ "Perbraukite aukštyn trimis pirštais ir palaikykite. Palieskite, kad sužinotumėte daugiau gestų." "Naudokite klaviatūrą, kad peržiūrėtumėte visas programas" "Bet kuriuo metu paspauskite veiksmų klavišą. Palieskite, kad sužinotumėte daugiau gestų." - "Funkcija „Itin blanku“ dabar yra ryškumo juostos dalis" - "Dabar galite padaryti ekraną itin blankų, dar labiau sumažindami ryškumo lygį nuo ekrano viršaus.\n\nŠi funkcija geriausiai veikia, kai esate tamsioje aplinkoje." - "Pašalinti funkcijos „Itin blanku“ spartųjį klavišą" - "Funkcijos „Itin blanku“ spartusis klavišas pašalintas. Jei norite sumažinti ryškumą, naudokite įprastą ryškumo juostą." - - - - - - - + - + - + - + + "Ryšiai" + "Pritaikomumas" + "Paslaugų programos" + "Privatumas" + "Teikia programos" + "Ekranas" + "Nežinoma" diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 29af399aaf940368789b68d0dcb672d991cf0811..625da9210410bdc564bf541cdff6f52654748697 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -105,8 +105,7 @@ "Pievienot piezīmei" "Iekļaut saiti" "%1$s (%2$d)" - - + "Nevar pievienot saites no citiem profiliem." "Ekrāna ierakstītājs" "Ekrāna ieraksta apstrāde" "Aktīvs paziņojums par ekrāna ierakstīšanas sesiju" @@ -1180,6 +1179,10 @@ "%1$d%%" "Skaļruņi un displeji" "Ieteiktās ierīces" + + + + "Pārtrauciet savu kopīgoto sesiju, lai pārvietotu multivides saturu uz citu ierīci." "Pārtraukt" "Kā darbojas apraide" @@ -1290,6 +1293,8 @@ "Pievienot" "Pārvaldīt" "Šis paziņojums neatbalsta vilkšanu uz sadalīto ekrānu." + + "Wi‑Fi nav pieejams" "Prioritātes režīms" "Signāls ir iestatīts" @@ -1346,6 +1351,8 @@ "Pielāgot bloķēšanas ekrānu" "Bloķēšanas ekrāna pielāgošana pēc atbloķēšanas" "Wi-Fi nav pieejams" + + "Kamera ir bloķēta" "Kameras un mikrofona lietošana ir bloķēta" "Mikrofons ir bloķēts" @@ -1401,7 +1408,8 @@ "Uzziniet par skārienpaliktņa žestiem, īsinājumtaustiņiem un citām iespējām." "Žests pāriešanai atpakaļ" "Žests pāriešanai uz sākumu" - "Darbību taustiņš" + + "Gatavs" "Atpakaļ" "Lai atgrieztos, ar trīs pirkstiem velciet pa kreisi vai pa labi jebkurā vietā uz skārienpaliktņa.\n\nVarat arī izmantot šim nolūkam īsinājumtaustiņus: darbību taustiņu + Esc." @@ -1411,6 +1419,14 @@ "Lai jebkurā brīdī pārietu uz sākuma ekrānu, ar trim pirkstiem velciet augšup no ekrāna apakšdaļas." "Lieliski!" "Jūs sekmīgi veicāt sākuma ekrāna atvēršanas žestu." + + + + + + + + "Darbību taustiņš" "Lai piekļūtu savām lietotnēm, tastatūrā nospiediet darbību taustiņu." "Apsveicam!" @@ -1434,22 +1450,19 @@ "Ar trīs pirkstiem velciet augšup un turiet. Lai apgūtu citus žestus, pieskarieties šeit." "Visu lietotņu skatīšana, izmantojot tastatūru" "Jebkurā laikā varat nospiest darbību taustiņu. Lai apgūtu citus žestus, pieskarieties šeit." - "Papildu aptumšošana tagad ir iekļauta spilgtuma joslā" - "Tagad varat veikt ekrāna papildu aptumšošanu, vēl vairāk samazinot spilgtumu ekrāna augšdaļā.\n\nTas darbojas vislabāk, ja esat tumšā vietā." - "Noņemt papildu aptumšošanas saīsni" - "Papildu aptumšošanas saīsne ir noņemta. Lai samazinātu spilgtumu, izmantojiet parasto spilgtuma joslu." - - - - - - - + - + - + - + + "Savienojamība" + "Pieejamība" + "Utilītprogrammas" + "Konfidencialitāte" + "Nodrošina lietotnes" + "Displejs" + "Nezināma" diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 11fb67c759e020829447690787480d12b929d69c..f1aab8437d93656a3fc0383fd0967ee93cb20817 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -105,8 +105,7 @@ "Додај во белешка" "Опфати линк" "%1$s (%2$d)" - - + "Не може да се додаваат линкови од други профили" "Снимач на екран" "Се обработува снимка од екран" "Тековно известување за сесија за снимање на екранот" @@ -1180,6 +1179,10 @@ "%1$d %%" "Звучници и екрани" "Предложени уреди" + + + + "Сопрете ја споделената сесија за да ги преместите аудиовизуелните содржини на друг уред" "Сопри" "Како функционира емитувањето" @@ -1290,6 +1293,7 @@ "Додај" "Управувајте со корисниците" "Известувањево не поддржува влечење на поделен екран" + "Локацијата е активна" "Wi‑Fi е недостапна" "Приоритетен режим" "Алармот е наместен" @@ -1346,6 +1350,7 @@ "Приспособете го заклучениот екран" "Отклучување за приспособување на заклучениот екран" "Wi-Fi не е достапно" + "Локацијата е активна" "Камерата е блокирана" "Камерата и микрофонот се блокирани" "Микрофонот е блокиран" @@ -1401,7 +1406,7 @@ "Научете движења за допирната подлога, кратенки од тастатурата и друго" "Движење за назад" "Движење за почетен екран" - "Копче за дејство" + "Прегледајте ги неодамнешните апликации" "Готово" "Назад" "За да се вратите назад, повлечете налево или надесно со три прста каде било на допирната подлога.\n\nЗа ова може да ја користите и кратенката од тастатурата Action + ESC." @@ -1411,6 +1416,10 @@ "За да одите на вашиот почетен екран кога сакате, повлечете нагоре со три прсти од дното на екранот." "Одлично!" "Го научивте движењето за враќање на почетниот екран." + "Прегледајте ги неодамнешните апликации" + "Повлечете нагоре и задржете со три прста на допирната подлога." + "Одлично!" + "Го завршивте движењето за прегледување на неодамнешните апликации." "Копче за дејство" "За да пристапите до апликациите, притиснете го копчето за дејство на тастатурата." "Честитки!" @@ -1434,22 +1443,15 @@ "Повлечете нагоре и задржете со три прста. Допрете за да научите повеќе движења." "Користете ја тастатурата за да ги видите сите апликации" "Притиснете го копчето за дејство кога сакате. Допрете за да научите повеќе движења." - "Отсега „Дополнително затемнување“ е дел од лентата за осветленост" - "Отсега може да го затемнувате екранот дополнително со намалување на нивото на осветленост од горниот дел на екранот.\n\nОва функционира најдобро кога сте во темна средина." - "Отстрани ја кратенката за „Дополнително затемнување“" - "Кратенката за „Дополнително затемнување“ е отстранета. Користете ја стандардната лента за осветленост за да ја намалите осветленоста." - - - - - - - - - - - - - - + "Отсега „Дополнително затемнување“ е дел од лизгачот за осветленост" + "Отсега може да го затемнувате екранот дополнително со намалување на нивото на осветленост уште повеќе.\n\nОтсега функцијава е дел од лизгачот за осветленост, па се отстрануваат кратенките за „Дополнително затемнување“." + "Отстрани ги кратенките за „Дополнително затемнување“" + "Кратенките за „Дополнително затемнување“ се отстранети" + "Поврзливост" + "Пристапност" + "Услужни програми" + "Приватност" + "Обезбедено од апликации" + "Екран" + "Непознато" diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 720275e3a6a45b173c3871b62a0ade5052a668bd..42281ce1294da4cbebe5177523e65b24b38118d0 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "സ്‌പീക്കറുകളും ഡിസ്പ്ലേകളും" "നിർദ്ദേശിച്ച ഉപകരണങ്ങൾ" + + + + "മീഡിയയെ മറ്റൊരു ഉപകരണത്തിലേക്ക് നീക്കുന്നതിന് നിങ്ങളുടെ പങ്കിട്ട സെഷൻ നിർത്തുക" "നിർത്തുക" "ബ്രോഡ്‌കാസ്‌റ്റ് എങ്ങനെയാണ് പ്രവർത്തിക്കുന്നത്" @@ -1289,6 +1293,7 @@ "ചേർക്കുക" "ഉപയോക്താക്കളെ മാനേജ് ചെയ്യുക" "സ്പ്ലിറ്റ് സ്ക്രീനിലേക്ക് വലിച്ചിടുന്നതിനെ ഈ അറിയിപ്പ് പിന്തുണയ്ക്കുന്നില്ല" + "ലൊക്കേഷൻ സജീവമാണ്" "വൈഫൈ ലഭ്യമല്ല" "മുൻഗണനാ മോഡ്" "അലാറം സജ്ജീകരിച്ചു" @@ -1345,6 +1350,7 @@ "ലോക്ക് സ്‌ക്രീൻ ഇഷ്ടാനുസൃതമാക്കൂ" "ലോക്ക് സ്ക്രീൻ ഇഷ്ടാനുസൃതമാക്കാൻ അൺലോക്ക് ചെയ്യുക" "വൈഫൈ ലഭ്യമല്ല" + "ലൊക്കേഷൻ സജീവമാണ്" "ക്യാമറ ബ്ലോക്ക് ചെയ്തിരിക്കുന്നു" "ക്യാമറയും മൈക്രോഫോണും ബ്ലോക്ക് ചെയ്തിരിക്കുന്നു" "മൈക്രോഫോൺ ബ്ലോക്ക് ചെയ്തിരിക്കുന്നു" @@ -1400,7 +1406,7 @@ "ടച്ച്‌പാഡ് ജെസ്ച്ചറുകൾ, കീബോർഡ് കുറുക്കുവഴികൾ എന്നിവയും മറ്റും മനസ്സിലാക്കുക" "\'മടങ്ങുക\' ജെസ്ച്ചർ" "ഹോം ജെസ്‌ച്ചർ" - "ആക്ഷൻ കീ" + "അടുത്തിടെയുള്ള ആപ്പുകൾ കാണുക" "പൂർത്തിയായി" "മടങ്ങുക" "തിരികെ പോകാൻ, ടച്ച്പാഡിൽ എവിടെയെങ്കിലും മൂന്ന് വിരലുകൾ ഉപയോഗിച്ച് ഇടത്തേക്കോ വലത്തേക്കോ സ്വൈപ്പ് ചെയ്യുക.\n\nഇതിന് Action + ESC കീബോഡ് കുറുക്കുവഴികളും നിങ്ങൾക്ക് ഉപയോഗിക്കാം." @@ -1410,6 +1416,10 @@ "ഏതുസമയത്തും ഹോം സ്ക്രീനിലേക്ക് പോകാൻ, മൂന്ന് വിരലുകൾ ഉപയോഗിച്ച് സ്ക്രീനിന്റെ താഴെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യൂ." "കൊള്ളാം!" "ഹോമിലേക്ക് പോകുക ജെസ്ച്ചർ നിങ്ങൾ പൂർത്തിയാക്കി." + "അടുത്തിടെയുള്ള ആപ്പുകൾ കാണുക" + "നിങ്ങളുടെ ടച്ച്പാഡിൽ മൂന്ന് വിരലുകൾ കൊണ്ട് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്‌ത് പിടിക്കുക." + "കൊള്ളാം!" + "അടുത്തിടെയുള്ള ആപ്പുകൾ കാണുക എന്ന ജെസ്ച്ചർ നിങ്ങൾ പൂർത്തിയാക്കി." "Action കീ" "നിങ്ങളുടെ ആപ്പുകൾ ആക്‌സസ് ചെയ്യാൻ, നിങ്ങളുടെ കീബോർഡിലെ Action കീ അമർത്തുക." "അഭിനന്ദനങ്ങൾ!" @@ -1433,22 +1443,15 @@ "മൂന്ന് വിരലുകൾ കൊണ്ട് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്‌ത് പിടിക്കുക. കൂടുതൽ ജെസ്ച്ചറുകളറിയാൻ ടാപ്പ് ചെയ്യൂ." "എല്ലാ ആപ്പുകളും കാണാൻ നിങ്ങളുടെ കീബോർഡ് ഉപയോഗിക്കുക" "ഏതുസമയത്തും ആക്ഷൻ കീ അമർത്തുക. കൂടുതൽ ജെസ്ച്ചറുകൾ മനസ്സിലാക്കാൻ ടാപ്പ് ചെയ്യുക." - "കൂടുതൽ ഡിം ചെയ്യൽ, ഇപ്പോൾ തെളിച്ചം ബാറിന്റെ ഭാഗമാണ്" - "മുകളിൽ നിന്ന് തെളിച്ചം കുറയ്ക്കുന്നതിലൂടെ നിങ്ങൾക്ക് ഇപ്പോൾ സ്‌ക്രീൻ കൂടുതൽ മങ്ങിക്കാൻ കഴിയും.\n\nനിങ്ങൾ ഇരുണ്ട മുറിയിലായിരിക്കുമ്പോൾ ഇത് മികച്ച രീതിയിൽ പ്രവർത്തിക്കുന്നു." - "കൂടുതൽ ഡിം ചെയ്യൽ കുറുക്കുവഴി നീക്കം ചെയ്യുക" - "കൂടുതൽ ഡിം ചെയ്യാനുള്ള കുറുക്കുവഴി നീക്കം ചെയ്തു. തെളിച്ചം കുറയ്ക്കാൻ, സാധാരണ \'തെളിച്ചം ബാർ\' ഉപയോഗിക്കുക." - - - - - - - - - - - - - - + "കൂടുതൽ ഡിം ചെയ്യൽ, ഇപ്പോൾ തെളിച്ച സ്ലൈഡറിന്റെ ഭാഗമാണ്" + "തെളിച്ചം വളരെ കുറയ്ക്കുന്നതിലൂടെ നിങ്ങൾക്കിപ്പോൾ സ്ക്രീൻ കൂടുതൽ ഡിം ചെയ്യാനാകും.\n\nഈ ഫീച്ചർ ഇപ്പോൾ തെളിച്ച സ്ലൈഡറിന്റെ ഭാഗമായതിനാൽ, കൂടുതൽ ഡിം ചെയ്യൽ കുറുക്കുവഴികൾ നീക്കം ചെയ്യുകയാണ്." + "കൂടുതൽ ഡിം ചെയ്യൽ കുറുക്കുവഴികൾ നീക്കം ചെയ്യുക" + "കൂടുതൽ ഡിം ചെയ്യൽ കുറുക്കുവഴികൾ നീക്കം ചെയ്തു" + "കണക്റ്റിവിറ്റി" + "ഉപയോഗസഹായി" + "യൂട്ടിലിറ്റികൾ" + "സ്വകാര്യത" + "ആപ്പുകൾ നൽകുന്നത്" + "ഡിസ്‌പ്ലേ" + "അജ്ഞാതം" diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 50a942e4a2c8b33d94ca260b7cbd51b29bb75d54..b861020614a075ccea82715509a8de831a186499 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -105,8 +105,7 @@ "Тэмдэглэлд нэмэх" "Холбоосыг оруулах" "%1$s (%2$d)⁠" - - + "Бусад профайлаас холбоос нэмэх боломжгүй" "Дэлгэцийн үйлдэл бичигч" "Дэлгэц бичлэг боловсруулж байна" "Дэлгэц бичих горимын үргэлжилж буй мэдэгдэл" @@ -1180,6 +1179,10 @@ "%1$d%%" "Чанга яригч ба дэлгэц" "Санал болгосон төхөөрөмжүүд" + + + + "Өөр төхөөрөмж рүү медиа зөөхийн тулд хуваалцсан харилцан үйлдлээ зогсооно уу" "Зогсоох" "Нэвтрүүлэлт хэрхэн ажилладаг вэ?" @@ -1290,6 +1293,7 @@ "Нэмэх" "Хэрэглэгчдийг удирдах" "Энэ мэдэгдэл нь дэлгэцийг хуваах горим руу чирэхийг дэмждэггүй" + "Байршил идэвхтэй" "Wi‑Fi боломжгүй" "Чухал горим" "Сэрүүлгийг тохируулсан" @@ -1346,6 +1350,7 @@ "Түгжээтэй дэлгэцийг өөрчлөх" "Түгжээтэй дэлгэцийг өөрчлөхийн тулд түгжээг тайлна уу" "Wi-Fi боломжгүй байна" + "Байршил идэвхтэй" "Камерыг блоклосон" "Камер болон микрофоныг блоклосон" "Микрофоныг блоклосон" @@ -1401,7 +1406,7 @@ "Мэдрэгч самбарын зангаа, товчлуурын шууд холбоос болон бусад зүйлийг мэдэж аваарай" "Буцах зангаа" "Үндсэн нүүрний зангаа" - "Тусгай товчлуур" + "Саяхны аппуудыг харах" "Болсон" "Буцах" "Буцахын тулд мэдрэгч самбар дээр гурван хуруугаар хүссэн газраа зүүн эсвэл баруун тийш шударна уу.\n\nТа мөн үүнийг хийхэд Action + ESC товчлуурын шууд холбоосыг ашиглах боломжтой." @@ -1411,6 +1416,10 @@ "Үндсэн нүүр лүүгээ хүссэн үедээ очихын тулд дэлгэцийнхээ доод талаас гурван хуруугаараа дээш шударна уу." "Янзтай!" "Та үндсэн нүүр лүү очих зангааг гүйцэтгэлээ." + "Саяхны аппуудыг харах" + "Мэдрэгч самбар дээрээ гурван хуруугаа ашиглан дээш шудраад, удаан дарна уу." + "Сайн байна!" + "Та саяхны аппуудыг харах зангааг гүйцэтгэсэн." "Тусгай товчлуур" "Аппууддаа хандахын тулд гар дээр тань байх тусгай товчлуурыг дарна уу." "Баяр хүргэе!" @@ -1434,22 +1443,15 @@ "Гурван хуруугаа ашиглан дээш шудраад, удаан дарна уу. Илүү олон зангаа сурахын тулд товшино уу." "Бүх аппыг харахын тулд гараа ашиглах" "Тусгай товчлуурыг хүссэн үедээ дарна уу. Илүү олон зангаа сурахын тулд товшино уу." - "Хэт бүүдгэр онцлог одоо гэрэлтүүлгийн самбарын нэг хэсэг боллоо" - "Та одоо дэлгэцийнхээ дээд талаас гэрэлтүүлгийн түвшнийг бүр илүү багасгаснаар дэлгэцийг хэт бүүдгэр болгох боломжтой.\n\nЭнэ нь таныг харанхуй орчинд байхад хамгийн сайн ажилладаг." - "Хэт бүүдгэр онцлогийн товчлолыг хасах" - "Хэт бүүдгэр онцлогийн товчлолыг хассан. Гэрэлтүүлгээ багасгахын тулд энгийн гэрэлтүүлгийн самбарыг ашиглана уу." - - - - - - - - - - - - - - + "Хэт бүүдгэр онцлог одоо гэрэлтүүлгийн гулсуулагчийн нэг хэсэг боллоо" + "Та одоо гэрэлтүүлгийн түвшнийг бүр илүү багасгаснаар дэлгэцийг хэт бүүдгэр болгох боломжтой.\n\nЭнэ онцлог нь одоо гэрэлтүүлгийн гулсуулагчийн нэг хэсэг болсон тул Хэт бүүдгэр онцлогийн тохиргоог хасаж байна." + "Хэт бүүдгэр онцлогийн товчлолыг хасах" + "Хэт бүүдгэр онцлогийн товчлолыг хассан" + "Холболт" + "Хандалт" + "Хэрэгсэл" + "Нууцлал" + "Аппуудаас өгсөн" + "Дэлгэц" + "Тодорхойгүй" diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 75ef6e0394ac2947326d3fac490641ec4a838356..eac5553c86550615cb2162f5f1362084b65b6597 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "स्पीकर आणि डिस्प्ले" "सुचवलेली डिव्हाइस" + + + + "मीडिया दुसऱ्या डिव्हाइसवर शेअर करण्यासाठी तुमचे शेअर केलेले सेशन थांबवा" "थांबवा" "ब्रॉडकास्टिंग कसे काम करते" @@ -1289,6 +1293,7 @@ "जोडा" "वापरकर्ते व्यवस्‍थापित करा" "ही सूचना स्प्लिट स्क्रीनवर ड्रॅग करण्याला सपोर्ट करत नाही" + "स्थान अ‍ॅक्टिव्ह आहे" "वाय-फाय उपलब्ध नाही" "प्राधान्य मोड" "अलार्म सेट केला" @@ -1345,6 +1350,7 @@ "कस्टमाइझ लॉक स्‍क्रीन" "लॉक स्‍क्रीन कस्टमाइझ करण्यासाठी अनलॉक करा" "वाय-फाय उपलब्ध नाही" + "स्थान अ‍ॅक्टिव्ह आहे" "कॅमेरा ब्लॉक केला" "कॅमेरा आणि मायक्रोफोन ब्लॉक केले आहेत" "मायक्रोफोन ब्लॉक केला" @@ -1400,7 +1406,7 @@ "टचपॅड जेश्चर, कीबोर्ड शॉर्टकट आणि आणखी बरेच काही जाणून घ्या" "मागे जा जेश्चर" "होम जेश्चर" - "अ‍ॅक्शन की" + "अलीकडील अ‍ॅप्स पहा" "पूर्ण झाले" "मागे जा" "मागे जाण्यासाठी, तीन बोटांनी टचपॅडवर कुठेही डावीकडे किंवा उजवीकडे स्वाइप करा.\n\nतुम्ही यासाठी Action + ESC हा कीबोर्ड शॉर्टकटदेखील वापरू शकता." @@ -1410,6 +1416,10 @@ "कधीही तुमच्या होम स्क्रीनवर जाण्यासाठी, तीन बोटांनी तुमच्या स्क्रीनच्या तळापासून स्वाइप करा." "छान!" "तुम्ही गो होम जेश्चर पूर्ण केले आहे." + "अलीकडील अ‍ॅप्स पहा" + "तुमच्या टचपॅडवर तीन बोटांनी वरती आणि खाली स्वाइप करा." + "उत्तम कामगिरी!" + "तुम्ही अलीकडील ॲप्स पाहण्याचे जेश्चर पूर्ण केले आहे." "अ‍ॅक्शन की" "तुमची ॲप्स अ‍ॅक्सेस करण्यासाठी, तुमच्या कीबोर्डवरील अ‍ॅक्शन की प्रेस करा." "अभिनंदन!" @@ -1433,22 +1443,15 @@ "तीन बोटांनी वरती आणि खाली स्वाइप करा. आणखी जेश्चर जाणून घेण्यासाठी टॅप करा." "सर्व ॲप्स पाहण्यासाठी तुमचा कीबोर्ड वापरा" "अ‍ॅक्शन की कधीही प्रेस करा. आणखी जेश्चर जाणून घेण्यासाठी टॅप करा." - "आणखी डिम हे आता ब्राइटनेस बारचा भाग आहे" - "तुम्ही आता तुमच्या स्क्रीनच्या सर्वात वरून ब्राइटनेसची पातळी आणखी कमी करून स्क्रीनला आणखी डिम करू शकता.\n\nतुम्ही गडद वातावरणात असता, तेव्हा हे सर्वोत्तम कार्य करते." - "आणखी डिमचा शॉर्टकट काढून टाका" - "आणखी डिमचा शॉर्टकट काढून टाकला आहे. तुमचा ब्राइटनेस कमी करण्यासाठी, नेहमीचा ब्राइटनेस बार वापरा." - - - - - - - - - - - - - - + "आणखी डिम हे आता ब्राइटनेस स्लायडरमध्ये समाविष्ट आहे" + "तुम्ही आता ब्राइटनेसची पातळी आणखी कमी करून स्क्रीनला आणखी डिम करू शकता.\n\nहे वैशिष्ट्य आता ब्राइटनेसच्या स्लायडरमध्ये समाविष्ट असल्याने, आणखी डिम शॉर्टकट काढून टाकले जात आहेत." + "आणखी डिम शॉर्टकट काढून टाका" + "आणखी डिम शॉर्टकट काढून टाकले आहेत" + "कनेक्टिव्हिटी" + "अ‍ॅक्सेसिबिलिटी" + "उपयुक्तता" + "गोपनीयता" + "अ‍ॅप्सद्वारे पुरवलेले" + "डिस्प्ले" + "अज्ञात" diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index e8213ba5f74cd6bd5aff795d485d55bfd1a035c9..ab7ae77e021f6c44424898f1aa40672ae53a813f 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Pembesar Suara & Paparan" "Peranti yang Dicadangkan" + + + + "Hentikan sesi dikongsi anda untuk mengalihkan media kepada peranti yang lain" "Berhenti" "Cara siaran berfungsi" @@ -1289,6 +1293,7 @@ "Tambah" "Urus pengguna" "Pemberitahuan ini tidak menyokong penyeretan kepada skrin pisah" + "Lokasi aktif" "Wi‑Fi dimatikan" "Mod keutamaan" "Penggera ditetapkan" @@ -1345,6 +1350,7 @@ "Sesuaikan skrin kunci" "Buka kunci untuk menyesuaikan skrin kunci" "Wi-Fi tidak tersedia" + "Lokasi aktif" "Kamera disekat" "Kamera dan mikrofon disekat" "Mikrofon disekat" @@ -1400,7 +1406,7 @@ "Ketahui gerak isyarat pad sentuh, pintasan papan kekunci dan pelbagai lagi" "Gerak isyarat kembali" "Gerak isyarat pergi ke laman utama" - "Kekunci tindakan" + "Lihat apl terbaharu" "Selesai" "Kembali" "Untuk kembali, leret ke kiri atau ke kanan menggunakan tiga jari di mana-mana sahaja pada pad sentuh.\n\nAnda juga boleh menggunakan pintasan papan kekunci Action + ESC untuk kembali." @@ -1410,6 +1416,10 @@ "Untuk mengakses skrin utama anda pada bila-bila masa, leret ke atas menggunakan tiga jari daripada bahagian bawah skrin anda." "Bagus!" "Anda telah melengkapkan gerak isyarat akses laman utama." + "Lihat apl terbaharu" + "Leret ke atas dan tahan menggunakan tiga jari pada pad sentuh anda." + "Syabas!" + "Anda melengkapkan gerak isyarat lihat apl terbaharu." "Kekunci tindakan" "Untuk mengakses semua apl anda, tekan kekunci tindakan pada papan kekunci anda." "Tahniah!" @@ -1433,22 +1443,15 @@ "Leret ke atas, tahan dengan tiga jari. Ketik untuk mengetahui lebih lanjut tentang gerak isyarat." "Gunakan papan kekunci anda untuk melihat semua apl" "Tekan kekunci tindakan pada bila-bila masa. Ketik dan ketahui lebih lanjut tentang gerak isyarat." - "Kini ciri amat malap merupakan sebahagian daripada bar kecerahan" - "Kini anda boleh menjadikan skrin amat malap dengan merendahkan tahap kecerahan lebih jauh daripada bahagian atas skrin anda.\n\nCiri ini berfungsi paling baik apabila anda berada dalam persekitaran yang gelap." - "Alih keluar pintasan amat malap" - "Pintasan amat malap dialih keluar. Untuk mengurangkan kecerahan anda, gunakan bar kecerahan biasa." - - - - - - - - - - - - - - + "Kini ciri amat malap merupakan sebahagian daripada peluncur kecerahan" + "Kini anda boleh menjadikan skrin amat malap dengan merendahkan lebih lagi tahap kecerahan.\n\nMemandangkan ciri ini kini merupakan sebahagian daripada peluncur kecerahan, pintasan amat malap dialih keluar." + "Alih keluar pintasan amat malap" + "Pintasan amat malap dialih keluar" + "Kesambungan" + "Kebolehaksesan" + "Utiliti" + "Privasi" + "Disediakan oleh apl" + "Paparan" + "Tidak diketahui" diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 8abb63da1b3aa8b124a62f77345c1a7b60e10013..89449f83e0ea0b91afb95119b01451b106dff4a9 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -442,7 +442,7 @@ "ပိတ်" "စနစ်ထည့်သွင်းရန်" "ဆက်တင်များတွင် စီမံရန်" - "{count,plural, =0{အသုံးပြုနေသော မုဒ်မရှိပါ}=1{{mode} ကို အသုံးပြုနေသည်}other{မုဒ် # ခုကို အသုံးပြုနေသည်}}" + "{count,plural, =0{သုံးနေသော မုဒ်မရှိပါ}=1{{mode} ကို သုံးနေသည်}other{မုဒ် # ခု သုံးနေသည်}}" "နှိုးစက်သံ၊ သတိပေးချက်အသံများ၊ ပွဲစဉ်သတိပေးသံများနှင့် သင်ခွင့်ပြုထားသူများထံမှ ဖုန်းခေါ်မှုများမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။" "နှိုးစက်သံမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။" "စိတ်ကြိုက် ပြုလုပ်ရန်" @@ -577,7 +577,7 @@ "အကြောင်းကြားချက် မရှိပါ" "အကြောင်းကြားချက်သစ် မရှိပါ" "အကြောင်းကြားချက် သတိပေးမှု လျှော့ချခြင်း ဖွင့်ထားသည်" - "အကြောင်းကြားချက်များစွာ တစ်ပြိုင်နက်ရပါက သင့်စက်၏ အသံအတိုးအကျယ်နှင့် သတိပေးချက်ကို ၂ မိနစ်ကြာသည်အထိ အလိုအလျောက်လျှော့ချသည်။" + "အကြောင်းကြားချက်များစွာ တစ်ပြိုင်နက်ရပါက သင့်စက်၏ အသံနှင့် သတိပေးချက်ကို ၂ မိနစ်ကြာသည်အထိ အလိုအလျောက်လျှော့ချသည်။" "ပိတ်ရန်" "အကြောင်းကြားချက်ဟောင်းကြည့်ရန် လော့ခ်ဖွင့်ပါ" "ဤစက်ပစ္စည်းကို သင့်မိဘက စီမံခန့်ခွဲသည်" @@ -1179,6 +1179,10 @@ "%1$d%%" "စပီကာနှင့် ဖန်သားပြင်များ" "အကြံပြုထားသော စက်ပစ္စည်းများ" + + + + "အခြားစက်သို့ မီဒီယာရွှေ့ပြောင်းရန် သင်၏မျှဝေထားသောစက်ရှင်ကို ရပ်ပါ" "ရပ်ရန်" "ထုတ်လွှင့်မှုဆောင်ရွက်ပုံ" @@ -1289,6 +1293,8 @@ "ထည့်ရန်" "အသုံးပြုသူများ စီမံရန်" "ဤအကြောင်းကြားချက်သည် ‘မျက်နှာပြင် ခွဲ၍ပြသခြင်း’ သို့ ဖိဆွဲမှုကို မပံ့ပိုးပါ" + + "Wi‑Fi မရပါ" "ဦးစားပေးမုဒ်" "နိုးစက် သတ်မှတ်ထားသည်" @@ -1345,6 +1351,8 @@ "လော့ခ်မျက်နှာပြင်စိတ်ကြိုက်လုပ်ရန်" "လော့ခ်မျက်နှာပြင် စိတ်ကြိုက်လုပ်ရန် ဖွင့်ပါ" "Wi-Fi မရနိုင်ပါ" + + "ကင်မရာကို ပိတ်ထားသည်" "ကင်မရာနှင့် မိုက်ခရိုဖုန်းကို ပိတ်ထားသည်" "မိုက်ခရိုဖုန်းကို ပိတ်ထားသည်" @@ -1400,7 +1408,8 @@ "တာ့ချ်ပက်လက်ဟန်များ၊ လက်ကွက်ဖြတ်လမ်းများ စသည်တို့ကို လေ့လာပါ" "နောက်သို့ လက်ဟန်" "ပင်မစာမျက်နှာ လက်ဟန်" - "လုပ်ဆောင်ချက်ကီး" + + "ပြီးပြီ" "ပြန်သွားရန်" "နောက်ပြန်သွားရန် တာ့ချ်ပက်ပေါ်ရှိ မည်သည့်နေရာ၌မဆို လက်သုံးချောင်းဖြင့် ဘယ် (သို့) ညာသို့ ပွတ်ဆွဲပါ။\n\n၎င်းအတွက် လက်ကွက်ဖြတ်လမ်း Action + ESC ကိုလည်း သုံးနိုင်သည်။" @@ -1410,6 +1419,14 @@ "ပင်မစာမျက်နှာသို့ အချိန်မရွေးသွားရန် စခရင်အောက်ခြေမှ အပေါ်သို့ လက်သုံးချောင်းဖြင့် ပွတ်ဆွဲပါ။" "ကောင်းသည်။" "ပင်မစာမျက်နှာသို့သွားသည့် လက်ဟန် အပြီးသတ်လိုက်ပါပြီ။" + + + + + + + + "လုပ်ဆောင်ချက်ကီး" "သင့်အက်ပ်များသုံးရန် ကီးဘုတ်ပေါ်ရှိ လုပ်ဆောင်ချက်ကီးကို နှိပ်ပါ။" "ဂုဏ်ယူပါသည်။" @@ -1433,22 +1450,19 @@ "လက်သုံးချောင်းဖြင့် အပေါ်သို့ပွတ်ဆွဲပြီး ဖိထားပါ။ လက်ဟန်များ ပိုမိုလေ့လာရန် တို့ပါ။" "အက်ပ်အားလုံးကြည့်ရန် သင့်ကီးဘုတ်ကို သုံးပါ" "လုပ်ဆောင်ချက်ကီးကို အချိန်မရွေးနှိပ်ပါ။ လက်ဟန်များ ပိုမိုလေ့လာရန် တို့ပါ။" - "ပိုမှိန်ခြင်းသည် တောက်ပမှုဘားတွင် ပါဝင်လာပြီ" - "သင့်စခရင်ထိပ်ဆုံး၌ပင် တောက်ပမှုအဆင့်လျှော့ချခြင်းဖြင့် စခရင်ကို ပိုမှိန်အောင် လုပ်နိုင်ပါပြီ။\n\nသင်သည် မှောင်သောပတ်ဝန်းကျင်၌ရှိချိန် ၎င်းက အကောင်းဆုံးအလုပ်လုပ်သည်။" - "ပိုမှိန်ခြင်း ဖြတ်လမ်း ဖယ်ရှားရန်" - "ပိုမှိန်ခြင်း ဖြတ်လမ်းကို ဖယ်ရှားလိုက်ပြီ။ တောက်ပမှုလျှော့ရန် ပုံမှန် တောက်ပမှုဘားကို အသုံးပြုပါ။" - - - - - - - + - + - + - + + "ချိတ်ဆက်နိုင်မှု" + "အများသုံးနိုင်မှု" + "အထောက်အကူပြု ဆော့ဖ်ဝဲလ်များ" + "ကိုယ်ရေးအချက်အလက် လုံခြုံမှု" + "အက်ပ်များက ပံ့ပိုးထားသည်" + "ဖန်သားပြင်" + "အမျိုးအမည်မသိ" diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 8ccb77618fa184290ffcca0734e49b56322e4e9b..c9656f25e4a0b08b0b23b5707f3e642689b70bd6 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -105,8 +105,7 @@ "Legg til i notat" "Inkluder linken" "%1$s (%2$d)" - - + "Linker kan ikke legges til fra andre profiler" "Skjermopptak" "Behandler skjermopptaket" "Vedvarende varsel for et skjermopptak" @@ -1180,6 +1179,10 @@ "%1$d %%" "Høyttalere og skjermer" "Foreslåtte enheter" + + + + "Stopp den delte økten for å flytte medieinnholdet til en annen enhet" "Stopp" "Slik fungerer kringkasting" @@ -1290,6 +1293,8 @@ "Legg til" "Brukervalg" "Dette varselet støtter ikke at du drar det til en delt skjerm" + + "Wi‑Fi er utilgjengelig" "Prioriteringsmodus" "Alarmen er stilt inn" @@ -1346,6 +1351,8 @@ "Tilpass låseskjermen" "Du må låse opp enheten for å tilpasse låseskjermen" "Wifi er ikke tilgjengelig" + + "Kameraet er blokkert" "Kameraet og mikrofonen er blokkert" "Mikrofonen er blokkert" @@ -1401,7 +1408,8 @@ "Lær deg styreflatebevegelser, hurtigtaster med mer" "Tilbakebevegelse" "Startskjermbevegelse" - "Handlingstast" + + "Ferdig" "Gå tilbake" "For å gå tilbake, sveip mot høyre eller venstre med tre fingre hvor som helst på styreflaten.\n\nDu kan også gjøre dette med hurtigtasten Action + Esc." @@ -1411,6 +1419,14 @@ "For å gå til startskjermen, sveip opp med tre fingre fra bunnen av skjermen når som helst." "Bra!" "Du har fullført bevegelsen for å gå til startskjermen." + + + + + + + + "Handlingstast" "For å åpne appene dine, trykk på handlingstasten på tastaturet." "Gratulerer!" @@ -1434,22 +1450,19 @@ "Sveip opp og hold med tre fingre. Trykk for å lære flere bevegelser." "Bruk tastaturet for å se alle apper" "Trykk på handlingstasten når som helst. Trykk for å lære flere bevegelser." - "Nå er ekstra dimmet en del av lysstyrkeraden" - "Nå kan du gjøre skjermen ekstra dimmet ved å redusere lysstyrkenivået enda mer fra toppen av skjermen.\n\nDette fungerer best i mørke omgivelser." - "Fjern hurtigtasten for ekstra dimmet" - "Hurtigtasten for ekstra dimmet er fjernet. For å redusere lysstyrken kan du bruke den vanlige lysstyrkeraden." - - - - - - - + - + - + - + + "Tilkobling" + "Tilgjengelighet" + "Systemverktøy" + "Personvern" + "Levert av apper" + "Skjerm" + "Ukjent" diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 60e99eaf48b6880cd9eeaff5b4fcf5ce0d84aeb2..16dd82f86a4154ff2ae597e3bb031210ccd18eb3 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -442,7 +442,7 @@ "अफ छ" "सेटअप गर्नुहोस्" "सेटिङमा गई व्यवस्थापन गर्नुहोस्" - "{count,plural, =0{कुनै पनि मोड सक्रिय छैन}=1{{mode} सक्रिय छ}other{# वटा मोड सक्रिय छन्}}" + "{count,plural, =0{कुनै पनि सक्रिय छैन}=1{{mode} सक्रिय छ}other{# मोड सक्रिय छन्}}" "तपाईंलाई अलार्म, रिमाइन्डर, कार्यक्रम र तपाईंले निर्दिष्ट गर्नुभएका कलरहरू बाहेकका ध्वनि र कम्पनहरूले बाधा पुऱ्याउने छैनन्। तपाईंले अझै सङ्गीत, भिडियो र खेलहरू लगायत आफूले प्ले गर्न छनौट गरेका जुनसुकै कुरा सुन्न सक्नुहुनेछ।" "तपाईंलाई अलार्महरू बाहेकका ध्वनि र कम्पनहरूले बाधा पुऱ्याउने छैनन्। तपाईंले अझै सङ्गीत, भिडियो र खेलहरू लगायत आफूले प्ले गर्न छनौट गरेका जुनसुकै कुरा सुन्न सक्नुहुनेछ।" " कस्टम बनाउनुहोस्" @@ -1179,6 +1179,10 @@ "%1$d%%" "स्पिकर तथा डिस्प्लेहरू" "सिफारिस गरिएका डिभाइसहरू" + + + + "मिडिया अर्को डिभाइसमा सार्नका लागि तपाईंले सेयर गरेको सत्र अन्त्य गर्नुहोस्" "रोक्नुहोस्" "प्रसारण गर्ने सुविधाले कसरी काम गर्छ" @@ -1289,6 +1293,7 @@ "हाल्नुहोस्" "प्रयोगकर्ताहरूको व्यवस्थापन गर्नुहोस्" "यो सूचना ड्र्याग गरेर स्प्लिट स्क्रिनमा लैजान मिल्दैन" + "लोकेसन सक्रिय छ" "Wi‑Fi उपलब्ध छैन" "प्राथमिकता मोड" "अलार्म सेट गरिएको छ" @@ -1345,6 +1350,7 @@ "लक स्क्रिन कस्टमाइज गर्नुहोस्" "लक स्क्रिन कस्टमाइज गर्न अनलक गर्नुहोस्" "Wi-Fi उपलब्ध छैन" + "लोकेसन सक्रिय छ" "क्यामेरा ब्लक गरिएको छ" "क्यामेरा र माइक्रोफोन ब्लक गरिएको छ" "माइक्रोफोन ब्लक गरिएको छ" @@ -1400,7 +1406,7 @@ "टचप्याड जेस्चर, किबोर्डका सर्टकट र अन्य कुरा प्रयोग गर्न सिक्नुहोस्" "ब्याक जेस्चर" "होम जेस्चर" - "एक्सन की" + "हालसालै चलाइएका एपहरू हेर्नुहोस्" "सम्पन्न भयो" "पछाडि जानुहोस्" "पछाडि जान तीन वटा औँलाले टचप्याडमा कतै छोएर बायाँ वा दायाँतिर स्वाइप गर्नुहोस्।\n\nतपाईं यसका लागि किबोर्डको सर्टकट \"Action + ESC\" पनि प्रयोग गर्न सक्नुहुन्छ।" @@ -1410,6 +1416,10 @@ "जुनसुकै बेला आफ्नो होम स्क्रिनमा जान स्क्रिनको फेदबाट तीन वटा औँलाले माथितिर स्वाइप गर्नुहोस्।" "राम्रो!" "तपाईंले \"होम स्क्रिनमा जानुहोस्\" नामक जेस्चर प्रयोग गर्ने तरिका सिक्नुभयो।" + "हालसालै चलाइएका एपहरू हेर्नुहोस्" + "तीन वटा औँला प्रयोग गरी टचप्याडमा माथितिर स्वाइप गर्नुहोस् र होल्ड गर्नुहोस्।" + "अद्भुत!" + "तपाईंले हालसालै चलाइएका एपहरू हेर्ने जेस्चर पूरा गर्नुभएको छ।" "एक्सन की" "आफ्ना एपहरू एक्सेस गर्न आफ्नो किबोर्डमा भएको एक्सन की थिच्नुहोस्।" "बधाई छ!" @@ -1433,22 +1443,15 @@ "तिन वटा औँला प्रयोग गरी माथितिर स्वाइप गर्नुहोस् र होल्ड गर्नुहोस्। थप जेस्चर प्रयोग गर्ने तरिका सिक्न ट्याप गर्नुहोस्।" "सबै एपहरू हेर्न आफ्नो किबोर्ड प्रयोग गर्नुहोस्" "जुनसुकै बेला एक्सन की थिच्नुहोस्। थप जेस्चर प्रयोग गर्ने तरिका सिक्न ट्याप गर्नुहोस्।" - "\"अझै मधुरो\" सुविधा अब ब्राइटनेस बारमा समावेश गरिएको छ" - "तपाईं अब आफ्नो स्क्रिनको सिरानबाट चमकको स्तर घटाएर आफ्नो स्क्रिन अझै मधुरो बनाउन सक्नुहुन्छ।\n\nतपाईं अँध्यारो ठाउँमा भएका बेला यो सुविधाले अझ राम्रोसँग काम गर्छ।" - "\"अझै मधुरो\" सर्टकट हटाउनुहोस्" - "\"अझै मधुरो\" सर्टकट हटाइएको छ। स्क्रिनको चमक घटाउन \"रेगुलर ब्राइटनेस बार\" प्रयोग गर्नुहोस्।" - - - - - - - - - - - - - - + "\"अझै मधुरो\" सुविधा अब चमक घटबढ गर्ने स्लाइडरमा समावेश गरिएको छ" + "तपाईं चमकको स्तर अझ बढी घटाएर स्क्रिन अझै मधुरो बनाउन सक्नुहुन्छ।\n\n\"अझै मधुरो\" सुविधा अब चमक घटबढ गर्ने स्लाइडरमा समावेश गरिएकाले यो सुविधाका सर्टकर्टहरू हटाइँदै छन्।" + "\"अझै मधुरो\" सुविधाका सर्टकटहरू हटाउनुहोस्" + "\"अझै मधुरो\" सुविधाका सर्टकटहरू हटाइएका छन्" + "कनेक्टिभिटी" + "सर्वसुलभता" + "युटिलिटी" + "गोपनीयता" + "एपले उपलब्ध गराएका" + "डिस्प्ले" + "अज्ञात" diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 4b43ab173d3a42623c6ae8cff8506bd5c6e95456..b10c2033d2593441eb15e6fc624e901ef118c413 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -577,7 +577,7 @@ "Geen meldingen" "Geen nieuwe meldingen" "Afkoelperiode van meldingen staat aan" - "Je apparaatvolume en meldingen worden automatisch maximaal 2 minuten beperkt als je te veel meldingen tegelijk krijgt." + "Als je te veel meldingen tegelijk krijgt, worden het volume op je apparaat en meldingen automatisch maximaal 2 minuten beperkt." "Uitzetten" "Ontgrendel om oudere meldingen te zien" "Dit apparaat wordt beheerd door je ouder" @@ -1179,6 +1179,10 @@ "%1$d%%" "Speakers en schermen" "Voorgestelde apparaten" + + + + "Stop je gedeelde sessie om media naar een ander apparaat te verplaatsen" "Stoppen" "Hoe uitzenden werkt" @@ -1289,6 +1293,8 @@ "Toevoegen" "Gebruikers beheren" "Deze melding biedt geen ondersteuning voor slepen naar het gesplitste scherm" + + "Wifi niet beschikbaar" "Prioriteitsmodus" "Wekker gezet" @@ -1345,6 +1351,8 @@ "Vergrendelscherm aanpassen" "Ontgrendelen om het vergrendelscherm aan te passen" "Wifi niet beschikbaar" + + "Camera geblokkeerd" "Camera en microfoon geblokkeerd" "Microfoon geblokkeerd" @@ -1400,7 +1408,8 @@ "Leer meer over onder andere touchpadgebaren en sneltoetsen" "Gebaar voor terug" "Gebaar voor startscherm" - "Actietoets" + + "Klaar" "Terug" "Als je wilt teruggaan, swipe je met 3 vingers naar links of rechts op de touchpad.\n\nJe kunt hiervoor ook de sneltoets Actie + ESC gebruiken." @@ -1410,6 +1419,14 @@ "Swipe met 3 vingers omhoog vanaf de onderkant van het scherm om naar het startscherm te gaan." "Mooi zo!" "Je weet nu hoe je het gebaar Naar startscherm maakt." + + + + + + + + "Actietoets" "Als je toegang tot je apps wilt krijgen, druk je op de actietoets op je toetsenbord." "Gefeliciteerd!" @@ -1433,22 +1450,19 @@ "Swipe met 3 vingers omhoog en houd vast. Tik voor meer gebaren." "Je toetsenbord gebruiken om alle apps te bekijken" "Druk op de actietoets wanneer je wilt. Tik voor meer gebaren." - "Extra dimmen maakt nu deel uit van de helderheidsbalk" - "Je kunt het scherm nu extra dimmen door het helderheidsniveau nog verder te verlagen vanaf de bovenkant van het scherm.\n\nDit werkt het beste als je in een donkere omgeving bent." - "Snelkoppeling voor extra dimmen verwijderen" - "Snelkoppeling voor extra dimmen verwijderd. Als je de helderheid wilt verlagen, gebruik je de gewone helderheidsbalk." - - - - - - - + - + - + - + + "Connectiviteit" + "Toegankelijkheid" + "Hulpprogramma\'s" + "Privacy" + "Geleverd door apps" + "Scherm" + "Onbekend" diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index af4ee77c04edd04e88695b453e83c3f66ccb05fd..64b2b7caef33f9adc74a362f10beb617c8e8ff31 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -105,8 +105,7 @@ "ନୋଟରେ ଯୋଗ କରନ୍ତୁ" "ଲିଙ୍କକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରନ୍ତୁ" "%1$s (%2$d)" - - + "ଅନ୍ୟ ପ୍ରୋଫାଇଲରୁ ଲିଙ୍କଗୁଡ଼ିକ ଯୋଗ କରାଯାଇପାରିବ ନାହିଁ" "ସ୍କ୍ରିନ ରେକର୍ଡର" "ସ୍କ୍ରିନ ରେକର୍ଡିଂର ପ୍ରକ୍ରିୟାକରଣ" "ଏକ ସ୍କ୍ରି‍ନ୍‍ ରେକର୍ଡ୍‍ ସେସନ୍‍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି" @@ -1180,6 +1179,10 @@ "%1$d%%" "ସ୍ପିକର ଏବଂ ଡିସପ୍ଲେଗୁଡ଼ିକ" "ପ୍ରସ୍ତାବିତ ଡିଭାଇସଗୁଡ଼ିକ" + + + + "ଅନ୍ୟ ଏକ ଡିଭାଇସକୁ ମିଡିଆ ମୁଭ କରିବା ପାଇଁ ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ସେସନକୁ ବନ୍ଦ କରନ୍ତୁ" "ବନ୍ଦ କରନ୍ତୁ" "ବ୍ରଡକାଷ୍ଟିଂ କିପରି କାମ କରେ" @@ -1290,6 +1293,7 @@ "ଯୋଗ କରନ୍ତୁ" "ୟୁଜରମାନଙ୍କୁ ପରିଚାଳନା କରନ୍ତୁ" "ଏହି ବିଜ୍ଞପ୍ତି ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ଟାଣିବାକୁ ସମର୍ଥନ କରେ ନାହିଁ" + "ଲୋକେସନ ସକ୍ରିୟ ଅଛି" "ୱାଇ-ଫାଇ ଉପଲବ୍ଧ ନାହିଁ" "ପ୍ରାଥମିକତା ମୋଡ" "ଆଲାରାମ ସେଟ" @@ -1346,6 +1350,7 @@ "ଲକ ସ୍କ୍ରିନକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ" "ଲକ ସ୍କ୍ରିନକୁ କଷ୍ଟମାଇଜ କରିବା ପାଇଁ ଅନଲକ କରନ୍ତୁ" "ୱାଇ-ଫାଇ ଉପଲବ୍ଧ ନାହିଁ" + "ଲୋକେସନ ସକ୍ରିୟ ଅଛି" "କେମେରାକୁ ବ୍ଲକ କରାଯାଇଛି" "କେମେରା ଏବଂ ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି" "ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି" @@ -1401,7 +1406,7 @@ "ଟଚପେଡ ଜେଶ୍ଚର, କୀବୋର୍ଡ ସର୍ଟକଟ ଏବଂ ଆହୁରି ଅନେକ କିଛି ବିଷୟରେ ଜାଣନ୍ତୁ" "ବେକ ଜେଶ୍ଚର" "ହୋମ ଜେଶ୍ଚର" - "ଆକ୍ସନ କୀ" + "ବର୍ତ୍ତମାନର ଆପ୍ସ ଭ୍ୟୁ କରନ୍ତୁ" "ହୋଇଗଲା" "ପଛକୁ ଫେରନ୍ତୁ" "ପଛକୁ ଫେରିବା ପାଇଁ ଯେ କୌଣସି ସ୍ଥାନରେ ତିନି ଆଙ୍ଗୁଠି ବ୍ୟବହାର କରି ବାମ କିମ୍ବା ଡାହାଣକୁ ସ୍ୱାଇପ କରନ୍ତୁ।\n\nଏଥିପାଇଁ ଆପଣ କୀବୋର୍ଡ ସର୍ଟକଟ ଆକ୍ସନ + ESC ମଧ୍ୟ ବ୍ୟବହାର କରିପାରିବେ।" @@ -1411,6 +1416,10 @@ "ଯେ କୌଣସି ସମୟରେ ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନକୁ ଯିବା ପାଇଁ ଆପଣଙ୍କ ସ୍କିନର ତଳୁ ତିନୋଟି ଆଙ୍ଗୁଠିରେ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ।" "ବଢ଼ିଆ!" "ଆପଣ \'ହୋମକୁ ଯାଆନ୍ତୁ\' ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" + "ବର୍ତ୍ତମାନର ଆପ୍ସ ଭ୍ୟୁ କରନ୍ତୁ" + "ଆପଣଙ୍କ ଟଚପେଡରେ ତିନୋଟି ଆଙ୍ଗୁଠିକୁ ବ୍ୟବହାର କରି ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ।" + "ବଢ଼ିଆ କାମ!" + "ଆପଣ ବର୍ତ୍ତମାନର ଆପ୍ସ ଜେଶ୍ଚରକୁ ଭ୍ୟୁ କରିବା ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" "ଆକ୍ସନ କୀ" "ଆପଣଙ୍କ ଆପ୍ସ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣଙ୍କର କୀବୋର୍ଡରେ ଆକ୍ସନ କୀ\'କୁ ଦବାନ୍ତୁ।" "ଅଭିନନ୍ଦନ!" @@ -1434,22 +1443,15 @@ "ତିନୋଟି ଆଙ୍ଗୁଠିରେ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ। ଜେଶ୍ଚରଗୁଡ଼ିକ ବିଷୟରେ ଅଧିକ ଜାଣିବାକୁ ଟାପ କରନ୍ତୁ।" "ସମସ୍ତ ଆପ୍ସ ଭ୍ୟୁ କରିବା ପାଇଁ ଆପଣଙ୍କ କୀବୋର୍ଡକୁ ବ୍ୟବହାର କରନ୍ତୁ" "ଯେ କୌଣସି ସମୟରେ ଆକ୍ସନ କୀ\'କୁ ଦବାନ୍ତୁ। ଜେଶ୍ଚରଗୁଡ଼ିକ ବିଷୟରେ ଅଧିକ ଜାଣିବାକୁ ଟାପ କରନ୍ତୁ।" - "ଅତିରିକ୍ତ ଡିମ ବର୍ତ୍ତମାନ ଉଜ୍ଜ୍ୱଳତା ବାରର ଅଂଶ ଅଟେ" - "ବର୍ତ୍ତମାନ ଆପଣ ଆପଣଙ୍କ ସ୍କ୍ରିନର ଶୀର୍ଷରୁ ଉଜ୍ଜ୍ୱଳତାର ଲେଭେଲ ହ୍ରାସ କରି ସ୍କ୍ରିନକୁ ଅତିରିକ୍ତ ଡିମ କରିପାରିବେ।\n\nଆପଣ ଏକ ଡାର୍କ ପରିବେଶରେ ଥିଲେ ଏହା ସବୁଠାରୁ ଭଲ କାମ କରେ।" - "ଅତିରିକ୍ତ ଡିମ ସର୍ଟକଟକୁ କାଢ଼ି ଦିଅନ୍ତୁ" - "ଅତିରିକ୍ତ ଡିମର ସର୍ଟକଟ କାଢ଼ି ଦିଆଯାଇଛି। ଆପଣଙ୍କ ଉଜ୍ଜ୍ୱଳତା ହ୍ରାସ କରିବା ପାଇଁ ନିୟମିତ ଉଜ୍ଜ୍ୱଳତା ବାର ବ୍ୟବହାର କରନ୍ତୁ।" - - - - - - - - - - - - - - + "ଅତିରିକ୍ତ ଡିମ ବର୍ତ୍ତମାନ ଉଜ୍ଜ୍ୱଳତା ସ୍ଲାଇଡରର ଅଂଶ ଅଟେ" + "ବର୍ତ୍ତମାନ ଆପଣ ଉଜ୍ଜ୍ୱଳତାର ଲେଭେଲକୁ ଆହୁରି କମ କରି ସ୍କ୍ରିନକୁ ଅତିରିକ୍ତ ଡିମ କରିପାରିବେ।\n\nଏହି ଫିଚର ବର୍ତ୍ତମାନ ଉଜ୍ଜ୍ୱଳତା ସ୍ଲାଇଡରର ଅଂଶ ହୋଇଥିବା ଯୋଗୁଁ ଅତିରିକ୍ତ ଡିମ ସର୍ଟକଟଗୁଡ଼ିକୁ କାଢ଼ି ଦିଆଯାଉଛି।" + "ଅତିରିକ୍ତ ଡିମ ସର୍ଟକଟକୁ କାଢ଼ି ଦିଅନ୍ତୁ" + "ଅତିରିକ୍ତ ଡିମ ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି" + "କନେକ୍ଟିଭିଟି" + "ଆକ୍ସେସିବିଲିଟୀ" + "ୟୁଟିଲିଟି" + "ଗୋପନୀୟତା" + "ଆପ୍ସ ଦ୍ୱାରା ପ୍ରଦାନ କରାଯାଇଛି" + "ଡିସପ୍ଲେ" + "ଅଜଣା" diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index e6b275a5cef027b3dce4b453041f51400d6676ba..dda36f7b657de8504d50c3b729530ca8c6734412 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -105,8 +105,7 @@ "ਨੋਟ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ" "ਲਿੰਕ ਸ਼ਾਮਲ ਕਰੋ" "%1$s (%2$d)" - - + "ਹੋਰ ਪ੍ਰੋਫਾਈਲਾਂ ਤੋਂ ਲਿੰਕਾਂ ਨੂੰ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ" "ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਰ" "ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਜਾਰੀ ਹੈ" "ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ" @@ -578,7 +577,7 @@ "ਕੋਈ ਸੂਚਨਾ ਨਹੀਂ" "ਕੋਈ ਨਵੀਂ ਸੂਚਨਾ ਨਹੀਂ ਹੈ" "ਨੋਟੀਫ਼ਿਕੇਸ਼ਨ ਕੂਲਡਾਊਨ ਚਾਲੂ ਹੈ" - "ਇੱਕ ਵਾਰ \'ਚ ਕਈ ਸੂਚਨਾਵਾਂ ਮਿਲਣ \'ਤੇ, ਡੀਵਾਈਸ ਦੀ ਅਵਾਜ਼ ਤੇ ਅਲਰਟ ਵੱਧੋ-ਵੱਧ 2 ਮਿੰਟਾਂ ਲਈ ਆਪਣੇ ਆਪ ਘਟ ਹੋ ਜਾਂਦੇ ਹਨ।" + "ਇੱਕ ਵਾਰ \'ਚ ਕਈ ਸੂਚਨਾਵਾਂ ਮਿਲਣ \'ਤੇ, ਡੀਵਾਈਸ ਦੀ ਅਵਾਜ਼ ਅਤੇ ਅਲਰਟ ਵੱਧੋ-ਵੱਧ 2 ਮਿੰਟਾਂ ਲਈ ਆਪਣੇ-ਆਪ ਘੱਟ ਜਾਂਦੇ ਹਨ।" "ਬੰਦ ਕਰੋ" "ਪੁਰਾਣੀਆਂ ਸੂਚਨਾਵਾਂ ਦੇਖਣ ਲਈ ਅਣਲਾਕ ਕਰੋ" "ਇਸ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਬੰਧਨ ਤੁਹਾਡੇ ਮਾਂ-ਪਿਓ ਵੱਲੋਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ" @@ -1180,6 +1179,10 @@ "%1$d%%" "ਸਪੀਕਰ ਅਤੇ ਡਿਸਪਲੇਆਂ" "ਸੁਝਾਏ ਗਏ ਡੀਵਾਈਸ" + + + + "ਮੀਡੀਆ ਨੂੰ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਲਿਜਾਉਣ ਲਈ ਆਪਣੇ ਸਾਂਝੇ ਕੀਤੇ ਸੈਸ਼ਨ ਨੂੰ ਬੰਦ ਕਰੋ" "ਬੰਦ ਕਰੋ" "ਪ੍ਰਸਾਰਨ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ" @@ -1290,6 +1293,7 @@ "ਸ਼ਾਮਲ ਕਰੋ" "ਵਰਤੋਂਕਾਰਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ" "ਇਹ ਸੂਚਨਾ ਸਪਲਿਟ ਸਕ੍ਰੀਨ \'ਤੇ ਘਸੀਟਣ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ ਹੈ" + "ਟਿਕਾਣਾ ਕਿਰਿਆਸ਼ੀਲ ਹੈ" "ਵਾਈ-ਫਾਈ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" "ਤਰਜੀਹੀ ਮੋਡ" "ਅਲਾਰਮ ਸੈੱਟ ਹੈ" @@ -1346,6 +1350,7 @@ "ਲਾਕ ਸਕ੍ਰੀਨ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰੋ" "ਲਾਕ ਸਕ੍ਰੀਨ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਅਣਲਾਕ ਕਰੋ" "ਵਾਈ-ਫਾਈ ਉਪਲਬਧ ਨਹੀਂ" + "ਟਿਕਾਣਾ ਕਿਰਿਆਸ਼ੀਲ ਹੈ" "ਕੈਮਰਾ ਬਲਾਕ ਕੀਤਾ ਗਿਆ" "ਕੈਮਰਾ ਅਤੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤੇ ਗਏ" "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤਾ ਗਿਆ" @@ -1401,7 +1406,7 @@ "ਟੱਚਪੈਡ ਇਸ਼ਾਰੇ, ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਬਾਰੇ ਜਾਣੋ" "ਪਿੱਛੇ ਜਾਣ ਦਾ ਇਸ਼ਾਰਾ" "ਹੋਮ \'ਤੇ ਜਾਣ ਦਾ ਇਸ਼ਾਰਾ" - "ਕਾਰਵਾਈ ਕੁੰਜੀ" + "ਹਾਲੀਆ ਐਪਾਂ ਦੇਖੋ" "ਹੋ ਗਿਆ" "ਵਾਪਸ ਜਾਓ" "ਵਾਪਸ ਜਾਣ ਲਈ, ਟੱਚਪੈਡ \'ਤੇ ਕਿਤੇ ਵੀ ਤਿੰਨ ਉਂਗਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਪਾਸੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ।\n\nਤੁਸੀਂ ਇਸ ਲਈ ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ Action + ESC ਦੀ ਵਰਤੋਂ ਵੀ ਕਰ ਸਕਦੇ ਹੋ।" @@ -1411,6 +1416,10 @@ "ਕਿਸੇ ਵੀ ਸਮੇਂ ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਣ ਲਈ, ਤਿੰਨ ਉਂਗਲਾਂ ਨਾਲ ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ।" "ਵਧੀਆ!" "ਤੁਸੀਂ \'ਹੋਮ \'ਤੇ ਜਾਓ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ।" + "ਹਾਲੀਆ ਐਪਾਂ ਦੇਖੋ" + "ਆਪਣੇ ਟੱਚਪੈਡ \'ਤੇ ਤਿੰਨ ਉਂਗਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ।" + "ਬਹੁਤ ਵਧੀਆ!" + "ਤੁਸੀਂ \'ਹਾਲੀਆ ਐਪਾਂ ਦੇਖੋ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ ਹੈ।" "ਕਾਰਵਾਈ ਕੁੰਜੀ" "ਆਪਣੀਆਂ ਐਪਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਲਈ, ਆਪਣੇ ਕੀ-ਬੋਰਡ \'ਤੇ ਕਾਰਵਾਈ ਕੁੰਜੀ ਨੂੰ ਦਬਾਓ।" "ਵਧਾਈਆਂ!" @@ -1434,22 +1443,15 @@ "ਤਿੰਨ ਉਂਗਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ। ਹੋਰ ਇਸ਼ਾਰਿਆਂ ਨੂੰ ਜਾਣਨ ਲਈ ਟੈਪ ਕਰੋ।" "ਸਾਰੀਆਂ ਐਪਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਆਪਣਾ ਕੀ-ਬੋਰਡ ਵਰਤੋ" "ਕਿਸੇ ਵੀ ਸਮੇਂ ਕਾਰਵਾਈ ਕੁੰਜੀ ਦਬਾਓ। ਹੋਰ ਇਸ਼ਾਰਿਆਂ ਨੂੰ ਜਾਣਨ ਲਈ ਟੈਪ ਕਰੋ।" - "\'ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ\' ਹੁਣ ਚਮਕ ਪੱਟੀ ਦਾ ਹਿੱਸਾ ਹੈ" - "ਤੁਸੀਂ ਹੁਣ ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਸਿਖਰ ਤੋਂ ਚਕਮ ਦੇ ਪੱਧਰ ਨੂੰ ਹੋਰ ਵੀ ਘੱਟ ਕਰ ਕੇ ਸਕ੍ਰੀਨ ਦੀ ਚਮਕ ਨੂੰ ਜ਼ਿਆਦਾ ਘੱਟ ਕਰ ਸਕਦੇ ਹੋ।\n\nਇਹ ਉਦੋਂ ਬਿਹਤਰੀਨ ਕੰਮ ਕਰਦੀ ਹੈ, ਜਦੋਂ ਤੁਸੀਂ ਹਨੇਰੇ ਵਿੱਚ ਹੁੰਦੇ ਹੋ।" - "\'ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ\' ਸ਼ਾਰਟਕੱਟ ਹਟਾਓ" - "\'ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ\' ਸ਼ਾਰਟਕੱਟ ਹਟਾਇਆ ਗਿਆ। ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੀ ਚਕਮ ਨੂੰ ਘੱਟ ਕਰਨ ਲਈ, ਨਿਯਮਿਤ ਚਮਕ ਪੱਟੀ ਦੀ ਵਰਤੋਂ ਕਰੋ।" - - - - - - - - - - - - - - + "\'ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ\' ਹੁਣ ਚਮਕ ਸਲਾਈਡਰ ਦਾ ਹਿੱਸਾ ਹੈ" + "ਤੁਸੀਂ ਹੁਣ ਚਕਮ ਦੇ ਪੱਧਰ ਨੂੰ ਹੋਰ ਵੀ ਘੱਟ ਕਰ ਕੇ ਸਕ੍ਰੀਨ ਦੀ ਚਮਕ ਨੂੰ ਜ਼ਿਆਦਾ ਘੱਟ ਕਰ ਸਕਦੇ ਹੋ।\n\nਕਿਉਂਕਿ ਇਹ ਵਿਸ਼ੇਸ਼ਤਾ ਹੁਣ ਚਮਕ ਸਲਾਈਡਰ ਦਾ ਹਿੱਸਾ ਹੈ, \'ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ\' ਸ਼ਾਰਟਕੱਟ ਹਟਾਏ ਜਾ ਰਹੇ ਹਨ।" + "\'ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ\' ਸ਼ਾਰਟਕੱਟ ਹਟਾਓ" + "\'ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ\' ਸ਼ਾਰਟਕੱਟ ਹਟਾਏ ਗਏ" + "ਕਨੈਕਟੀਵਿਟੀ" + "ਪਹੁੰਚਯੋਗਤਾ" + "ਉਪਯੋਗਤਾਵਾਂ" + "ਪਰਦੇਦਾਰੀ" + "ਐਪਾਂ ਵੱਲੋਂ ਮੁਹੱਈਆ ਕੀਤਾ ਗਿਆ" + "ਡਿਸਪਲੇ" + "ਅਗਿਆਤ" diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 0fefcaecbdb6f3bde2e182f1ae2cea4ae80ad90c..c1cdb58ebab1a9461eb4d61eedb3d3c1c5163388 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -105,8 +105,7 @@ "Dodaj do notatek" "Dołącz link" "%1$s (%2$d)⁠" - - + "Nie można dodawać linków z innych profili" "Nagrywanie ekranu" "Przetwarzam nagrywanie ekranu" "Stałe powiadomienie o sesji rejestrowania zawartości ekranu" @@ -578,7 +577,7 @@ "Brak powiadomień" "Brak nowych powiadomień" "Wyciszanie powiadomień jest włączone" - "Gdy otrzymasz za dużo powiadomień, dźwięk i alerty zostaną automatycznie wyciszone na maks. 2 min." + "Gdy w krótkim czasie otrzymasz za dużo powiadomień, dźwięki zostaną automatycznie wyciszone na maks. 2 min." "Wyłącz" "Odblokuj i zobacz starsze powiadomienia" "Tym urządzeniem zarządza Twój rodzic" @@ -1180,6 +1179,10 @@ "%1$d%%" "Głośniki i wyświetlacze" "Proponowane urządzenia" + + + + "Zatrzymaj udostępnianie sesji, aby przenieść multimedia na inne urządzenie" "Zatrzymaj" "Jak działa transmitowanie" @@ -1290,6 +1293,8 @@ "Dodaj" "Zarządzaj użytkownikami" "To powiadomienie nie obsługuje dzielenia ekranu przez przeciąganie." + + "Sieć Wi‑Fi niedostępna" "Tryb priorytetowy" "Alarm ustawiony" @@ -1346,6 +1351,8 @@ "Dostosuj ekran blokady" "Odblokuj, aby dostosować ekran blokady" "Sieć Wi-Fi jest niedostępna" + + "Kamera jest zablokowana" "Kamera i mikrofon są zablokowane" "Mikrofon jest zablokowany" @@ -1401,7 +1408,8 @@ "Poznaj gesty na touchpada, skróty klawiszowe i inne funkcje" "Gest przejścia wstecz" "Gest przejścia na ekran główny" - "Klawisz działania" + + "Gotowe" "Wróć" "Aby przejść wstecz, przesuń 3 palcami w lewo lub w prawo w dowolnym miejscu touchpada.\n\nMożesz też użyć do tego skrótu klawiszowego Action + ESC." @@ -1411,6 +1419,14 @@ "Aby w dowolnym momencie wyświetlić ekran główny, przesuń od dołu ekranu w górę 3 palcami." "Super!" "Gest przechodzenia na ekran główny został opanowany." + + + + + + + + "Klawisz działania" "Aby uzyskać dostęp do aplikacji, naciśnij klawisz działania na klawiaturze." "Gratulacje!" @@ -1434,22 +1450,19 @@ "Przesuń w górę za pomocą 3 palców i przytrzymaj. Kliknij, aby poznać więcej gestów." "Wyświetlanie wszystkich aplikacji za pomocą klawiatury" "Naciśnij klawisz działania w dowolnym momencie. Kliknij, aby poznać więcej gestów." - "Dodatkowe przyciemnienie jest teraz częścią paska jasności" - "Możesz teraz dodatkowo przyciemnić ekran, jeszcze bardziej zmniejszając poziom jasności u góry ekranu.\n\nTa funkcja sprawdza się najlepiej, gdy jesteś w ciemnym otoczeniu." - "Usuń skrót do dodatkowego przyciemnienia" - "Skrót do dodatkowego przyciemnienia został usunięty. Aby zmniejszyć jasność, użyj standardowego paska jasności." - - - - - - - + - + - + - + + "Łączność" + "Ułatwienia dostępu" + "Narzędzia" + "Prywatność" + "Z aplikacji" + "Wyświetlacz" + "Nieznane" diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 1331ee65a71689b946e5f79dd6f14147367efaf6..fe90562eaf223a79af6fd3b36014652649e9a48b 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -105,8 +105,7 @@ "Incluir anotação" "Incluir link" "%1$s (%2$d)⁠" - - + "Não é possível adicionar links de outros perfis" "Gravador de tela" "Processando gravação de tela" "Notificação contínua para uma sessão de gravação de tela" @@ -578,7 +577,7 @@ "Sem notificações" "Nenhuma notificação nova" "O recurso de atenuar notificações está ativo" - "Volume e alertas do disp. são reduzidos por até 2 min quando você recebe muitas notificações juntas." + "Volume e alertas são reduzidos por até 2 min quando você recebe muitas notificações juntas." "Desativar" "Desbloqueie p/ acessar notificações antigas" "Este dispositivo é gerenciado pelo seu familiar responsável" @@ -1180,6 +1179,10 @@ "%1$d%%" "Alto-falantes e telas" "Opções de dispositivos" + + + + "Interrompa sua sessão compartilhada para transferir mídia a outro dispositivo" "Parar" "Como funciona a transmissão" @@ -1290,6 +1293,8 @@ "Adicionar" "Gerenciar usuários" "Esta notificação não pode ser arrastada para a tela dividida" + + "Wi-Fi indisponível" "Modo de prioridade" "Alarme definido" @@ -1346,6 +1351,8 @@ "Personalizar a tela de bloqueio" "Desbloqueie para personalizar a tela de bloqueio" "Wi-Fi indisponível" + + "Câmara bloqueada" "Câmera e microfone bloqueados" "Microfone bloqueado" @@ -1401,7 +1408,8 @@ "Aprenda gestos do touchpad, atalhos do teclado e muito mais" "Gesto de volta" "Gesto de início" - "Tecla de ação" + + "Concluído" "Voltar" "Para voltar, deslize para a esquerda ou direita usando 3 dedos em qualquer lugar do touchpad.\n\nVocê também pode usar o atalho de teclado Ação + ESC." @@ -1411,6 +1419,14 @@ "Para acessar sua tela inicial a qualquer momento, deslize de baixo para cima na tela com três dedos." "Legal!" "Você concluiu o gesto para acessar a tela inicial." + + + + + + + + "Tecla de ação" "Para acessar os apps, pressione a tecla de ação no teclado." "Parabéns!" @@ -1434,22 +1450,19 @@ "Deslize para cima e pressione com três dedos. Toque para aprender outros gestos." "Use o teclado para ver todos os apps" "Pressione a tecla de ação a qualquer momento. Toque para aprender outros gestos." - "O recurso Escurecer a tela agora faz parte da barra de brilho" - "Agora, na parte de cima, é possível usar o recurso Escurecer a tela, que diminui ainda mais o nível de brilho.\n\nIsso funciona melhor quando você está em um ambiente escuro." - "Remover atalho de Escurecer a tela" - "Atalho de Escurecer a tela removido. Use a barra normal para diminuir o brilho." - - - - - - - + - + - + - + + "Conectividade" + "Acessibilidade" + "Utilitários" + "Privacidade" + "Fornecidos por apps" + "Exibição" + "Desconhecidos" diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index e811fff9782ba66873622cb9795b7552f721c59d..3e9d7824f6faae828189c87748937ab6c2ca4b46 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Altifalantes e ecrãs" "Dispositivos sugeridos" + + + + "Pare a sua sessão partilhada para mover conteúdos multimédia para outro dispositivo" "Parar" "Como funciona a transmissão" @@ -1289,6 +1293,8 @@ "Adicionar" "Gerir utilizadores" "Esta notificação não pode ser arrastada para o ecrã dividido" + + "Wi‑Fi indisponível" "Modo Prioridade" "Alarme definido" @@ -1345,6 +1351,8 @@ "Personalizar ecrã de bloqueio" "Desbloqueie para personalizar o ecrã de bloqueio" "Wi-Fi indisponível" + + "Câmara bloqueada" "Câmara e microfone bloqueados" "Microfone bloqueado" @@ -1400,7 +1408,8 @@ "Aprenda gestos do touchpad, atalhos de teclado e muito mais" "Gesto para retroceder" "Gesto para aceder ao ecrã principal" - "Tecla de ação" + + "Concluir" "Voltar" "Para retroceder, deslize rapidamente para a esquerda ou direita com 3 dedos em qualquer parte do touchpad.\n\nPara o fazer, também pode usar o atalho de teclado Ação + ESC." @@ -1410,6 +1419,14 @@ "Para aceder ao ecrã principal em qualquer altura, deslize rapidamente com 3 dedos de baixo para cima no ecrã." "Boa!" "Concluiu o gesto para aceder ao ecrã principal." + + + + + + + + "Tecla de ação" "Para aceder às suas apps, prima a tecla de ação no teclado." "Parabéns!" @@ -1433,22 +1450,19 @@ "Deslize rapidamente para cima e mantenha premido com 3 dedos. Toque para aprender mais gestos." "Use o teclado para ver todas as apps" "Prima a tecla de ação em qualquer altura. Toque para aprender mais gestos." - "Agora, o escurecimento extra faz parte da barra do brilho" - "Agora, pode tornar o ecrã ainda mais escuro reduzindo ainda mais o nível de brilho a partir da parte superior do ecrã.\n\nIsto funciona melhor quando está num ambiente escuro." - "Remover atalho do escurecimento extra" - "Atalho do escurecimento extra removido. Para reduzir o brilho, use a barra do brilho normal." - - - - - - - + - + - + - + + "Conetividade" + "Acessibilidade" + "Utilitários" + "Privacidade" + "Disponibilizado por apps" + "Ecrã" + "Desconhecido" diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 1331ee65a71689b946e5f79dd6f14147367efaf6..fe90562eaf223a79af6fd3b36014652649e9a48b 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -105,8 +105,7 @@ "Incluir anotação" "Incluir link" "%1$s (%2$d)⁠" - - + "Não é possível adicionar links de outros perfis" "Gravador de tela" "Processando gravação de tela" "Notificação contínua para uma sessão de gravação de tela" @@ -578,7 +577,7 @@ "Sem notificações" "Nenhuma notificação nova" "O recurso de atenuar notificações está ativo" - "Volume e alertas do disp. são reduzidos por até 2 min quando você recebe muitas notificações juntas." + "Volume e alertas são reduzidos por até 2 min quando você recebe muitas notificações juntas." "Desativar" "Desbloqueie p/ acessar notificações antigas" "Este dispositivo é gerenciado pelo seu familiar responsável" @@ -1180,6 +1179,10 @@ "%1$d%%" "Alto-falantes e telas" "Opções de dispositivos" + + + + "Interrompa sua sessão compartilhada para transferir mídia a outro dispositivo" "Parar" "Como funciona a transmissão" @@ -1290,6 +1293,8 @@ "Adicionar" "Gerenciar usuários" "Esta notificação não pode ser arrastada para a tela dividida" + + "Wi-Fi indisponível" "Modo de prioridade" "Alarme definido" @@ -1346,6 +1351,8 @@ "Personalizar a tela de bloqueio" "Desbloqueie para personalizar a tela de bloqueio" "Wi-Fi indisponível" + + "Câmara bloqueada" "Câmera e microfone bloqueados" "Microfone bloqueado" @@ -1401,7 +1408,8 @@ "Aprenda gestos do touchpad, atalhos do teclado e muito mais" "Gesto de volta" "Gesto de início" - "Tecla de ação" + + "Concluído" "Voltar" "Para voltar, deslize para a esquerda ou direita usando 3 dedos em qualquer lugar do touchpad.\n\nVocê também pode usar o atalho de teclado Ação + ESC." @@ -1411,6 +1419,14 @@ "Para acessar sua tela inicial a qualquer momento, deslize de baixo para cima na tela com três dedos." "Legal!" "Você concluiu o gesto para acessar a tela inicial." + + + + + + + + "Tecla de ação" "Para acessar os apps, pressione a tecla de ação no teclado." "Parabéns!" @@ -1434,22 +1450,19 @@ "Deslize para cima e pressione com três dedos. Toque para aprender outros gestos." "Use o teclado para ver todos os apps" "Pressione a tecla de ação a qualquer momento. Toque para aprender outros gestos." - "O recurso Escurecer a tela agora faz parte da barra de brilho" - "Agora, na parte de cima, é possível usar o recurso Escurecer a tela, que diminui ainda mais o nível de brilho.\n\nIsso funciona melhor quando você está em um ambiente escuro." - "Remover atalho de Escurecer a tela" - "Atalho de Escurecer a tela removido. Use a barra normal para diminuir o brilho." - - - - - - - + - + - + - + + "Conectividade" + "Acessibilidade" + "Utilitários" + "Privacidade" + "Fornecidos por apps" + "Exibição" + "Desconhecidos" diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 35d00254bd09a333b68bb0e0d190ffdb8d1078b0..1279ca2246181f4b20d863676ee56579607d361b 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -105,8 +105,7 @@ "Adaugă în notă" "Include linkul" "%1$s (%2$d)⁠" - - + "Nu se pot adăuga linkuri din alte profiluri" "Recorder pentru ecran" "Se procesează înregistrarea" "Notificare în curs pentru o sesiune de înregistrare a ecranului" @@ -1180,6 +1179,10 @@ "%1$d%%" "Difuzoare și ecrane" "Dispozitive sugerate" + + + + "Oprește sesiunea comună ca să muți elementul media pe alt dispozitiv" "Oprește" "Cum funcționează transmisia" @@ -1290,6 +1293,8 @@ "Adaugă" "Gestionează" "Notificarea nu acceptă tragerea pe ecranul împărțit" + + "Wi‑Fi indisponibil" "Modul Prioritate" "Alarmă setată" @@ -1346,6 +1351,8 @@ "Personalizează ecranul de blocare" "Deblochează pentru a personaliza ecranul de blocare" "Conexiune Wi-Fi indisponibilă" + + "Camera foto a fost blocată" "Camera foto și microfonul sunt blocate" "Microfonul a fost blocat" @@ -1401,7 +1408,8 @@ "Învață gesturi pentru touchpad, comenzi rapide de la tastatură și altele" "Gestul Înapoi" "Gestul Ecran de pornire" - "Tastă de acțiuni" + + "Gata" "Înapoi" "Pentru a reveni, glisează spre stânga sau spre dreapta cu trei degete oriunde pe touchpad.\n\nPoți folosi și comanda rapidă de la tastatură Action + ESC pentru aceasta." @@ -1411,6 +1419,14 @@ "Pentru a accesa oricând ecranul de pornire, glisează în sus cu trei degete din partea de jos a ecranului" "Bravo!" "Ai finalizat gestul „accesează ecranul de pornire”." + + + + + + + + "Tasta de acțiuni" "Pentru a accesa aplicațiile, apasă tasta de acțiuni de pe tastatură." "Felicitări!" @@ -1434,22 +1450,19 @@ "Glisează în sus și ține apăsat cu trei degete. Atinge ca să înveți mai multe gesturi." "Folosește-ți tastatura ca să vezi toate aplicațiile" "Apasă oricând tasta de acțiuni. Atinge ca să înveți mai multe gesturi." - "Luminozitatea redusă suplimentar face acum parte din bara de luminozitate" - "Poți reduce suplimentar luminozitatea ecranului dacă scazi nivelul de luminozitate din partea de sus a ecranului.\n\nAcest lucru funcționează cel mai bine într-un mediu întunecat." - "Elimină comanda rapidă de luminozitate redusă suplimentar" - "S-a eliminat comanda rapidă de luminozitate redusă suplimentar. Ca să reduci luminozitatea, folosește bara obișnuită de luminozitate." - - - - - - - + - + - + - + + "Conectivitate" + "Accesibilitate" + "Utilitare" + "Confidențialitate" + "Oferite de aplicații" + "Ecran" + "Necunoscută" diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 2174dde011c0e88588d2ccce49799b608ac46087..557265852d3284e77389a951bfbe185c099dacc9 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -1179,6 +1179,10 @@ "%1$d %%" "Колонки и дисплеи" "Рекомендуемые устройства" + + + + "Чтобы перенести медиафайлы на другое устройство, закройте доступ." "Закрыть" "Как работают трансляции" @@ -1289,6 +1293,8 @@ "Добавить" "Управление пользователями" "Это уведомление нельзя перетаскивать между частями разделенного экрана." + + "Сеть Wi‑Fi недоступна" "Режим приоритета" "Будильник установлен" @@ -1345,6 +1351,8 @@ "Настройки заблок. экрана" "Разблокируйте устройство, чтобы настроить заблокированный экран" "Функция Wi-Fi недоступна" + + "Камера заблокирована" "Камера и микрофон заблокированы" "Микрофон заблокирован" @@ -1400,7 +1408,8 @@ "Узнайте о жестах на сенсорной панели, сочетаниях клавиш и многом другом." "Жест \"назад\"" "Жест \"на главный экран\"" - "Клавиша действия" + + "Готово" "Назад" "Чтобы вернуться назад, проведите по сенсорной панели тремя пальцами влево или вправо.\n\nВы также можете нажать клавишу действия + Esc." @@ -1410,6 +1419,14 @@ "Чтобы перейти на главный экран, проведите снизу вверх тремя пальцами." "Неплохо!" "Вы выполнили жест для перехода на главный экран." + + + + + + + + "Клавиша действия" "Чтобы перейти к приложениям, нажмите клавишу действия на клавиатуре." "Готово!" @@ -1433,22 +1450,19 @@ "Для этого проведите тремя пальцами вверх и удерживайте. Нажмите, чтобы посмотреть другие жесты." "Открывайте список всех приложений с помощью клавиатуры" "Для этого можно использовать клавишу действия. Нажмите, чтобы посмотреть другие жесты." - "Дополнительно уменьшать яркость теперь можно через стандартный ползунок" - "Чтобы дополнительно понизить яркость экрана, откройте настройки в его верхней части.\n\nРекомендуем использовать эту функцию, когда вокруг темно." - "Удалить быструю команду для дополнительного уменьшения яркости" - "Быстрая команда для дополнительного уменьшения яркости удалена. Чтобы изменить уровень яркости, воспользуйтесь стандартным ползунком яркости." - - - - - - - + - + - + - + + "Подключение" + "Специальные возможности" + "Утилиты" + "Конфиденциальность" + "Приложения" + "Экран" + "Неизвестно" diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 167e9cdeb64ed1d145cd0fea330c6d77b36d4acf..b0e322edeae35ae781bc7aa0f8fe8f1ea441a2a2 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -105,8 +105,7 @@ "සටහනට එක් කරන්න" "සබැඳිය ඇතුළත් කරන්න" "%1$s (%2$d)⁠" - - + "වෙනත් පැතිකඩවලින් සබැඳි එක් කළ නොහැක" "තිර රෙකෝඩරය" "තිර පටිගත කිරීම සකසමින්" "තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම" @@ -1180,6 +1179,10 @@ "%1$d%%" "ස්පීකර් සහ සංදර්ශක" "යෝජිත උපාංග" + + + + "මාධ්‍ය වෙනත් උපාංගයකට ගෙන යාමට ඔබේ බෙදා ගත් සැසිය නවත්වන්න" "නවත්වන්න" "විකාශනය ක්‍රියා කරන ආකාරය" @@ -1290,6 +1293,7 @@ "එක් කරන්න" "පරිශීලකයන් කළමනාකරණය කරන්න" "මෙම දැනුම්දීම බෙදුම් තිරය වෙත ඇද ගෙන යාමට සහාය නොදක්වයි." + "ස්ථානය සක්‍රියයි" "Wi‑Fi ලබා ගත නොහැකිය" "ප්‍රමුඛතා ප්‍රකාරය" "සීනුව සකසන ලදි" @@ -1346,6 +1350,7 @@ "අගුළු තිරය අභිරුචිකරණය කරන්න" "අගුළු තිරය අභිරුචිකරණය කිරීමට අගුළු හරින්න" "Wi-Fi ලද නොහැක" + "ස්ථානය සක්‍රියයි" "කැමරාව අවහිරයි" "කැමරාව සහ මයික්‍රොෆෝනය අවහිරයි" "මයික්‍රොෆෝනය අවහිරයි" @@ -1401,7 +1406,7 @@ "ස්පර්ශ පෑඩ් අභිනයන්, යතුරුපුවරු කෙටිමං සහ තවත් දේ ඉගෙන ගන්න" "ආපසු අභිනය" "නිවෙස් අභිනය" - "ක්‍රියා යතුර" + "මෑත යෙදුම් බලන්න" "නිමයි" "ආපස්සට යන්න" "ආපසු යාමට, ස්පර්ශ පුවරුවවේ ඕනෑම තැනක ඇඟිලි තුනක් භාවිතයෙන් වමට හෝ දකුණට ස්වයිප් කරන්න.\n\nඔබට මේ සඳහා යතුරු පුවරු කෙටිමං ක්‍රියාව + ESC ද භාවිත කළ හැක." @@ -1411,6 +1416,10 @@ "ඕනෑම වේලාවක ඔබේ මුල් තිරයට යාමට, ඔබේ තිරයේ පහළ සිට ඇඟිලි තුනකින් ඉහළට ස්වයිප් කරන්න." "කදිමයි!" "ඔබ මුල් පිටුවට යාමේ ඉංගිතය සම්පූර්ණ කළා." + "මෑත යෙදුම් බලන්න" + "ඉහළට ස්වයිප් කර ඔබේ ස්පර්ශ පුවරුව මත ඇඟිලි තුනක් භාවිතා කර සිටින්න." + "අනර්ඝ වැඩක්!" + "ඔබ මෑත යෙදුම් ඉංගිත බැලීම සම්පූර්ණ කර ඇත." "ක්‍රියා යතුර" "ඔබේ යෙදුම් වෙත ප්‍රවේශ වීමට, ඔබේ යතුරු පුවරුවෙහි ක්‍රියා යතුර ඔබන්න." "සුබ පැතුම්!" @@ -1434,22 +1443,15 @@ "ඇඟිලි තුනක් භාවිතයෙන් ඉහළට ස්වයිප් කර අල්ලාගෙන සිටින්න. තව ඉංගිත දැන ගැනීමට තට්ටු කරන්න." "සියලුම යෙදුම් බැලීමට ඔබේ යතුරු පුවරුව භාවිත කරන්න" "ඕනෑම අවස්ථාවක ක්‍රියාකාරී යතුර ඔබන්න. තව ඉංගිත දැන ගැනීමට තට්ටු කරන්න." - "තවත් අඳුර දැන් දීප්ත තීරුවේ කොටසකි" - "ඔබේ තිරයේ ඉහළ සිට දීප්තියේ මට්ටම තවත් අඩු කිරීමෙන් ඔබට දැන් තිරය තවත් අඳුරු කළ හැක.\n\nඔබ අඳුරු පරිසරයක සිටින විට මෙය වඩාත් හොඳින් ක්‍රියා කරයි." - "තවත් අඳුරු කෙටිමඟ ඉවත් කරන්න" - "තවත් අඳුරු කෙටිමඟ ඉවත් කරන ලදි. ඔබේ දීප්තිය අඩු කිරීමට, සාමාන්‍ය දීප්ත තීරුව භාවිත කරන්න." - - - - - - - - - - - - - - + "තවත් අඳුරු දැන් දීප්තියේ ස්ලයිඩරයේ කොටසකි" + "ඔබට දැන් දීප්ති මට්ටම තවත් අඩු කිරීමෙන් තිරය තවත් අඳුරු කළ හැක.\n\nමෙම විශේෂාංගය දැන් දීප්තියේ ස්ලයිඩරයේ කොටසක් බැවින්, අමතර අඳුරු කෙටිමං ඉවත් කරනු ලැබේ." + "තවත් අඳුරු කෙටිමං ඉවත් කරන්න" + "තවත් අඳුරු කෙටිමං ඉවත් කරන ලදි" + "සබැඳුම් හැකියාව" + "ප්‍රවේශ්‍යතාව" + "උපයෝගිතා" + "රහස්‍යතාව" + "යෙදුම් මගින් සපයනු ලැබේ" + "සංදර්ශකය" + "නොදනී" diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index dd872c25eeb554bb06b8b1aa0e174bc74ebb9417..50d98288df2b87ede5e5332faad017dd7292bc20 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -440,7 +440,7 @@ "Zapnuté" "Zapnuté • %1$s" "Vypnuté" - "Nastavenie" + "Nastaviť" "Správa v nastaveniach" "{count,plural, =0{Žiadne aktívne režimy}=1{{mode} je aktívny}few{# režimy sú aktívne}many{# modes are active}other{# režimov je aktívnych}}" "Nebudú vás vyrušovať zvuky ani vibrácie, iba budíky, pripomenutia, udalosti a volajúci, ktorých určíte. Budete naďalej počuť všetko, čo sa rozhodnete prehrať, ako napríklad hudbu, videá a hry." @@ -577,7 +577,7 @@ "Žiadne upozornenia" "Žiadne nové upozornenia" "Stlmenie upozornení je zapnuté" - "Keď dostanete priveľa upozornení naraz, hlasitosť vášho zariadenia a počet upozornení sa automaticky znížia až na dve minúty." + "Keď dostanete priveľa upozornení naraz, až na dve minúty sa zníži hlasitosť zariadenia a upozornenia sa obmedzia." "Vypnúť" "Odomknutím zobrazíte staršie upozornenia" "Toto zariadenie spravuje tvoj rodič" @@ -1179,6 +1179,10 @@ "%1$d %%" "Reproduktory a obrazovky" "Navrhované zariadenia" + + + + "Ak chcete preniesť médiá do iného zariadenia, ukončite zdieľanú reláciu" "Ukončiť" "Ako vysielanie funguje" @@ -1289,6 +1293,8 @@ "Pridať" "Spravovať použ." "Toto upozornenie nepodporuje presun na rozdelenú obrazovku" + + "Wi‑Fi nie je k dispozícii" "Režim priority" "Budík je nastavený" @@ -1345,6 +1351,8 @@ "Prispôsobiť uzamknutú obrazovku" "Uzamknutú obrazovku môžete prispôsobiť po odomknutí" "Wi‑Fi nie je k dispozícii" + + "Kamera je blokovaná" "Kamera a mikrofón sú blokované" "Mikrofón je blokovaný" @@ -1400,7 +1408,8 @@ "Naučte sa gestá touchpadu, klávesové skratky a ďalšie funkcie" "Gesto prechodu späť" "Gesto prechodu domov" - "Akčný kláves" + + "Hotovo" "Prejsť späť" "Ak chcete prejsť späť, potiahnite kdekoľvek na touchpade troma prstami doľava alebo doprava.\n\nMôžete použiť aj klávesovú skratku, teda akčný kláves + ESC." @@ -1410,6 +1419,14 @@ "Na plochu môžete kedykoľvek prejsť potiahnutím troma prstami zdola obrazovky." "Výborne!" "Dokončili ste gesto na prechod na plochu." + + + + + + + + "Akčný kláves" "Ak chcete získať prístup k aplikáciám, stlačte na klávesnici akčný kláves." "Blahoželáme!" @@ -1433,22 +1450,19 @@ "Potiahnite troma prstami nahor a pridržte ich. Viac o gestách sa dozviete klepnutím." "Zobrazte si všetky aplikácie pomocou klávesnice" "Akčný kláves môžete stlačiť kedykoľvek. Viac o gestách sa dozviete klepnutím." - "Mimoriadne stmavenie je teraz súčasťou posúvača na úpravu jasu" - "Teraz môžete obrazovku mimoriadne stmaviť ešte ďalším znížením úrovne jasu v hornej časti obrazovky.\n\nNajlepšie to funguje v tmavom prostredí." - "Odstrániť skratku mimoriadneho stmavenia" - "Skratka mimoriadneho stmavenia bola odstránená. Ak chcete znížiť jas, použite bežný posúvač jasu." - - - - - - - + - + - + - + + "Pripojenie" + "Dostupnosť" + "Utility" + "Ochrana súkromia" + "Poskytnuté aplikáciami" + "Zobrazovanie" + "Neznáme" diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 01ca4e57162dadd3e9774ea6df6f529b233b27be..6452a54dcdf798121d6896dbb285ed5c2dc7d30d 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -1179,6 +1179,10 @@ "%1$d %%" "Zvočniki in zasloni" "Predlagane naprave" + + + + "Ustavi deljeno sejo za premik predstavnosti v drugo napravo." "Ustavi" "Kako deluje oddajanje" @@ -1289,6 +1293,7 @@ "Dodaj" "Upravljaj uporabnike" "To obvestilo ne podpira vlečenja v razdeljen zaslon." + "Lokacija je aktivna" "Wi‑Fi ni na voljo." "Prednostni način" "Alarm je nastavljen." @@ -1345,6 +1350,7 @@ "Prilagajanje zaklenjenega zaslona" "Odklenite za prilagajanje zaklenjenega zaslona" "Wi-Fi ni na voljo." + "Lokacija je aktivna" "Fotoaparat je blokiran." "Fotoaparat in mikrofon sta blokirana." "Mikrofon je blokiran." @@ -1400,7 +1406,7 @@ "Učenje potez na sledilni ploščici, bližnjičnih tipk in drugega" "Poteza za pomik nazaj" "Poteza za začetni zaslon" - "Gumb za dejanje" + "Ogled nedavnih aplikacij" "Končano" "Nazaj" "Za pomik nazaj povlecite levo ali desno s tremi prsti kjer koli na sledilni ploščici.\n\nUporabite lahko tudi bližnjični tipki Action + ESC." @@ -1410,6 +1416,10 @@ "Za pomik na začetni zaslon lahko kadar koli s tremi prsti povlečete navzgor z dna zaslona." "Odlično!" "Izvedli ste potezo za pomik na začetni zaslon." + "Ogled nedavnih aplikacij" + "Na sledilni ploščici s tremi prsti povlecite navzgor in pridržite." + "Odlično!" + "Izvedli ste potezo za ogled nedavnih aplikacij." "Tipka za dejanja" "Za dostop do aplikacij pritisnite tipko za dejanja na tipkovnici." "Čestitamo!" @@ -1433,22 +1443,15 @@ "S tremi prsti povlecite navzgor in pridržite. Dotaknite se, če želite spoznati več potez." "Uporaba tipkovnice za prikaz vseh aplikacij" "Kadar koli pritisnite tipko za dejanja. Dotaknite se, če želite spoznati več potez." - "Funkcija Zelo zatemnjeno je zdaj del vrstice za uravnavanje svetlosti" - "Zdaj lahko zelo zatemnite zaslon tako, da na vrhu zaslona dodatno zmanjšate raven svetlosti.\n\nTa funkcija najbolje deluje v temnem okolju." - "Odstrani bližnjico do funkcije Zelo zatemnjeno" - "Bližnjica do funkcije Zelo zatemnjeno je odstranjena. Če želite zmanjšati svetlost, uporabite običajno vrstico za uravnavanje svetlosti." - - - - - - - - - - - - - - + "Funkcija Zelo zatemnjeno je zdaj del drsnika za svetlost" + "Zdaj lahko zelo zatemnite zaslon tako, da dodatno zmanjšate raven svetlosti.\n\nKer je ta funkcija zdaj del drsnika za svetlost, bodo bližnjice do funkcije Zelo zatemnjeno odstranjene." + "Odstrani bližnjice do funkcije Zelo zatemnjeno" + "Bližnjice do funkcije Zelo zatemnjeno so odstranjene" + "Povezljivost" + "Dostopnost" + "Orodja" + "Zasebnost" + "Zagotavljajo aplikacije" + "Zaslon" + "Neznano" diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index c4f880877ea2863436ce681558ae1aeebccdb29b..7ea8044ce1a8850ecc3f1e6399a684fbfcc8aa59 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -105,8 +105,7 @@ "Shto te shënimi" "Përfshi lidhjen" "%1$s (%2$d)⁠" - - + "Lidhjet nuk mund të shtohen nga profilet e tjera" "Regjistruesi i ekranit" "Regjistrimi i ekranit po përpunohet" "Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit" @@ -1180,6 +1179,10 @@ "%1$d%%" "Altoparlantët dhe ekranet" "Pajisjet e sugjeruara" + + + + "Ndalo sesionin e ndarë për ta zhvendosur median në një pajisje tjetër" "Ndalo" "Si funksionon transmetimi" @@ -1290,6 +1293,7 @@ "Shto" "Menaxho përdoruesit" "Ky njoftim nuk mbështet zvarritjen tek ekrani i ndarë" + "Vendndodhja aktive" "Wi‑Fi nuk ofrohet" "Modaliteti \"Me përparësi\"" "Alarmi është caktuar" @@ -1346,6 +1350,7 @@ "Personalizo ekranin e kyçjes" "Shkyçe për të personalizuar ekranin e kyçjes" "Wi-Fi nuk ofrohet" + "Vendndodhja aktive" "Kamera u bllokua" "Kamera dhe mikrofoni u bllokuan" "Mikrofoni u bllokua" @@ -1401,7 +1406,7 @@ "Mëso gjestet e bllokut me prekje, shkurtoret e tastierës etj." "Gjesti i kthimit prapa" "Gjesti për të shkuar tek ekrani bazë" - "Tasti i veprimit" + "Shiko aplikacionet e fundit" "U krye" "Kthehu prapa" "Për t\'u kthyer, rrëshqit shpejt majtas ose djathtas duke përdorur tri gishta kudo në bllokun me prekje.\n\nPër ta bërë këtë, mund të përdorësh gjithashtu shkurtoren e tastierës \"Action + ESC\"." @@ -1411,6 +1416,10 @@ "Për të shkuar tek ekrani bazë në çdo kohë, rrëshqit shpejt lart me tre gishta nga fundi i ekranit." "Bukur!" "E ke përfunduar gjestin e kalimit tek ekrani bazë." + "Shiko aplikacionet e fundit" + "Rrëshqit shpejt lart dhe mbaj shtypur me tre gishta në bllokun me prekje." + "Punë e shkëlqyer!" + "Përfundove gjestin për shikimin e aplikacioneve të fundit." "Tasti i veprimit" "Për t\'u qasur në aplikacionet e tua, shtyp tastin e veprimit në tastierë." "Urime!" @@ -1434,22 +1443,15 @@ "Rrëshqit shpejt lart dhe mbaj shtypur me tre gishta. Trokit për të mësuar më shumë gjeste." "Përdor tastierën për të shikuar të gjitha aplikacionet" "Shtyp tastin e veprimit në çdo kohë. Trokit për të mësuar më shumë gjeste." - "Modaliteti \"Shumë më i zbehtë\" tani është pjesë e shiritit të ndriçimit" - "Tani mund ta bësh ekranin shumë më të zbehtë duke e ulur nivelin e ndriçimit edhe më tej nga kreu i ekranit.\n\nKjo funksionon më mirë kur je në një mjedis të errët." - "Hiq shkurtoren e modalitetit \"Shumë më i zbehtë\"" - "Shkurtorja e modalitetit \"Shumë më i zbehtë\" u hoq. Për të ulur ndriçimin, përdor shiritin e zakonshëm të ndriçimit." - - - - - - - - - - - - - - + "Modaliteti \"Shumë më i zbehtë\" tani është pjesë e rrëshqitësit të ndriçimit" + "Tani mund ta bësh ekranin shumë më të zbehtë duke e ulur nivelin e ndriçimit edhe më tej.\n\nDuke qenë se kjo veçori tani është pjesë e rrëshqitësit të ndriçimit, shkurtoret e modalitetit \"Shumë më i zbehtë\" janë hequr." + "Hiq shkurtoret e modalitetit \"Shumë më i zbehtë\"" + "Shkurtoret e modalitetit \"Shumë më i zbehtë\" u hoqën" + "Lidhshmëria" + "Qasshmëria" + "Aplikacione utilitare" + "Privatësia" + "Mundësuar nga aplikacionet" + "Ekrani" + "Nuk njihet" diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 66a4915ae3bedda9ede6f187532b5a42a61a8404..1930eade82b4db850364d4c7ce9c5eef5ec3d3d8 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Звучници и екрани" "Предложени уређаји" + + + + "Зауставите дељену сесију да бисте преместили медијски садржај на други уређај" "Заустави" "Како функционише емитовање" @@ -1289,6 +1293,8 @@ "Додај" "Управљаj корисницима" "Ово обавештење не подржава превлачење на подељени екран" + + "WiFi није доступан" "Приоритетни режим" "Аларм је подешен" @@ -1345,6 +1351,8 @@ "Прилагоди закључани екран" "Откључајте да бисте прилагодили закључани екран" "WiFi није доступан" + + "Камера је блокирана" "Камера и микрофон су блокирани" "Микрофон је блокиран" @@ -1400,7 +1408,8 @@ "Научите покрете за тачпед, тастерске пречице и друго" "Покрет за враћање" "Покрет за почетну страницу" - "Тастер радњи" + + "Готово" "Назад" "Да бисте се вратили, превуците улево са три прста било где на тачпеду.\n\nМожете да користите и тастерску пречицу Alt + ESC за ово." @@ -1410,6 +1419,14 @@ "Да бисте отишли на почетни екран у било ком тренутку, превуците нагоре од дна екрана помоћу три прста." "Свака част!" "Довршили сте покрет за повратак на почетну страницу." + + + + + + + + "Тастер радњи" "Да бисте приступили апликацијама, притисните тастер радњи на тастатури." "Честитамо!" @@ -1433,22 +1450,19 @@ "Превуците нагоре и задржите са три прста. Додирните да бисте видели више покрета." "Користите тастатуру да бисте прегледали све апликације" "Притисните тастер радњи у било ком тренутку. Додирните да бисте видели више покрета." - "Додатно затамњивање је сада део траке за осветљеност" - "Сада можете додатно да затамните екран смањивањем нивоа осветљености при врху екрана. \n\nОво најбоље функционише када сте у тамном окружењу." - "Уклони пречицу за додатно затамњивање" - "Уклоњена је пречица за додатно затамњивање. Да бисте смањили осветљеност, користите уобичајену траку за осветљеност." - - - - - - - + - + - + - + + "Повезивање" + "Приступачност" + "Услужне апликације" + "Приватност" + "Обезбеђују апликације" + "Екран" + "Непознато" diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 5ec9e963a15f2fff524e35bc67e480afa9cc6414..a4abb771f9fb31a08d2a75fb6dcc9314f6db710c 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -105,8 +105,7 @@ "Lägg till i anteckning" "Inkludera länk" "%1$s (%2$d)" - - + "Det går inte att lägga till länkar från andra profiler" "Skärminspelare" "Behandlar skärminspelning" "Avisering om att skärminspelning pågår" @@ -577,7 +576,7 @@ "Starta nu" "Inga aviseringar" "Det finns inga nya aviseringar" - "Gradvis sänkning för aviseringar är på" + "Dämpning av aviseringar är på" "Enheten sänker volymen och minimerar aviseringar i upp till två minuter när du får för många aviseringar samtidigt." "Inaktivera" "Lås upp för att se äldre aviseringar" @@ -1180,6 +1179,10 @@ "%1$d %%" "Högtalare och skärmar" "Förslag på enheter" + + + + "Stoppa din delade session för att flytta media till en annan enhet" "Stoppa" "Så fungerar utsändning" @@ -1290,6 +1293,8 @@ "Lägg till" "Välj användare" "Det går inte att dra den här aviseringen till delad skärm" + + "Wifi är inte tillgängligt" "Prioritetsläge" "Alarmet är aktiverat" @@ -1346,6 +1351,8 @@ "Anpassa låsskärmen" "Lås upp för att anpassa låsskärmen" "Wifi är inte tillgängligt" + + "Kameran är blockerad" "Kameran och mikrofonen är blockerade" "Mikrofonen är blockerad" @@ -1401,7 +1408,8 @@ "Lär dig rörelser för styrplattan, kortkommandon med mera" "Tillbaka-rörelse" "Rörelse för att öppna startskärmen" - "Åtgärdstangent" + + "Klar" "Tillbaka" "Gå tillbaka genom att svepa åt vänster eller höger med tre fingrar var som helst på styrplattan.\n\nDu kan även använda kortkommandot Åtgärd + Esc." @@ -1411,6 +1419,14 @@ "Öppna startskärmen när som helst genom att svepa uppåt med tre fingrar från skärmens nederkant." "Bra!" "Du är klar med rörelsen för att öppna startskärmen." + + + + + + + + "Åtgärdstangent" "Tryck på åtgärdstangenten på tangentbordet för att komma åt dina appar." "Grattis!" @@ -1434,22 +1450,19 @@ "Svep uppåt med tre fingrar och håll kvar. Tryck för att lära dig fler rörelser." "Använd tangentbordet för att se alla appar" "Tryck på åtgärdstangenten när som helst. Tryck för att lära dig fler rörelser." - "Extradimmat är nu en del av fältet för ljusstyrka" - "Nu kan du göra skärmen extradimmad genom att sänka ljusstyrkan ännu mer från överst på skärmen.\n\nDetta fungerar bäst när omgivningen är mörk." - "Ta bort kortkommandot för extradimmat" - "Kortkommandot för extradimmat har tagits bort. Använd det vanliga fältet för ljusstyrka om du vill sänka ljusstyrkan." - - - - - - - + - + - + - + + "Anslutning" + "Tillgänglighet" + "Verktyg" + "Integritet" + "Tillhandahålls av appar" + "Skärm" + "Okänt" diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index ababf70103026ca021ccde3d3c1c57516f006640..67ad0b50ba1dd1ab073c8848a46cbaaa12718aac 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -105,8 +105,7 @@ "Ongeza kwenye dokezo" "Jumuisha kiungo" "(%2$d) %1$s" - - + "Huruhusiwi kuweka viungo kutoka kwenye wasifu mwingine" "Kinasa Skrini" "Inachakata rekodi ya skrini" "Arifa inayoendelea ya kipindi cha kurekodi skrini" @@ -441,9 +440,9 @@ "Imewashwa" "Imewashwa • %1$s" "Imezimwa" - "Weka mipangilio" + "Ratibu" "Dhibiti katika mipangilio" - "{count,plural, =0{Hakuna hali za kutumika}=1{Unatumia {mode}}other{Unatumia hali #}}" + "{count,plural, =0{Hakuna hali zinazotumika}=1{Unatumia {mode}}other{Unatumia hali #}}" "Hutasumbuliwa na sauti na mitetemo, isipokuwa kengele, vikumbusho, matukio na simu zinazopigwa na watu uliobainisha. Bado utasikia chochote utakachochagua kucheza, ikiwa ni pamoja na muziki, video na michezo." "Hutasumbuliwa na sauti na mitetemo, isipokuwa kengele. Bado utasikia chochote utakachochagua kucheza, ikiwa ni pamoja na muziki, video na michezo." "Badilisha upendavyo" @@ -1180,6 +1179,10 @@ "%1$d%%" "Spika na Skrini" "Vifaa Vilivyopendekezwa" + + + + "Simamisha kipindi unachoshiriki ili uhamishie maudhui kwenye kifaa kingine" "Simamisha" "Jinsi utangazaji unavyofanya kazi" @@ -1290,6 +1293,8 @@ "Weka" "Dhibiti watumiaji" "Arifa hii haitumii utaratibu wa kuburuta ili kugawa skrini" + + "Wi-Fi haipatikani" "Hali ya kipaumbele" "Kengele imewekwa" @@ -1346,6 +1351,8 @@ "Wekea mapendeleo skrini iliyofungwa" "Fungua ili uweke mapendeleo ya skrini iliyofungwa" "Wi-Fi haipatikani" + + "Kamera imezuiwa" "Kamera na maikrofoni zimezuiwa" "Maikrofoni imezuiwa" @@ -1401,7 +1408,8 @@ "Jifunze kuhusu miguso ya padi ya kugusa, mikato ya kibodi na mengineyo" "Ishara ya kurudi nyuma" "Mguso wa kurudi kwenye skrini ya kwanza" - "Kitufe cha vitendo" + + "Nimemaliza" "Rudi nyuma" "Telezesha vidole vitatu kushoto au kulia mahali popote kwenye padi ya kugusa ili urudi nyuma.\n\nUnaweza pia kutumia mikato ya kibodi ya Kitendo pamoja na ESC kutekeleza kitendo hiki." @@ -1411,6 +1419,14 @@ "Ili uende kwenye skrini ya kwanza wakati wowote, telezesha vidole vitatu juu kutoka sehemu ya chini ya skrini yako." "Safi!" "Umeweka ishara ya kwenda kwenye skrini ya kwanza." + + + + + + + + "Kitufe cha vitendo" "Bonyeza kitufe cha vitendo kwenye kibodi yako ili ufikie programu zako." "Hongera!" @@ -1434,22 +1450,19 @@ "Telezesha vidole vitatu juu na ushikilie. Gusa ili upate maelezo kuhusu miguso zaidi." "Kutumia kibodi yako kuangalia programu zote" "Bonyeza kitufe cha vitendo wakati wowote. Gusa ili upate maelezo kuhusu miguso zaidi." - "Kipunguza mwangaza zaidi sasa ni sehemu ya upau wa mwangaza" - "Sasa unaweza kupunguza mwangaza zaidi kwa kupunguza kabisa kiwango cha mwangaza katika sehemu ya juu ya skrini yako.\n\nMipangilio hii hufanya kazi vyema zaidi ukiwa katika mazingira yenye giza." - "Ondoa njia ya mkato ya kipunguza mwangaza zaidi" - "Njia ya mkato ya kipunguza mwangaza zaidi imeondolewa. Tumia upau wa kawaida wa mwangaza ili upunguze mwangaza wako." - - - - - - - + - + - + - + + "Muunganisho" + "Ufikivu" + "Vipengee" + "Faragha" + "Vinavyotolewa na programu" + "Maonyesho" + "Visivyojulikana" diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index 3efe7a560d9445832089e88605543d40d9929cce..2a27b47e54cad4581fb1caa3ae22c25ce7458216 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -26,10 +26,6 @@ 8dp 0dp - - 864dp - 728dp - 16dp 24dp diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 54ab3c337f94690acbb9b0ffc557dc00ac68afa2..c574912d17d4e5fce223bee0cf1afae16a82c384 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "ஸ்பீக்கர்கள் & டிஸ்ப்ளேக்கள்" "பரிந்துரைக்கப்படும் சாதனங்கள்" + + + + "மீடியாவை வேறொரு சாதனத்திற்கு மாற்ற \'பகிரப்படும் அமர்வை\' நிறுத்தவும்" "நிறுத்து" "பிராட்காஸ்ட் எவ்வாறு செயல்படுகிறது?" @@ -1289,6 +1293,8 @@ "சேர்" "பயனர்களை நிர்வகித்தல்" "பிரிக்கப்பட்ட திரைக்குள் இந்த அறிவிப்பை இழுத்துவிட முடியாது" + + "வைஃபை கிடைக்கவில்லை" "முன்னுரிமைப் பயன்முறை" "அலாரம் அமைக்கப்பட்டுள்ளது" @@ -1345,6 +1351,8 @@ "பூட்டுத் திரையை பிரத்தியேகமாக்கு" "பூட்டுத் திரையைப் பிரத்தியேகப்படுத்த அன்லாக் செய்யுங்கள்" "வைஃபை கிடைக்கவில்லை" + + "கேமரா தடுக்கப்பட்டுள்ளது" "கேமராவும் மைக்ரோஃபோனும் தடுக்கப்பட்டுள்ளன" "மைக்ரோஃபோன் தடுக்கப்பட்டுள்ளது" @@ -1400,7 +1408,8 @@ "டச்பேட் சைகைகள், கீபோர்டு ஷார்ட்கட்கள் மற்றும் பலவற்றைத் தெரிந்துகொள்ளுங்கள்" "பின்செல்வதற்கான சைகை" "முகப்பிற்குச் செல்வதற்கான சைகை" - "ஆக்ஷன் பட்டன்" + + "முடிந்தது" "பின்செல்" "பின்செல்ல, உங்கள் டச்பேடில் எங்கு வேண்டுமானாலும் இடது அல்லது வலதுபுறமாக மூன்று விரல்களால் ஸ்வைப் செய்யவும்.\n\nஇதற்கு நீங்கள் கீபோர்டு ஷார்ட்கட் செயல்பாடுகள் + Esc பட்டனையும் பயன்படுத்தலாம்." @@ -1410,6 +1419,14 @@ "எப்போது வேண்டுமானாலும் உங்கள் முகப்புத் திரைக்குச் செல்ல, மூன்று விரல்களால் திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யவும்." "அற்புதம்!" "முகப்புக்குச் செல்வதற்கான சைகையை நிறைவுசெய்துவிட்டீர்கள்." + + + + + + + + "ஆக்‌ஷன் பட்டன்" "ஆப்ஸை அணுக உங்கள் கீபோர்டில் உள்ள ஆக்‌ஷன் பட்டனை அழுத்துங்கள்." "வாழ்த்துகள்!" @@ -1433,22 +1450,19 @@ "மூன்று விரல்களால் மேல்நோக்கி ஸ்வைப் செய்து பிடிக்கவும். சைகைகள் குறித்து மேலும் அறிய தட்டவும்." "அனைத்து ஆப்ஸையும் பார்க்க உங்கள் கீபோர்டைப் பயன்படுத்துங்கள்" "எப்போது வேண்டுமானாலும் ஆக்ஷன் பட்டனை அழுத்தலாம். சைகைகள் குறித்து மேலும் அறிய தட்டவும்." - "\'மிகக் குறைவான வெளிச்சம்\' அம்சம் இப்போது ஒளிர்வுப் பட்டியின் ஒரு பகுதியாகும்" - "இப்போது உங்கள் திரையின் மேற்பகுதியில் ஒளிர்வு அளவைக் குறைப்பதன் மூலம் திரையை மிகக் குறைவான வெளிச்சத்திற்குக் கொண்டு வரலாம்.\n\nஇருட்டான சூழலில் இருக்கும்போது இது சிறப்பாகச் செயல்படும்." - "மிகக் குறைவான வெளிச்சத்திற்கான ஷார்ட்கட்டை அகற்று" - "மிகக் குறைவான வெளிச்சத்திற்கான ஷார்ட்கட் அகற்றப்பட்டது. உங்கள் ஒளிர்வைக் குறைக்க, வழக்கமான ஒளிர்வுப் பட்டியைப் பயன்படுத்துங்கள்." - - - - - - - + - + - + - + + "இணைப்புநிலை" + "மாற்றுத்திறன் வசதி" + "யூட்டிலிட்டி சேவைகள்" + "தனியுரிமை" + "ஆப்ஸ் வழங்குபவை" + "டிஸ்ப்ளே" + "தெரியவில்லை" diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 2c4109a8fd9e7fa132e50307d69484d9a40f8ff2..f4bf055599bdfb273ebbe5db5e23601d256bf84e 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -125,7 +125,7 @@ "స్క్రీన్ రికార్డింగ్ చేయబడుతోంది" "స్క్రీన్, ఆడియో రికార్డింగ్ చేయబడుతున్నాయి" "స్క్రీన్‌పై తాకే స్థానాలను చూపండి" - "ఆపివేయి" + "ఆపివేయండి" "షేర్ చేయండి" "స్క్రీన్ రికార్డింగ్ సేవ్ చేయబడింది" "చూడటానికి ట్యాప్ చేయండి" @@ -577,7 +577,7 @@ "నోటిఫికేషన్‌లు లేవు" "కొత్త నోటిఫికేషన్‌లు ఏవీ లేవు" "నోటిఫికేషన్ కూల్‌డౌన్ ఆన్ అయింది" - "ఒకేసారి పలు నోటిఫికేషన్‌లు వస్తే, పరికర వాల్యూమ్, అలర్ట్‌లు ఆటోమేటిక్‌గా 2 నిమిషాలకు తగ్గించబడతాయి." + "ఒకేసారి పలు నోటిఫికేషన్లు వస్తే, పరికర వాల్యూమ్, అలర్ట్స్ ఆటోమేటిగ్గా 2 నిమిషాలకు తగ్గించబడతాయి." "ఆఫ్ చేయండి" "పాత నోటిఫికేషన్‌ల కోసం అన్‌లాక్ చేయండి" "ఈ పరికరాన్ని మీ తల్లి/తండ్రి మేనేజ్ చేస్తున్నారు" @@ -1179,6 +1179,10 @@ "%1$d%%" "స్పీకర్‌లు & డిస్‌ప్లేలు" "సూచించబడిన పరికరాలు" + + + + "మీడియాను మరొక పరికరానికి తరలించడానికి మీ షేర్ చేసిన సెషన్‌ను ఆపండి" "ఆపండి" "ప్రసారం కావడం అనేది ఎలా పని చేస్తుంది" @@ -1289,6 +1293,7 @@ "జోడించండి" "యూజర్‌లను మేనేజ్ చేయండి" "ఈ నోటిఫికేషన్ స్ప్లిట్ స్క్రీన్‌కు లాగడాన్ని సపోర్ట్ చేయదు" + "లొకేషన్ యాక్టివ్‌గా ఉంది" "Wi‑Fi అందుబాటులో లేదు" "ముఖ్యమైన ఫైల్స్ మోడ్" "అలారం సెట్ చేశాను" @@ -1345,6 +1350,7 @@ "లాక్ స్క్రీన్ అనుకూలంగా మార్చండి" "లాక్ స్క్రీన్‌ను అనుకూలంగా మార్చుకోవడానికి అన్‌లాక్ చేయండి" "Wi-Fi అందుబాటులో లేదు" + "లొకేషన్ యాక్టివ్‌గా ఉంది" "కెమెరా బ్లాక్ చేయబడింది" "కెమెరా, మైక్రోఫోన్ బ్లాక్ చేయబడ్డాయి" "మైక్రోఫోన్ బ్లాక్ చేయబడింది" @@ -1400,7 +1406,7 @@ "టచ్‌ప్యాడ్ సంజ్ఞలు, కీబోర్డ్ షార్ట్‌కట్‌లు, అలాగే మరిన్నింటిని గురించి తెలుసుకోండి" "వెనుకకు పంపే సంజ్ఞ" "హోమ్‌కు పంపే సంజ్ఞ" - "యాక్షన్ కీ" + "ఇటీవలి యాప్‌లను చూడండి" "పూర్తయింది" "వెనుకకు" "వెనుకకు వెళ్లడానికి, టచ్‌ప్యాడ్‌లో ఎక్కడైనా మూడు వేళ్లను ఉపయోగించి ఎడమ లేదా కుడి వైపునకు స్వైప్ చేయండి.\n\nమీరు దీని కోసం + ESC కీబోర్డ్ షార్ట్‌కట్ యాక్షన్‌ను కూడా ఉపయోగించవచ్చు." @@ -1410,6 +1416,10 @@ "ఏ సమయంలోనైనా మీ మొదటి స్క్రీన్‌కు వెళ్లడానికి, మీ స్క్రీన్ కింది నుండి మూడు వేళ్లతో పైకి స్వైప్ చేయండి." "సూపర్!" "మొదటి స్క్రీన్‌కు వెళ్ళడానికి ఉపయోగించే సంజ్ఞకు సంబంధించిన ట్యుటోరియల్‌ను మీరు పూర్తి చేశారు." + "ఇటీవలి యాప్‌లను చూడండి" + "మీ టచ్‌ప్యాడ్‌లో మూడు వేళ్లను ఉపయోగించి పైకి స్వైప్ చేసి, హోల్డ్ చేయండి." + "పనితీరు అద్భుతంగా ఉంది!" + "మీరు ఇటీవలి యాప్‌ల వీక్షణ సంజ్ఞను పూర్తి చేశారు." "యాక్షన్ కీ" "మీ యాప్‌లను యాక్సెస్ చేయడానికి, మీ కీబోర్డ్‌లో యాక్షన్ కీని నొక్కండి." "అభినందనలు!" @@ -1433,22 +1443,15 @@ "మూడు వేళ్లతో పైకి స్వైప్ చేసి, హోల్డ్ చేయండి. మరిన్ని సంజ్ఞల గురించి తెలుసుకోవడానికి ట్యాప్ చేయండి." "యాప్‌లన్నింటినీ చూడటానికి మీ కీబోర్డ్‌ను ఉపయోగించండి" "ఏ సమయంలోనైనా యాక్షన్ కీని నొక్కండి. మరిన్ని సంజ్ఞల గురించి తెలుసుకోవడానికి ట్యాప్ చేయండి." - "కాంతిని మరింత డిమ్ చేసే ఫీచర్ ఇప్పుడు బ్రైట్‌నెస్ బార్‌లో ఒక భాగం" - "మీరు ఇప్పుడు మీ స్క్రీన్ పైభాగం నుండి బ్రైట్‌నెస్ స్థాయిని తగ్గించడం ద్వారా కూడా స్క్రీన్ కాంతిని మరింత డిమ్ చేయవచ్చు.\n\nమీరు డార్క్ ఎన్విరాన్‌మెంట్‌లో ఉన్నప్పుడు కూడా ఇది బాగా పని చేస్తుంది." - "కాంతిని మరింత డిమ్ చేసే షార్ట్‌కట్‌ను తీసివేయండి" - "కాంతిని మరింత డిమ్ చేసే షార్ట్‌కట్ తీసివేయబడింది. మీ బ్రైట్‌నెస్‌ను తగ్గించడానికి, సాధారణ బ్రైట్‌నెస్ బార్‌ను ఉపయోగించండి." - - - - - - - - - - - - - - + "కాంతిని మరింత డిమ్ చేసే ఫీచర్ ఇప్పుడు బ్రైట్‌నెస్ స్లయిడర్‌లో ఒక భాగం" + "మీరు ఇప్పుడు బ్రైట్‌నెస్ స్థాయిని మరింత తగ్గించడం ద్వారా స్క్రీన్‌ను కాంతిని మరింత డిమ్ చేయవచ్చు.\n\nఈ ఫీచర్ ఇప్పుడు బ్రైట్‌నెస్ స్లయిడర్‌లో భాగం కాబట్టి, కాంతిని మరింత డిమ్ చేసే షార్ట్‌కట్‌లు తీసివేయబడుతున్నాయి." + "కాంతిని మరింత డిమ్ చేసే షార్ట్‌కట్‌లను తీసివేయండి" + "కాంతిని మరింత డిమ్ చేసే షార్ట్‌కట్‌లు తీసివేయబడ్డాయి" + "కనెక్టివిటీ" + "Accessibility" + "యుటిలిటీలు" + "గోప్యత" + "యాప్‌ల ద్వారా అందించబడినవి" + "డిస్‌ప్లే" + "తెలియదు" diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 20e730c2df57d0875850882d5befd1b8afc510b1..42248317ffe5bcfa38c3cec09a7c93dbec450534 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "ลำโพงและจอแสดงผล" "อุปกรณ์ที่แนะนำ" + + + + "หยุดเซสชันที่แชร์อยู่เพื่อย้ายสื่อไปยังอุปกรณ์อื่น" "หยุด" "วิธีการทำงานของการออกอากาศ" @@ -1289,6 +1293,8 @@ "เพิ่ม" "จัดการผู้ใช้" "การแจ้งเตือนนี้ไม่รองรับการลากเพื่อแยกหน้าจอ" + + "ใช้ Wi‑Fi ไม่ได้" "โหมดลำดับความสำคัญ" "ตั้งปลุกแล้ว" @@ -1345,6 +1351,8 @@ "ปรับแต่งหน้าจอล็อก" "ปลดล็อกเพื่อปรับแต่งหน้าจอล็อก" "Wi-Fi ไม่พร้อมใช้งาน" + + "กล้องถูกบล็อกอยู่" "กล้องและไมโครโฟนถูกบล็อกอยู่" "ไมโครโฟนถูกบล็อกอยู่" @@ -1400,7 +1408,8 @@ "ดูข้อมูลเกี่ยวกับท่าทางสัมผัสของทัชแพด แป้นพิมพ์ลัด และอื่นๆ" "ท่าทางสัมผัสสำหรับย้อนกลับ" "ท่าทางสัมผัสสำหรับหน้าแรก" - "ปุ่มดำเนินการ" + + "เสร็จสิ้น" "ย้อนกลับ" "หากต้องการย้อนกลับ ให้ใช้ 3 นิ้วปัดไปทางซ้ายหรือขวาที่ใดก็ได้บนทัชแพด\n\nหรือใช้การดำเนินการแป้นพิมพ์ลัด + ESC ได้เช่นเดียวกัน" @@ -1410,6 +1419,14 @@ "ใช้ 3 นิ้วปัดขึ้นจากด้านล่างของหน้าจอเพื่อไปที่หน้าจอหลักได้ทุกเมื่อ" "ดีมาก" "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าแรกเสร็จแล้ว" + + + + + + + + "ปุ่มดำเนินการ" "หากต้องการเข้าถึงแอป ให้กดปุ่มดำเนินการบนแป้นพิมพ์" "ยินดีด้วย" @@ -1433,22 +1450,19 @@ "ใช้ 3 นิ้วปัดขึ้นแล้วค้างไว้ แตะเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับท่าทางสัมผัสต่างๆ" "ใช้แป้นพิมพ์เพื่อดูแอปทั้งหมด" "กดปุ่มดำเนินการได้ทุกเมื่อ แตะเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับท่าทางสัมผัสต่างๆ" - "ตอนนี้การหรี่แสงเพิ่มเติมเป็นส่วนหนึ่งของแถบความสว่างแล้ว" - "ตอนนี้คุณสามารถหรี่แสงหน้าจอเพิ่มเติมได้โดยลดระดับความสว่างจากด้านบนของหน้าจอมากขึ้น\n\nฟีเจอร์นี้จะทำงานได้ดีเมื่อคุณอยู่ในที่มืด" - "นำทางลัดหรี่แสงเพิ่มเติมออก" - "นำทางลัดหรี่แสงเพิ่มเติมออกแล้ว หากต้องการลดความสว่าง ให้ใช้แถบความสว่างปกติ" - - - - - - - + - + - + - + + "การเชื่อมต่อ" + "การช่วยเหลือพิเศษ" + "ยูทิลิตี" + "ความเป็นส่วนตัว" + "ให้บริการโดยแอป" + "จอแสดงผล" + "ไม่ทราบ" diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 0bef7436269ca113e4308471f5bf926c56560171..5145f26a886a165fb4f3d9f64de07161d4d913bb 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -1179,6 +1179,10 @@ "%1$d%%" "Mga Speaker at Display" "Mga Iminumungkahing Device" + + + + "Ihinto ang iyong nakabahaging session para maglipat ng media sa ibang device" "Ihinto" "Paano gumagana ang pag-broadcast" @@ -1289,6 +1293,8 @@ "Magdagdag" "Pamahalaan ang mga user" "Hindi sinusuportahan ng notification na ito ang pag-drag sa split screen" + + "Hindi available ang Wi‑Fi" "Priority mode" "Nakatakda ang alarm" @@ -1345,6 +1351,8 @@ "I-customize ang lock screen" "I-unlock para i-customize ang lock screen" "Hindi available ang Wi-Fi" + + "Naka-block ang camera" "Naka-block ang camera at mikropono" "Naka-block ang mikropono" @@ -1400,7 +1408,8 @@ "Matuto ng mga galaw sa touchpad, keyboard shortcut, at higit pa" "Galaw para bumalik" "Galaw para sa Home" - "Action key" + + "Tapos na" "Bumalik" "Para bumalik, mag-swipe pakaliwa o pakanan gamit ang tatlong daliri saanman sa touchpad.\n\nPuwede mo ring gamitin ang keyboard shortcut na Action + ESC para dito." @@ -1410,6 +1419,14 @@ "Para pumunta sa iyong home screen anumang oras, mag-swipe pataas gamit ang tatlong daliri mula sa ibaba ng screen. mo." "Magaling!" "Nakumpleto mo na ang galaw para pumunta sa home." + + + + + + + + "Action key" "Para ma-access ang iyong mga app, pindutin ang action key sa keyboard mo." "Binabati kita!" @@ -1433,22 +1450,19 @@ "Mag-swipe pataas at i-hold gamit ang tatlong daliri. I-tap para matuto pa tungkol sa mga galaw." "Gamitin ang iyong keyboard para tingnan ang lahat ng app" "Pindutin ang action key kahit kailan. I-tap para matuto pa tungkol sa mga galaw." - "Bahagi na ng brightness bar ang extra dim" - "Puwede mo nang gawing extra dim ang screen sa pamamagitan ng pagpapababa ng level ng liwanag nang higit pa mula sa itaas ng iyong screen.\n\nPinakamahusay itong gumagana kapag nasa madilim na kapaligiran ka." - "Alisin ang shortcut ng extra dim" - "Inalis ang shortcut ng extra dim. Para bawasan ang liwanag, gamitin ang karaniwang bar ng liwanag." - - - - - - - + - + - + - + + "Pagkakonekta" + "Accessibility" + "Mga Utility" + "Privacy" + "Ibinibigay ng mga app" + "Display" + "Hindi Alam" diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 060ecd1010ebf2f7119a3c17f3a4ab46e4dd7103..63c9a3c1672d8f724a971c05f646739963535c2c 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -105,8 +105,7 @@ "Nota ekle" "Bağlantıyı dahil et" "%1$s (%2$d)" - - + "Başka profillerden bağlantı eklenemez" "Ekran Kaydedicisi" "Ekran kaydı işleniyor" "Ekran kaydı oturumu için devam eden bildirim" @@ -1180,6 +1179,10 @@ "%%%1$d" "Hoparlörler ve Ekranlar" "Önerilen Cihazlar" + + + + "Medyayı başka bir cihaza taşımak için paylaşılan oturumunuzu durdurun" "Durdur" "Yayınlamanın işleyiş şekli" @@ -1290,6 +1293,8 @@ "Ekle" "Kullanıcıları yönet" "Bu bildirim bölünmüş ekrana sürüklemeyi desteklemiyor" + + "Kablosuz kullanılamıyor" "Öncelik modu" "Alarm kuruldu" @@ -1346,6 +1351,8 @@ "Kilit ekranını özelleştir" "Kilit ekranını özelleştirmek için kilidi açın" "Kablosuz bağlantı kullanılamıyor" + + "Kamera engellendi" "Kamera ve mikrofon engellendi" "Mikrofon engellendi" @@ -1401,7 +1408,8 @@ "Dokunmatik alan hareketlerini, klavye kısayollarını ve daha fazlasını öğrenin" "Geri hareketi" "Ana sayfa hareketi" - "Eylem tuşu" + + "Bitti" "Geri dön" "Geri dönmek için dokunmatik alanın herhangi bir yerinde üç parmağınızla sola veya sağa kaydırın.\n\nDilerseniz işlem düğmesi + Esc klavye kısayolunu kullanarak da geri dönebilirsiniz." @@ -1411,6 +1419,14 @@ "İstediğiniz zaman ana ekrana gitmek için üç parmağınızla ekranınızın altından yukarı doğru kaydırın." "Güzel!" "Ana ekrana git hareketini tamamladınız." + + + + + + + + "Eylem tuşu" "Uygulamalarınıza erişmek için klavyenizdeki eylem tuşuna basın." "Tebrikler!" @@ -1434,22 +1450,19 @@ "Üç parmağınızla yukarı kaydırıp basılı tutun. Daha fazla hareket öğrenmek için dokunun." "Tüm uygulamaları görüntülemek için klavyenizi kullanın" "İstediğiniz zaman eylem tuşuna basın. Daha fazla hareket öğrenmek için dokunun." - "Ekstra loş özelliği, parlaklık çubuğuna eklendi" - "Artık ekranınızın üst kısmından parlaklık seviyesini daha da düşürerek ekranı ekstra loş hale getirebilirsiniz.\n\nBu özellik, karanlık ortamdayken en iyi sonucu verir." - "Ekstra loş kısayolunu kaldır" - "Ekstra loş kısayolu kaldırıldı. Parlaklık seviyesini düşürmek için normal parlaklık çubuğunu kullanın." - - - - - - - + - + - + - + + "Bağlantı" + "Erişilebilirlik" + "Yardımcı programlar" + "Gizlilik" + "Uygulamalar tarafından sağlanır" + "Ekran" + "Bilinmiyor" diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index ae07ae26d0f0a77c9704e1314fae4c63299abee4..98ed6c0470c5f1d13a6e5d4caac030ce0534cee9 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -105,8 +105,7 @@ "Додати до примітки" "Додати посилання" "%1$s (%2$d)" - - + "Не можна додавати посилання з інших профілів" "Запис відео з екрана" "Обробка записування екрана" "Сповіщення про сеанс запису екрана" @@ -1180,6 +1179,10 @@ "%1$d%%" "Колонки й екрани" "Пропоновані пристрої" + + + + "Зупиніть сеанс спільного доступу, щоб перенести медіаконтент на інший пристрій" "Зупинити" "Як працює трансляція" @@ -1290,6 +1293,8 @@ "Додати" "Керувати користувачами" "Це сповіщення не підтримує режим розділеного екрана" + + "Мережа Wi-Fi недоступна" "Режим пріоритету" "Будильник установлено" @@ -1346,6 +1351,8 @@ "Налаштувати заблокований екран" "Розблокуйте, щоб налаштувати заблокований екран" "Мережа Wi-Fi недоступна" + + "Камеру заблоковано" "Камеру й мікрофон заблоковано" "Мікрофон заблоковано" @@ -1401,7 +1408,8 @@ "Жести для сенсорної панелі, комбінації клавіш тощо: докладніше" "Жест \"Назад\"" "Жест переходу на головний екран" - "Клавіша дії" + + "Готово" "Назад" "Щоб перейти назад, проведіть трьома пальцями вліво або вправо по сенсорній панелі.\n\nТакож можна скористатися комбінацією \"клавіша дії\" + ESC." @@ -1411,6 +1419,14 @@ "Щоб будь-коли перейти на головний екран, проведіть трьома пальцями вгору від нижнього краю екрана." "Чудово!" "Ви виконали жест переходу на головний екран." + + + + + + + + "Клавіша дії" "Щоб переглянути додатки, натисніть клавішу дії на клавіатурі." "Вітаємо!" @@ -1434,22 +1450,19 @@ "Проведіть трьома пальцями вгору й утримуйте їх на екрані. Натисніть, щоб дізнатися про інші жести." "Щоб переглянути всі додатки, використовуйте клавіатуру" "Будь-коли натисніть клавішу дії. Натисніть, щоб дізнатися про інші жести." - "Тепер на панелі регулювання яскравості є функція додаткового зменшення яскравості" - "Тепер ви можете зробити екран ще темнішим, додатково зменшуючи рівень яскравості вгорі екрана.\n\nНайкраще ця функція працює, коли ви перебуваєте в темному місці." - "Видалити комбінацію клавіш для додаткового зменшення яскравості" - "Комбінацію клавіш для додаткового зменшення яскравості видалено. Щоб зменшити яскравість, використовуйте стандартну панель регулювання." - - - - - - - + - + - + - + + "Обмін даними" + "Функції доступності" + "Утиліти" + "Конфіденційність" + "Надано додатками" + "Екран" + "Невідомо" diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 5d1f66e364ef37cbcfa8969611edfabf41c3d804..cd57d52a20aa879382e7e7d8ab18bc7e67a57ecb 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -1179,6 +1179,10 @@ "%%%1$d" "اسپیکرز اور ڈسپلیز" "تجویز کردہ آلات" + + + + "میڈیا کو دوسرے آلے پر منتقل کرنے کے لیے اپنا مشترکہ سیشن بند کریں" "بند کریں" "براڈکاسٹنگ کیسے کام کرتا ہے" @@ -1289,6 +1293,7 @@ "شامل کریں" "صارفین کا نظم کریں" "یہ اطلاع اسپلٹ اسکرین پر گھسیٹنے کو سپورٹ نہیں کرتی ہے" + "مقام فعال ہے" "‏Wi-Fi دستیاب نہیں ہے" "ترجیحی وضع" "الارم سیٹ ہوگیا" @@ -1345,6 +1350,7 @@ "مقفل اسکرین کو حسب ضرورت بنائیں" "مقفل اسکرین کو حسب ضرورت بنانے کے لیے غیر مقفل کریں" "‏Wi-Fi دستیاب نہیں ہے" + "مقام فعال ہے" "کیمرا مسدود ہے" "کیمرا اور مائیکروفون مسدود ہے" "مائیکروفون مسدود ہے" @@ -1400,7 +1406,7 @@ "ٹچ پیڈ کے اشارے، کی بورڈ شارٹ کٹس اور مزید جانیں" "پیچھے جانے کا اشارہ" "ہوم کا اشارہ" - "ایکشن کلید" + "حالیہ ایپس دیکھیں" "ہو گیا" "واپس جائیں" "‏واپس جانے کے لیے، ٹچ پیڈ پر کہیں بھی تین انگلیوں کی مدد سے دائیں یا بائیں سوائپ کریں۔\n\nآپ اس کیلئے کی بورڈ شارٹ کٹ ایکشن + Esc کا بھی استعمال کر سکتے ہیں۔" @@ -1410,6 +1416,10 @@ "کسی بھی وقت اپنی ہوم اسکرین پر جانے کے لیے، تین انگلیوں کی مدد سے اپنی اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں۔" "عمدہ!" "آپ نے ہوم پر جانے کا اشارہ مکمل کر لیا۔" + "حالیہ ایپس دیکھیں" + "اپنے ٹچ پیڈ پر تین انگلیوں کا استعمال کرتے ہوئے اوپر کی طرف سوائپ کریں اور دبائے رکھیں۔" + "بہترین!" + "آپ نے حالیہ ایپس کا اشارہ مکمل کر لیا ہے۔" "ایکشن کلید" "اپنی ایپس تک رسائی حاصل کرنے کے لیے، اپنے کی بورڈ پر ایکشن کلید کو دبائیں۔" "مبارکباد!" @@ -1433,22 +1443,15 @@ "تین انگلیوں سے اوپر کی طرف سوائپ کریں اور دبائے رکھیں۔ مزید اشارے جاننے کے لیے تھپتھپائیں۔" "سبھی ایپس دیکھنے کے لیے اپنے کی بورڈ کا استعمال کریں" "کسی بھی وقت ایکشن کلید دبائیں۔ مزید اشارے جاننے کے لیے تھپتھپائیں۔" - "اضافی دھندلا اب چمک بار کا حصہ ہے" - "آپ اپنی اسکرین کے اوپری حصے سے چمکیلے پن لیول کو مزید کم کر کے اپنی اسکرین کو اضافی دھندلی بنا سکتے ہیں۔\n\nجب آپ تاریک ماحول میں ہوتے ہیں تو یہ بہتر کام کرتا ہے۔" - "اضافی دھندلا شارٹ کٹ کو ہٹائیں" - "اضافی دھندلا شارٹ کٹ کو ہٹا دیا گیا۔ اپنا چمکیلا پن کم کرنے کیلئے، ریگولر چمک بار کا استعمال کریں" - - - - - - - - - - - - - - + "اضافی دھندلا اب چمک سلائیڈر کا حصہ ہے" + "آپ چمکیلے پن لیول کو مزید کم کر کے اپنی اسکرین کو اضافی دھندلی بنا سکتے ہیں۔\n\nچونکہ یہ خصوصیت اب چمکیلے پن کے سلائیڈر کا حصہ ہے، اس لیے اضافی دھندلا شارٹ کٹس کو ہٹایا جا رہا ہے۔" + "اضافی دھندلا شارٹ کٹس کو ہٹائیں" + "اضافی دھندلا شارٹ کٹس ہٹا دیے گئے" + "کنیکٹویٹی" + "ایکسیسبیلٹی" + "یوٹیلیٹیز" + "رازداری" + "ایپس کے ذریعہ فراہم کردہ" + "ڈسپلے" + "نامعلوم" diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index af35adf0f390a8d08c5872f5203b7efa43e4a469..07faf6a9d41a71832ec9370f0ced6025dd84f484 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -105,8 +105,7 @@ "Qaydga qoʻshish" "Havolani kiritish" "%1$s, (%2$d)⁠" - - + "Boshqa profillardan havola kiritish mumkin emas" "Ekranni yozib olish" "Ekran yozib olinmoqda" "Ekrandan yozib olish seansi uchun joriy bildirishnoma" @@ -443,7 +442,7 @@ "Yoqilmagan" "Sozlash" "Sozlamalarda boshqarish" - "{count,plural, =0{Hech qanday rejim faol emas}=1{{mode} faol}other{# ta rejim faol}}" + "{count,plural, =0{0 ta rejim faol}=1{{mode} faol}other{# ta rejim faol}}" "Turli ovoz va tebranishlar endi sizni bezovta qilmaydi. Biroq, signallar, eslatmalar, tadbirlar haqidagi bildirishnomalar va siz tanlagan abonentlardan kelgan chaqiruvlar bundan mustasno. Lekin, ijro etiladigan barcha narsalar, jumladan, musiqa, video va o‘yinlar ovozi eshitiladi." "Turli ovoz va tebranishlar endi sizni bezovta qilmaydi. Biroq, signallar bundan mustasno. Lekin, ijro etiladigan barcha narsalar, jumladan, musiqa, video va o‘yinlar ovozi eshitiladi." "Sozlash" @@ -577,7 +576,7 @@ "Boshlash" "Bildirishnomalar yo‘q" "Yangi bildirishoma yoʻq" - "Bildirishnomalarni sekinlatish yoniq" + "Bildirishnomalar ovozini pasaytirish yoniq" "Bir vaqtda juda koʻp bildirishnoma olsangiz, qurilmangiz tovushi va ogohlantirishlar 2 daqiqagacha avtomatik pasaytiriladi." "Faolsizlantirish" "Eskilarini koʻrish uchun qulfni yeching" @@ -1180,6 +1179,10 @@ "%1$d%%" "Karnaylar va displeylar" "Taklif qilingan qurilmalar" + + + + "Mediani boshqa qurilmaga koʻchirish uchun umumiy seansingizni toʻxtating" "Toʻxtatish" "Translatsiya qanday ishlaydi" @@ -1290,6 +1293,8 @@ "Kiritish" "Foyd-ni boshqarish" "Bu bildirishnoma ikkiga ajratilgan ekranda ishlamaydi." + + "Wi‑Fi ishlamayapti" "Imtiyozli rejim" "Signal oʻrnatildi" @@ -1346,6 +1351,8 @@ "Ekran qulfini moslash" "Ekran qulfini sozlash uchun qulfni oching" "Wi-Fi mavjud emas" + + "Kamera bloklangan" "Kamera va mikrofon bloklangan" "Mikrofon bloklangan" @@ -1401,7 +1408,8 @@ "Sensorli panel ishoralari, tezkor tugmalar va boshqalar haqida" "Orqaga qaytish ishorasi" "Asosiy ekran ishorasi" - "Amal tugmasi" + + "Tayyor" "Orqaga qaytish" "Ortga qaytish uchun sensorli panelda uchta barmoqni chapga yoki oʻngga suring.\n\nBuning uchun Action + ESC tezkor tugmalaridan ham foydalanishingiz mumkin." @@ -1411,6 +1419,14 @@ "Istalgan vaqtda bosh ekranga oʻtish uchun ekranning pastidan uchta barmoq bilan tepaga suring." "Yaxshi!" "Bosh ekranni ochish ishorasi darsini tamomladingiz." + + + + + + + + "Amal tugmasi" "Ilovalarga kirish uchun klaviaturadagi amal tugmasini bosing" "Tabriklaymiz!" @@ -1434,22 +1450,19 @@ "Uchta barmoq bilan tepaga surib, bosib turing. Boshqa ishoralar bilan tanishish uchun bosing." "Klaviatura orqali barcha ilovalarni koʻrish" "Amal tugmasini istalganda bosing. Boshqa ishoralar bilan tanishish uchun bosing." - "Juda xira endi yorqinlik panelida joylashgan" - "Endi yorqinlik darajasini ekranning yuqori qismidan yanada pasaytirish orqali ekranni yanada xiralashtirishingiz mumkin.\n\nBu qorongʻi muhitda eng yaxshi ishlaydi." - "Juda xira yorligʻini olib tashlash" - "Juda xira yorligʻi olib tashlandi. Yorqinlikni pasaytirish uchun oddiy yorqinlik panelidan foydalaning." - - - - - - - + - + - + - + + "Aloqa" + "Qulayliklar" + "Vositalar" + "Maxfiylik" + "Ilovalarga tegishli" + "Ekran" + "Noaniq" diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index b7cd6b0d899fe2111a41cea2a484e742755fce79..279fe1ee55ecd188d1d40eff0e3d9da0f7e6ca45 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -105,8 +105,7 @@ "Thêm vào ghi chú" "Thêm đường liên kết" "%1$s (%2$d)" - - + "Không thể thêm đường liên kết từ các hồ sơ khác" "Trình ghi màn hình" "Đang xử lý video ghi màn hình" "Thông báo đang diễn ra về phiên ghi màn hình" @@ -1180,6 +1179,10 @@ "%1$d%%" "Loa và màn hình" "Thiết bị được đề xuất" + + + + "Dừng phiên chia sẻ của bạn để chuyển nội dung nghe nhìn sang thiết bị khác" "Dừng" "Cách tính năng truyền hoạt động" @@ -1290,6 +1293,8 @@ "Thêm" "Quản lý ng.dùng" "Thông báo này không hỗ trợ thao tác kéo để chia đôi màn hình" + + "Không có Wi‑Fi" "Chế độ ưu tiên" "Đã đặt chuông báo" @@ -1346,6 +1351,8 @@ "Tuỳ chỉnh màn hình khoá" "Mở khoá để tuỳ chỉnh màn hình khoá" "Không có Wi-Fi" + + "Máy ảnh bị chặn" "Máy ảnh và micrô bị chặn" "Micrô bị chặn" @@ -1401,7 +1408,8 @@ "Tìm hiểu về cử chỉ trên bàn di chuột, phím tắt và nhiều mục khác" "Cử chỉ quay lại" "Cử chỉ chuyển đến màn hình chính" - "Phím hành động" + + "Xong" "Quay lại" "Để quay lại, hãy dùng 3 ngón tay vuốt sang trái hoặc sang phải ở vị trí bất kỳ trên bàn di chuột.\n\nBạn cũng có thể dùng phím tắt Hành động + ESC cho thao tác này." @@ -1411,6 +1419,14 @@ "Để chuyển đến màn hình chính bất cứ lúc nào, hãy dùng 3 ngón tay vuốt lên từ cuối màn hình lên." "Tốt lắm!" "Bạn đã thực hiện xong cử chỉ chuyển đến màn hình chính." + + + + + + + + "Phím hành động" "Để truy cập vào các ứng dụng của bạn, hãy nhấn phím hành động trên bàn phím." "Xin chúc mừng!" @@ -1434,22 +1450,19 @@ "Dùng 3 ngón tay vuốt lên và giữ. Hãy nhấn để tìm hiểu các cử chỉ khác." "Sử dụng bàn phím để xem tất cả ứng dụng" "Nhấn phím hành động bất cứ lúc nào. Hãy nhấn để tìm hiểu các cử chỉ khác." - "Chế độ siêu tối hiện đã có trên thanh độ sáng" - "Giờ đây, bạn có thể đặt màn hình ở chế độ siêu tối bằng cách giảm thêm độ sáng từ đầu màn hình.\n\nChế độ này hoạt động hiệu quả nhất khi bạn ở trong một môi trường tối." - "Xoá lối tắt của chế độ siêu tối" - "Đã xoá lối tắt của chế độ siêu tối. Để giảm độ sáng, hãy dùng thanh độ sáng như thông thường." - - - - - - - + - + - + - + + "Khả năng kết nối" + "Hỗ trợ tiếp cận" + "Phần mềm tiện ích" + "Quyền riêng tư" + "Do các ứng dụng cung cấp" + "Hiển thị" + "Không xác định" diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index a329965d5714dc0c8dc1cd09e531bd6dcdeb8e56..6b1f0a7f0841536bc122a82be67a5aa08d2d95e9 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -105,8 +105,7 @@ "添加到备注中" "包括链接" "%1$s (%2$d)⁠" - - + "无法添加来自其他个人资料的链接" "屏幕录制器" "正在处理屏幕录制视频" "持续显示屏幕录制会话通知" @@ -443,7 +442,7 @@ "已关闭" "设置" "在设置中管理" - "{count,plural, =0{未启用任何模式}=1{{mode}已启用}other{# 个模式已启用}}" + "{count,plural, =0{未启用任何模式}=1{已启用“{mode}”模式}other{已启用 # 个模式}}" "您将不会受到声音和振动的打扰(闹钟、提醒、活动和所指定来电者的相关提示音除外)。您依然可以听到您选择播放的任何内容(包括音乐、视频和游戏)的相关音效。" "您将不会受到声音和振动的打扰(闹钟提示音除外)。您依然可以听到您选择播放的任何内容(包括音乐、视频和游戏)的相关音效。" "自定义" @@ -577,8 +576,8 @@ "立即开始" "没有通知" "没有新通知" - "“通知音量渐降”设置已开启" - "当您一次收到过多通知时,设备音量会自动降低,提醒次数也会自动减少,这种状况最长可持续 2 分钟。" + "已触发“通知音量渐降”" + "如果您在短时间内收到很多通知,设备音量和提醒次数会自动降低,最长持续 2 分钟。" "关闭" "解锁即可查看旧通知" "此设备由您的家长管理" @@ -1180,6 +1179,10 @@ "%1$d%%" "音箱和显示屏" "建议的设备" + + + + "停止共享的会话,即可将媒体移到其他设备" "停止" "广播的运作方式" @@ -1290,6 +1293,8 @@ "添加" "管理用户" "此通知不支持拖动到分屏中" + + "WLAN 已关闭" "优先模式" "闹钟已设置" @@ -1346,6 +1351,8 @@ "自定义锁屏" "解锁以自定义锁定屏幕" "没有 WLAN 连接" + + "已禁用摄像头" "已禁用摄像头和麦克风" "已禁用麦克风" @@ -1401,7 +1408,8 @@ "了解触控板手势、键盘快捷键等" "返回手势" "主屏幕手势" - "快捷操作按键" + + "完成" "返回" "如要返回,请使用三根手指在触控板上的任意位置左滑或右滑。\n\n您也可以使用键盘快捷操作键 + ESC 键进行返回。" @@ -1411,6 +1419,14 @@ "若要随时进入主屏幕,请用三根手指从屏幕的底部向上滑动。" "很好!" "您完成了“前往主屏幕”手势教程。" + + + + + + + + "快捷操作按键" "如要访问您的应用,请按下键盘上的快捷操作按键。" "恭喜!" @@ -1434,22 +1450,19 @@ "用三根手指向上滑动并按住。点按即可了解更多手势。" "使用键盘查看所有应用" "您可随时按下快捷操作按键。点按即可了解更多手势。" - "“极暗”功能现已在亮度条中" - "现在,您可从屏幕顶部进一步调低亮度,将屏幕调成极暗。\n\n此功能在昏暗环境中效果最佳。" - "移除“极暗”快捷方式" - "已移除“极暗”快捷方式。如要调低亮度,请使用常规亮度条。" - - - - - - - + - + - + - + + "连接" + "无障碍功能" + "实用程序" + "隐私设置" + "由应用提供" + "显示" + "未知" diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 73bfbe6c2be21272d686681f7aacabfe93b918f2..13b1a0f895047ba681e8186e05acf5a21197b82a 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -105,8 +105,7 @@ "新增至筆記" "加入連結" "%1$s (%2$d)" - - + "無法新增來自其他設定檔的連結" "螢幕錄影機" "正在處理螢幕錄影內容" "持續顯示錄影畫面工作階段通知" @@ -577,8 +576,8 @@ "立即開始" "沒有通知" "沒有新通知" - "調低通知強度功能已開啟" - "如果同一時間收到太多通知,裝置會在最長 2 分鐘內調低音量,並減少警示。" + "通知緩和已開啟" + "當你在短時間內收到太多通知時,裝置就會調低音量並減少通知數量最多兩分鐘。" "關閉" "解鎖即可查看舊通知" "此裝置由你的家長管理" @@ -1180,6 +1179,10 @@ "%1$d%%" "喇叭和螢幕" "建議的裝置" + + + + "停止共享工作階段以移動媒體至其他裝置" "停止" "廣播運作方式" @@ -1290,6 +1293,8 @@ "新增" "管理使用者" "此通知無法拖曳到分割螢幕中。" + + "Wi‑Fi 已關閉" "優先模式" "已設定鬧鐘" @@ -1346,6 +1351,8 @@ "自訂上鎖畫面" "解鎖後即可自訂上鎖畫面" "無法連線至 Wi-Fi" + + "已封鎖相機" "已封鎖相機和麥克風" "已封鎖麥克風" @@ -1401,7 +1408,8 @@ "瞭解觸控板手勢、鍵盤快速鍵等等" "返去手勢" "主畫面手勢" - "快捷操作鍵" + + "完成" "返回" "用三隻手指在觸控板上任何一處向左或向右滑動即可返回。\n\n你也可使用鍵盤快速鍵 Action 鍵 + Esc 鍵執行此操作。" @@ -1411,6 +1419,14 @@ "只要用三隻手指從螢幕底部向上滑動,隨時可以返回主畫面。" "做得好!" "你已完成「返回主畫面」手勢的教學課程。" + + + + + + + + "快捷操作鍵" "如要存取應用程式,請在鍵盤上按下快捷操作鍵。" "恭喜!" @@ -1434,22 +1450,19 @@ "用三隻手指向上滑動並按住。輕按即可瞭解更多手勢。" "使用鍵盤查看所有應用程式" "隨時按下快捷操作鍵。輕按即可瞭解更多手勢。" - "亮度列現已加入超暗功能" - "而家喺螢幕頂部進一步校低亮度,就可以令螢幕變得超暗\n\n呢個功能喺陰暗環境之下嘅效果最好" - "移除超暗功能快速鍵" - "超暗功能快速鍵已移除。如要降低亮度,請使用一般的亮度列。" - - - - - - - + - + - + - + + "裝置連接" + "無障礙功能" + "實用程式" + "私隱" + "由應用程式提供" + "螢幕" + "不明" diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 46847fcf6599fa65708f317389fd032d2fd9102f..0107819914f2715e841985c660a3882f84d3d90d 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -105,8 +105,7 @@ "新增至記事本" "包含連結" "%1$s (%2$d)" - - + "無法新增其他設定檔中的連結" "螢幕錄影器" "處理螢幕錄影內容" "持續顯示螢幕畫面錄製工作階段通知" @@ -1180,6 +1179,10 @@ "%1$d%%" "喇叭和螢幕" "建議的裝置" + + + + "停止共用的工作階段,即可將媒體移至其他裝置" "停止" "廣播功能的運作方式" @@ -1290,6 +1293,8 @@ "新增" "管理使用者" "這項通知無法拖曳到分割畫面中。" + + "Wi‑Fi 已關閉" "優先模式" "鬧鐘設定成功" @@ -1346,6 +1351,8 @@ "自訂螢幕鎖定畫面" "解鎖後即可自訂螢幕鎖定畫面" "無法連上 Wi-Fi" + + "已封鎖攝影機" "已封鎖攝影機和麥克風" "已封鎖麥克風" @@ -1401,7 +1408,8 @@ "學習觸控板手勢、鍵盤快速鍵等" "返回手勢" "主畫面手勢" - "快捷操作鍵" + + "完成" "返回" "如要返回,請在觸控板的任何位置上用三指向左或向右滑動。\n\n使用快捷操作鍵 + ESC 鍵 (鍵盤快速鍵) 也可以返回。" @@ -1411,6 +1419,14 @@ "用 3 指從螢幕底部向上滑動,就能隨時返回主畫面。" "太棒了!" "你已完成「返回主畫面」手勢的教學課程。" + + + + + + + + "快捷操作鍵" "如要存取應用程式,請按下鍵盤上的快捷操作鍵。" "恭喜!" @@ -1434,22 +1450,19 @@ "用三指向上滑動並按住。輕觸即可進一步瞭解手勢。" "使用鍵盤查看所有應用程式" "你隨時可以按下快捷操作鍵。輕觸即可進一步瞭解手勢。" - "「超暗」已移到亮度列" - "現在只要在螢幕頂端將亮度設定調得更低,就能讓螢幕變得更暗。\n\n這項設定最適合在昏暗環境下使用。" - "移除「超暗」捷徑" - "「超暗」捷徑已移除。如要調低亮度,請使用一般的亮度列。" - - - - - - - + - + - + - + + "連線" + "無障礙" + "公用程式" + "隱私權" + "由應用程式提供" + "螢幕" + "不明" diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index dbfb926b867c527ac4674dc9539ddbbd89f0806a..8eddc80f64833d7780c873082c8086087036e9ed 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -105,8 +105,7 @@ "Engeza kunothi" "Faka ilinki" "%1$s (%2$d)" - - + "Amalinki awakwazi ukufakwa ukusuka kwamanye amaphrofayela" "Okokuqopha iskrini" "Icubungula okokuqopha iskrini" "Isaziso esiqhubekayo seseshini yokurekhoda isikrini" @@ -1180,6 +1179,10 @@ "%1$d%%" "Izipikha Neziboniso" "Amadivayisi Aphakanyisiwe" + + + + "Misa iseshini yakho eyabiwe ukuze uhambise imidiya kwenye idivayisi" "Misa" "Indlela ukusakaza okusebenza ngayo" @@ -1290,6 +1293,8 @@ "Faka" "Phatha abasebenzisi" "Lesi saziso asikusekeli ukuhudulela ekuhlukaniseni isikrini." + + "I-Wi-Fi ayitholakali" "Imodi ebalulekile" "I-alamu isethiwe" @@ -1346,6 +1351,8 @@ "Yenza ngokwezifiso ukukhiya isikrini" "Vula ukuze wenze ukuvala isikrini ngendlela oyifisayo" "I-Wi-Fi ayitholakali" + + "Ikhamera ivinjiwe" "Ikhamera nemakrofoni zivinjiwe" "Imakrofoni ivinjiwe" @@ -1401,7 +1408,8 @@ "Funda ukunyakaza kwephedi yokuthinta, izinqamuleli zamakhibhodi, nokuningi" "Ukunyakazisa umzimba kwangemuva" "Ukunyakazisa umzimba kwasekhaya" - "Inkinobho yokufinyelela" + + "Kwenziwe" "Buyela emuva" "Ukuze ubuyele emuva, swayiphela kwesokunxele noma kwesokudla usebenzisa iminwe emithathu noma yikuphi ephedini yokuthinta.\n\nUngasebenzisa nesinqamuleli sekhibhodi Isenzo + ESC kulokhu." @@ -1411,6 +1419,14 @@ "Ukuze uye esikrinini sakho sasekhaya nganoma isiphi isikhathi, swayipha uye phezulu ngeminwe emithathu usuka phansi esikrinini sakho." "Kuhle!" "Ukuqedile ukuthinta kokuya ekhaya." + + + + + + + + "Inkinobho yokufinyelela" "Ukuze ufinyelele ama-app wakho, cindezela inkinobho yokufinyelela kukhibhodi yakho." "Halala!" @@ -1434,22 +1450,19 @@ "Swayiphela phezulu bese uyabamba usebenzisa iminwe emithathu. Thepha ukuze ufunde kabanzi ngokunyakazisa umzimba." "Sebenzisa ikhibhodi yakho ukubuka wonke ama-app" "Cindezela inkinobho yokufinyelela noma kunini. Thepha ukuze ufunde kabanzi ngokunyakazisa umzimba." - "Ukufiphala okwengeziwe manje sekuyingxenye yebha yokukhanya" - "Manje ungenza isikrini sifiphale ngokwengeziwe ngokwehlisa izinga lokukhanya nakakhulu kusukela phezulu kwesikrini sakho.\n\nLokhu kusebenza kahle kakhulu uma usendaweni emnyama." - "Susa isinqamuleli esifiphele esengeziwe" - "Isinqamuleli esifiphele ngokwengeziwe sikhishiwe. Ukuze wehlise ukukhanya kwakho, sebenzisa ibha yokukhanya evamile." - - - - - - - + - + - + - + + "Ukuxhumana" + "Ukufinyeleleka" + "Okusetshenziswayo" + "Ubumfihlo" + "Kuhlinzekwe ama-app" + "Bonisa" + "Akwaziwa" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 00846cb1037838c008a865c925f0186cfe4b3406..e94248dc72ce1ab9055b7cc117b397d5ac14b62f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1005,10 +1005,6 @@ 64dp 16dp - - 412dp - 728dp - 2dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f9c2aef5f070e9b9db775774ba418dbe57adb38c..a3db7761f59d22af453fa3a437c5348c4c46269c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3114,6 +3114,10 @@ Speakers & Displays Suggested Devices + + Input + + Output Stop your shared session to move media to another device @@ -3707,6 +3711,12 @@ [CHAR LIMIT=NONE] --> or + + Drag handle + diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index 2f2b10f8dc0c94b4fdfa492420bf72509760a502..66c54a389c8e1b657186c87fe4e6a2e615f505bc 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -65,7 +65,7 @@ + app:layout_constraintVertical_bias="0" /> + app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" /> { public static final String TAG = "KeyguardSimPinView"; private static final String LOG_TAG = "KeyguardSimPinView"; - private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final TelephonyManager mTelephonyManager; @@ -71,7 +70,7 @@ public class KeyguardSimPinViewController KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @Override public void onSimStateChanged(int subId, int slotId, int simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); // If subId has gone to PUK required then we need to go to the PUK screen. if (subId == mSubId && simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) { getKeyguardSecurityCallback().showCurrentSecurityScreen(); @@ -129,7 +128,7 @@ public class KeyguardSimPinViewController @Override void resetState() { super.resetState(); - if (DEBUG) Log.v(TAG, "Resetting state"); + Log.v(TAG, "Resetting state"); handleSubInfoChangeIfNeeded(); mMessageAreaController.setMessage(""); if (mShowDefaultMessage) { @@ -216,11 +215,9 @@ public class KeyguardSimPinViewController mMessageAreaController.setMessage(mView.getResources().getString( R.string.kg_password_pin_failed)); } - if (DEBUG) { - Log.d(LOG_TAG, "verifyPasswordAndUnlock " - + " CheckSimPin.onSimCheckResponse: " + result - + " attemptsRemaining=" + result.getAttemptsRemaining()); - } + Log.d(LOG_TAG, "verifyPasswordAndUnlock " + + " CheckSimPin.onSimCheckResponse: " + result + + " attemptsRemaining=" + result.getAttemptsRemaining()); } getKeyguardSecurityCallback().userActivity(); mCheckSimPinThread = null; @@ -280,10 +277,8 @@ public class KeyguardSimPinViewController displayMessage = mView.getResources() .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); } - if (DEBUG) { - Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining=" - + attemptsRemaining + " displayMessage=" + displayMessage); - } + Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining=" + + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; } @@ -323,14 +318,10 @@ public class KeyguardSimPinViewController @Override public void run() { - if (DEBUG) { - Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")"); - } + Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")"); TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); final PinResult result = telephonyManager.supplyIccLockPin(mPin); - if (DEBUG) { - Log.v(TAG, "supplyIccLockPin returned: " + result.toString()); - } + Log.v(TAG, "supplyIccLockPin returned: " + result.toString()); mView.post(() -> onSimCheckResponse(result)); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f731186f6d01b9577e9ba89a15bf4f09201092b4..22130f8278416feb91ba518638cd7bbde14615f8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -43,6 +43,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.systemui.Flags.simPinBouncerReset; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import android.annotation.AnyThread; @@ -1703,6 +1704,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab intent.getStringExtra(Intent.EXTRA_SIM_STATE), args.slotId, args.subId); + if (args.slotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + return; + } mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState) .sendToTarget(); } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { @@ -1940,7 +1944,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } int state = TelephonyManager.SIM_STATE_UNKNOWN; String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE); - int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0); + + int defaultSlotId = 0; + if (simPinBouncerReset()) { + defaultSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + } + int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, + defaultSlotId); int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) { @@ -2479,6 +2489,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab this::onTransitionStateChanged ); } + + // start() can be invoked in the middle of user switching, so check for this state and issue + // the call manually as that important event was missed. + if (mUserTracker.isUserSwitching()) { + handleUserSwitching(mUserTracker.getUserId(), () -> {}); + } } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index b10d37e5c27ad9b61538b32a5384c0544ad22e86..c95a94e5e388c7b84ca86d3ff46df02ee83f2f27 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -34,9 +34,7 @@ import com.android.settingslib.Utils import com.android.systemui.CoreStartable import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.data.repository.FacePropertyRepository -import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams -import com.android.systemui.biometrics.shared.model.toSensorType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor @@ -104,7 +102,6 @@ constructor( private var udfpsController: UdfpsController? = null private var udfpsRadius: Float = -1f - private var udfpsType: FingerprintSensorType = FingerprintSensorType.UNKNOWN override fun start() { init() @@ -373,11 +370,8 @@ constructor( private val udfpsControllerCallback = object : UdfpsController.Callback { override fun onFingerDown() { - // only show dwell ripple for device entry non-ultrasonic udfps - if ( - keyguardUpdateMonitor.isFingerprintDetectionRunning && - udfpsType != FingerprintSensorType.UDFPS_ULTRASONIC - ) { + // only show dwell ripple for device entry + if (keyguardUpdateMonitor.isFingerprintDetectionRunning) { showDwellRipple() } } @@ -403,7 +397,6 @@ constructor( if (it.size > 0) { udfpsController = udfpsControllerProvider.get() udfpsRadius = authController.udfpsRadius - udfpsType = it[0].sensorType.toSensorType() if (mView.isAttachedToWindow) { udfpsController?.addCallback(udfpsControllerCallback) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 5ffb9ab26bb0c93366e893332128e1f6dcfb6129..a3904caa9dccf472d1dc0c37ae7ce29c839822a2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -19,6 +19,7 @@ package com.android.systemui.biometrics; import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP; import static android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD; import static android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING; @@ -329,6 +330,22 @@ public class UdfpsController implements DozeReceiver, Dumpable { int sensorId, @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo ) { + if (isUltrasonic()) { + if (acquiredInfo == FINGERPRINT_ACQUIRED_START) { + mFgExecutor.execute(() -> { + for (Callback cb : mCallbacks) { + cb.onFingerDown(); + } + }); + } else { + mFgExecutor.execute(() -> { + for (Callback cb : mCallbacks) { + cb.onFingerUp(); + } + }); + } + } + if (BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquiredInfo)) { boolean acquiredGood = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD; mFgExecutor.execute(() -> { @@ -1024,6 +1041,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { return mSensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; } + private boolean isUltrasonic() { + return mSensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC; + } + public boolean isFingerDown() { return mOnFingerDown; } @@ -1105,8 +1126,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } - for (Callback cb : mCallbacks) { - cb.onFingerDown(); + if (isOptical()) { + for (Callback cb : mCallbacks) { + cb.onFingerDown(); + } } } @@ -1143,8 +1166,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mOnFingerDown) { mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x, y, minor, major, orientation, time, gestureStart, isAod); - for (Callback cb : mCallbacks) { - cb.onFingerUp(); + if (isOptical()) { + for (Callback cb : mCallbacks) { + cb.onFingerUp(); + } } } mOnFingerDown = false; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index bb450c0b6d902d1322ee589023fa2f586f86c9e6..18a7739f12ab75a0b5cf2a5b7e45b694b6f19d30 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -83,7 +83,7 @@ constructor( /** Sets whether Udfps overlay should handle touches */ fun setHandleTouches(shouldHandle: Boolean = true) { - if (authController.isUltrasonicUdfpsSupported + if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) { fingerprintManager?.setIgnoreDisplayTouches( requestId.value, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt index b5e54d5f079b10236a21a6506a30abb21788dde2..fdbc18dc9ae10ef223802eb0b1b16377045750c1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt @@ -5,7 +5,6 @@ import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.compose.ui.platform.ComposeView -import androidx.core.view.isGone import androidx.lifecycle.Lifecycle import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.composable.BouncerContainer @@ -13,7 +12,6 @@ import com.android.systemui.bouncer.ui.viewmodel.BouncerContainerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.setSnapshotBinding import com.android.systemui.lifecycle.viewModel import kotlinx.coroutines.awaitCancellation @@ -50,7 +48,6 @@ object ComposeBouncerViewBinder { setContent { BouncerContainer(viewModelFactory, dialogFactory) } } ) - view.setSnapshotBinding { view.isGone = !viewModel.isVisible } awaitCancellation() } finally { view.removeAllViews() diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt index 19e7537007bf4bd3d12dabc086e23f7fcc9d89bb..b8c30fe9d4a8787abf58a166a1ce065b199201ab 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt @@ -76,11 +76,7 @@ class BouncerHapticPlayer @Inject constructor(private val msdlPlayer: dagger.Laz /** Deliver MSDL feedback when the delete key of the pin bouncer is pressed */ fun playDeleteKeyPressFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_DELETE) - /** - * Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed - * - * @return whether MSDL feedback is allowed to play. - */ + /** Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed. */ fun playDeleteKeyLongPressedFeedback() = msdlPlayer.get().playToken(MSDLToken.LONG_PRESS) /** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */ diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index c67b35424cc91371e82f473857cd2099777876a6..4185aed3095ce89ae260025d535a812beb02dbf1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -21,6 +21,7 @@ import com.android.app.tracing.coroutines.flow.collectLatest import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.lifecycle.ExclusiveActivatable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.channels.Channel @@ -42,6 +43,7 @@ sealed class AuthMethodBouncerViewModel( /** Name to use for performance tracing purposes. */ val traceName: String, + protected val bouncerHapticPlayer: BouncerHapticPlayer? = null, ) : ExclusiveActivatable() { private val _animateFailure = MutableStateFlow(false) @@ -80,8 +82,13 @@ sealed class AuthMethodBouncerViewModel( return@collectLatest } + performAuthenticationHapticFeedback(authenticationResult) + _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED clearInput() + if (authenticationResult == AuthenticationResult.SUCCEEDED) { + onSuccessfulAuthentication() + } } awaitCancellation() } @@ -112,20 +119,26 @@ sealed class AuthMethodBouncerViewModel( /** Returns the input entered so far. */ protected abstract fun getInput(): List + /** Invoked after a successful authentication. */ + protected open fun onSuccessfulAuthentication() = Unit + + /** Perform authentication result haptics */ + private fun performAuthenticationHapticFeedback(result: AuthenticationResult) { + if (result == AuthenticationResult.SKIPPED) return + + bouncerHapticPlayer?.playAuthenticationFeedback( + authenticationSucceeded = result == AuthenticationResult.SUCCEEDED + ) + } + /** * Attempts to authenticate the user using the current input value. * * @see BouncerInteractor.authenticate */ - protected fun tryAuthenticate( - input: List = getInput(), - useAutoConfirm: Boolean = false, - ) { + protected fun tryAuthenticate(input: List = getInput(), useAutoConfirm: Boolean = false) { authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm)) } - private data class AuthenticationRequest( - val input: List, - val useAutoConfirm: Boolean, - ) + private data class AuthenticationRequest(val input: List, val useAutoConfirm: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt index c60f93244437560cda7263bf19e8e3e5b71aab7f..5a4f8eb36673e40dbdb130f6182a77bf0f6871ae 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt @@ -16,17 +16,15 @@ package com.android.systemui.bouncer.ui.viewmodel -import androidx.compose.runtime.getValue import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.lifecycle.ExclusiveActivatable -import com.android.systemui.lifecycle.Hydrator import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -39,11 +37,6 @@ constructor( private val deviceUnlockedInteractor: DeviceUnlockedInteractor, ) : ExclusiveActivatable() { - private val hydrator = Hydrator("BouncerContainerViewModel") - - val isVisible: Boolean by - hydrator.hydratedStateOf(traceName = "isVisible", source = legacyInteractor.isShowing) - override suspend fun onActivated(): Nothing { coroutineScope { launch { @@ -74,8 +67,7 @@ constructor( legacyInteractor.hide() } } - - hydrator.activate() + awaitCancellation() } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt index 0aada06a7eb741a0364078a961e4d4a58358d8f2..0bcb58dee93453a6ac86a6dabc07b26ba0ee26ea 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.qualifiers.Application @@ -61,6 +62,7 @@ constructor( private val pinViewModelFactory: PinBouncerViewModel.Factory, private val patternViewModelFactory: PatternBouncerViewModel.Factory, private val passwordViewModelFactory: PasswordBouncerViewModel.Factory, + private val bouncerHapticPlayer: BouncerHapticPlayer, ) : ExclusiveActivatable() { private val _selectedUserImage = MutableStateFlow(null) val selectedUserImage: StateFlow = _selectedUserImage.asStateFlow() @@ -162,10 +164,7 @@ constructor( } launch { - combine( - userSwitcher.users, - userSwitcher.menu, - ) { users, actions -> + combine(userSwitcher.users, userSwitcher.menu) { users, actions -> users.map { user -> UserSwitcherDropdownItemViewModel( icon = Icon.Loaded(user.image, contentDescription = null), @@ -178,7 +177,7 @@ constructor( icon = Icon.Resource( action.iconResourceId, - contentDescription = null + contentDescription = null, ), text = Text.Resource(action.textResourceId), onClick = action.onClicked, @@ -226,7 +225,7 @@ constructor( } private fun getChildViewModel( - authenticationMethod: AuthenticationMethodModel, + authenticationMethod: AuthenticationMethodModel ): AuthMethodBouncerViewModel? { // If the current child view-model matches the authentication method, reuse it instead of // creating a new instance. @@ -241,12 +240,14 @@ constructor( authenticationMethod = authenticationMethod, onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, + bouncerHapticPlayer = bouncerHapticPlayer, ) is AuthenticationMethodModel.Sim -> pinViewModelFactory.create( authenticationMethod = authenticationMethod, onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, + bouncerHapticPlayer = bouncerHapticPlayer, ) is AuthenticationMethodModel.Password -> passwordViewModelFactory.create( @@ -257,6 +258,7 @@ constructor( patternViewModelFactory.create( onIntentionalUserInput = ::onIntentionalUserInput, isInputEnabled = isInputEnabled, + bouncerHapticPlayer = bouncerHapticPlayer, ) else -> null } @@ -317,10 +319,7 @@ constructor( return when { // The wipe dialog takes priority over the lockout dialog. wipeText != null -> - DialogViewModel( - text = wipeText, - onDismiss = { wipeDialogMessage.value = null }, - ) + DialogViewModel(text = wipeText, onDismiss = { wipeDialogMessage.value = null }) lockoutText != null -> DialogViewModel( text = lockoutText, @@ -338,7 +337,7 @@ constructor( fun onKeyEvent(keyEvent: KeyEvent): Boolean { return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent( keyEvent.type, - keyEvent.nativeKeyEvent.keyCode + keyEvent.nativeKeyEvent.keyCode, ) ?: false } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 2493cf1a101b20d0beb0addba35376acd671710e..1427d787ea860a59ec5741e7f837aaea19379fff 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -81,50 +81,59 @@ constructor( val selectedUserId: StateFlow = _selectedUserId.asStateFlow() private val requests = Channel(Channel.BUFFERED) + private var wasSuccessfullyAuthenticated = false override suspend fun onActivated(): Nothing { - coroutineScope { - launch { super.onActivated() } - launch { - requests.receiveAsFlow().collect { request -> - when (request) { - is OnImeSwitcherButtonClicked -> { - inputMethodInteractor.showInputMethodPicker( - displayId = request.displayId, - showAuxiliarySubtypes = false, - ) - } - is OnImeDismissed -> { - interactor.onImeHiddenByUser() + try { + coroutineScope { + launch { super.onActivated() } + launch { + requests.receiveAsFlow().collect { request -> + when (request) { + is OnImeSwitcherButtonClicked -> { + inputMethodInteractor.showInputMethodPicker( + displayId = request.displayId, + showAuxiliarySubtypes = false, + ) + } + is OnImeDismissed -> { + interactor.onImeHiddenByUser() + } } } } + launch { + combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> + hasInput && !hasFocus && !wasSuccessfullyAuthenticated + } + .collect { _isTextFieldFocusRequested.value = it } + } + launch { + selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } + } + launch { + // Re-fetch the currently-enabled IMEs whenever the selected user changes, and + // whenever + // the UI subscribes to the `isImeSwitcherButtonVisible` flow. + combine( + // InputMethodManagerService sometimes takes + // some time to update its internal state when the + // selected user changes. + // As a workaround, delay fetching the IME info. + selectedUserInteractor.selectedUser.onEach { + delay(DELAY_TO_FETCH_IMES) + }, + _isImeSwitcherButtonVisible.onSubscriberAdded(), + ) { selectedUserId, _ -> + inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId) + } + .collect { _isImeSwitcherButtonVisible.value = it } + } + awaitCancellation() } - launch { - combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> - hasInput && !hasFocus - } - .collect { _isTextFieldFocusRequested.value = it } - } - launch { selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } } - launch { - // Re-fetch the currently-enabled IMEs whenever the selected user changes, and - // whenever - // the UI subscribes to the `isImeSwitcherButtonVisible` flow. - combine( - // InputMethodManagerService sometimes takes some time to update its - // internal - // state when the selected user changes. As a workaround, delay fetching the - // IME - // info. - selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) }, - _isImeSwitcherButtonVisible.onSubscriberAdded() - ) { selectedUserId, _ -> - inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId) - } - .collect { _isImeSwitcherButtonVisible.value = it } - } - awaitCancellation() + } finally { + // reset whenever the view model is "deactivated" + wasSuccessfullyAuthenticated = false } } @@ -141,6 +150,10 @@ constructor( return _password.value.toCharArray().toList() } + override fun onSuccessfulAuthentication() { + wasSuccessfullyAuthenticated = true + } + /** Notifies that the user has changed the password input. */ fun onPasswordInputChanged(newPassword: String) { if (newPassword.isNotEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 0a866b43429fc8c36be11da0f1d8742e402f57f3..158f102ccdb3d84d16961cf81e77500798298619 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -18,9 +18,11 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import android.util.TypedValue +import android.view.View import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.res.R import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -35,7 +37,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch /** Holds UI state and handles user input for the pattern bouncer UI. */ @@ -44,6 +45,7 @@ class PatternBouncerViewModel constructor( private val applicationContext: Context, interactor: BouncerInteractor, + @Assisted bouncerHapticPlayer: BouncerHapticPlayer, @Assisted isInputEnabled: StateFlow, @Assisted private val onIntentionalUserInput: () -> Unit, ) : @@ -51,6 +53,7 @@ constructor( interactor = interactor, isInputEnabled = isInputEnabled, traceName = "PatternBouncerViewModel", + bouncerHapticPlayer = bouncerHapticPlayer, ) { /** The number of columns in the dot grid. */ @@ -190,14 +193,7 @@ constructor( private fun defaultDots(): List { return buildList { (0 until columnCount).forEach { x -> - (0 until rowCount).forEach { y -> - add( - PatternDotViewModel( - x = x, - y = y, - ) - ) - } + (0 until rowCount).forEach { y -> add(PatternDotViewModel(x = x, y = y)) } } } } @@ -207,14 +203,17 @@ constructor( applicationContext.resources.getValue( com.android.internal.R.dimen.lock_pattern_dot_hit_factor, outValue, - true + true, ) max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR) } + fun performDotFeedback(view: View?) = bouncerHapticPlayer?.playPatternDotFeedback(view) + @AssistedFactory interface Factory { fun create( + bouncerHapticPlayer: BouncerHapticPlayer, isInputEnabled: StateFlow, onIntentionalUserInput: () -> Unit, ): PatternBouncerViewModel @@ -231,7 +230,7 @@ constructor( */ private fun PatternDotViewModel.isOnLineSegment( first: PatternDotViewModel, - second: PatternDotViewModel + second: PatternDotViewModel, ): Boolean { val anotherPoint = this // No need to consider any points outside the bounds of two end points @@ -253,14 +252,8 @@ private fun Int.isBetween(a: Int, b: Int): Boolean { return (this in a..b) || (this in b..a) } -data class PatternDotViewModel( - val x: Int, - val y: Int, -) { +data class PatternDotViewModel(val x: Int, val y: Int) { fun toCoordinate(): AuthenticationPatternCoordinate { - return AuthenticationPatternCoordinate( - x = x, - y = y, - ) + return AuthenticationPatternCoordinate(x = x, y = y) } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index da29c6230cd8d6ac0edccd25dc43ddc98779f009..0cb4260e4d7fcec4579e255984fa642e5b9977b4 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -19,12 +19,14 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context +import android.view.HapticFeedbackConstants import android.view.KeyEvent.KEYCODE_0 import android.view.KeyEvent.KEYCODE_9 import android.view.KeyEvent.KEYCODE_DEL import android.view.KeyEvent.KEYCODE_NUMPAD_0 import android.view.KeyEvent.KEYCODE_NUMPAD_9 import android.view.KeyEvent.isConfirmKey +import android.view.View import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType import com.android.keyguard.PinShapeAdapter @@ -32,6 +34,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.res.R import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -56,6 +59,7 @@ constructor( applicationContext: Context, interactor: BouncerInteractor, private val simBouncerInteractor: SimBouncerInteractor, + @Assisted bouncerHapticPlayer: BouncerHapticPlayer, @Assisted isInputEnabled: StateFlow, @Assisted private val onIntentionalUserInput: () -> Unit, @Assisted override val authenticationMethod: AuthenticationMethodModel, @@ -64,6 +68,7 @@ constructor( interactor = interactor, isInputEnabled = isInputEnabled, traceName = "PinBouncerViewModel", + bouncerHapticPlayer = bouncerHapticPlayer, ) { /** * Whether the sim-related UI in the pin view is showing. @@ -126,10 +131,9 @@ constructor( .collect { _hintedPinLength.value = it } } launch { - combine( - mutablePinInput, - interactor.isAutoConfirmEnabled, - ) { mutablePinEntries, isAutoConfirmEnabled -> + combine(mutablePinInput, interactor.isAutoConfirmEnabled) { + mutablePinEntries, + isAutoConfirmEnabled -> computeBackspaceButtonAppearance( pinInput = mutablePinEntries, isAutoConfirmEnabled = isAutoConfirmEnabled, @@ -183,8 +187,22 @@ constructor( mutablePinInput.value = mutablePinInput.value.deleteLast() } + fun onBackspaceButtonPressed(view: View?) { + if (bouncerHapticPlayer?.isEnabled == true) { + bouncerHapticPlayer.playDeleteKeyPressFeedback() + } else { + view?.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + } + /** Notifies that the user long-pressed the backspace button. */ fun onBackspaceButtonLongPressed() { + if (bouncerHapticPlayer?.isEnabled == true) { + bouncerHapticPlayer.playDeleteKeyLongPressedFeedback() + } clearInput() } @@ -266,13 +284,24 @@ constructor( } } - /** Notifies that the user has pressed down on a digit button. */ - fun onDigitButtonDown() { + /** + * Notifies that the user has pressed down on a digit button. This function also performs haptic + * feedback on the view. + */ + fun onDigitButtonDown(view: View?) { if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) { // Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button // touch. super.onDown() } + if (bouncerHapticPlayer?.isEnabled == true) { + bouncerHapticPlayer.playNumpadKeyFeedback() + } else { + view?.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } } @AssistedFactory @@ -281,6 +310,7 @@ constructor( isInputEnabled: StateFlow, onIntentionalUserInput: () -> Unit, authenticationMethod: AuthenticationMethodModel, + bouncerHapticPlayer: BouncerHapticPlayer, ): PinBouncerViewModel } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 769976ef505817e2044d5bd7e8420cc9fe4fc3e1..ae4b679dd4b86ef9ef67ffcb7b8a2be88c6a866e 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -282,6 +282,8 @@ public class FalsingDataProvider { return !mRecentKeyEvents.isEmpty(); } + // Deprecated in favor of {@code isTouchScreenSource}, b/329221787 + @Deprecated public boolean isFromTrackpad() { if (Flags.nonTouchscreenDevicesBypassFalsing()) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index c7a47b18f467e4e62cf9d2fc28d14c7a4282ece6..1ada56dea45a75a7c0a7cdb420910be243fc0dac 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -30,6 +30,7 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.os.Build; +import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -37,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.user.utils.UserScopedService; import javax.inject.Inject; import javax.inject.Provider; @@ -67,13 +69,13 @@ public class ClipboardListener implements public ClipboardListener(Context context, Provider clipboardOverlayControllerProvider, ClipboardToast clipboardToast, - ClipboardManager clipboardManager, + UserScopedService clipboardManager, KeyguardManager keyguardManager, UiEventLogger uiEventLogger) { mContext = context; mOverlayProvider = clipboardOverlayControllerProvider; mClipboardToast = clipboardToast; - mClipboardManager = clipboardManager; + mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT); mKeyguardManager = keyguardManager; mUiEventLogger = uiEventLogger; } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 6e01393015eda9fac37230c21c5f14f5f2638a16..08a7c395e57fc210f8e6797b7f6217f3d96a3582 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -19,11 +19,13 @@ package com.android.systemui.communal import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState @@ -84,6 +86,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Background private val bgScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, + private val uiEventLogger: UiEventLogger, ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT @@ -146,7 +149,7 @@ constructor( screenTimeout = systemSettings.getInt( Settings.System.SCREEN_OFF_TIMEOUT, - DEFAULT_SCREEN_TIMEOUT + DEFAULT_SCREEN_TIMEOUT, ) } .launchIn(bgScope) @@ -160,7 +163,7 @@ constructor( combine( communalSceneInteractor.currentScene, // Emit a value on start so the combine starts. - communalInteractor.userActivity.emitOnStart() + communalInteractor.userActivity.emitOnStart(), ) { scene, _ -> // Only timeout if we're on the hub is open. scene == CommunalScenes.Communal @@ -184,6 +187,7 @@ constructor( CommunalScenes.Blank, "dream started after timeout", ) + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) } } } @@ -212,6 +216,7 @@ constructor( newScene = CommunalScenes.Blank, loggingReason = "hub timeout", ) + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) } timeoutJob = null } @@ -219,7 +224,7 @@ constructor( } private suspend fun determineSceneAfterTransition( - lastStartedTransition: TransitionStep, + lastStartedTransition: TransitionStep ): Pair? { val to = lastStartedTransition.to val from = lastStartedTransition.from @@ -251,9 +256,8 @@ constructor( Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) } from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> { - // Make sure the communal hub is showing (immediately, not fading in) when - // transitioning from dozing to hub. - Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately) + // Make sure the communal hub is showing when transitioning from dozing to hub. + Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade) } else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt index f77dd587dca35672d7e18684a2863e78ce0347d7..f0f7ca522c7043bfda3ec0f7991f7b86200b4703 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt @@ -82,19 +82,26 @@ constructor( } _timers.value = - timerTargets.map { (stableId, target) -> - CommunalSmartspaceTimer( - // The view layer should have the instance based smartspaceTargetId instead of - // stable id, so that when a new instance of the timer is created, for example, - // when it is paused, the view should re-render its remote views. - smartspaceTargetId = - if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId, - createdTimestampMillis = targetCreationTimes[stableId]!!, - remoteViews = target.remoteViews!!, - ) - } - - logger.d({ "Smartspace timers updated: $str1" }) { str1 = _timers.value.toString() } + timerTargets + .map { (stableId, target) -> + CommunalSmartspaceTimer( + // The view layer should have the instance based smartspaceTargetId instead + // of stable id, so that when a new instance of the timer is created, for + // example, when it is paused, the view should re-render its remote views. + smartspaceTargetId = + if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId, + createdTimestampMillis = targetCreationTimes[stableId]!!, + remoteViews = target.remoteViews!!, + ) + } + .also { newVal -> + // Only log when value changes to avoid filling up the buffer. + if (newVal != _timers.value) { + logger.d({ "Smartspace timers updated: $str1" }) { + str1 = newVal.toString() + } + } + } } override fun startListening() { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt index 7453368d0ee7b50e9b97ca7233c9e9a4075d0f60..f7cd2ab891408a070bcdf45873a097f0a02706e6 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt @@ -16,10 +16,13 @@ package com.android.systemui.communal.domain.interactor +import android.annotation.SuppressLint import android.app.ActivityManager +import android.app.DreamManager import com.android.systemui.common.usagestats.domain.UsageStatsInteractor import com.android.systemui.common.usagestats.shared.model.ActivityEventModel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer @@ -34,10 +37,12 @@ import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout @@ -56,6 +61,8 @@ constructor( private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val taskStackChangeListeners: TaskStackChangeListeners, private val usageStatsInteractor: UsageStatsInteractor, + private val dreamManager: DreamManager, + @Background private val bgScope: CoroutineScope, @CommunalLog logBuffer: LogBuffer, ) { private companion object { @@ -127,13 +134,21 @@ constructor( * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it * does. This can detect activities started due to broadcast trampolines from widgets. */ + @SuppressLint("MissingPermission") suspend fun waitForActivityStartAndDismissKeyguard() { if (waitForActivityStartWhileOnHub()) { logger.d("Detected trampoline, requesting unlock") activityStarter.dismissKeyguardThenExecute( - /* action= */ { false }, + /* action= */ { + // Kill the dream when launching the trampoline activity. Right now the exit + // animation stalls when tapping the battery widget, and the dream remains + // visible until the transition hits some timeouts and gets cancelled. + // TODO(b/362841648): remove once exit animation is fixed. + bgScope.launch { dreamManager.stopDream() } + false + }, /* cancel= */ null, - /* afterKeyguardGone= */ false + /* afterKeyguardGone= */ false, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt index 11fb2332dc5fed8e62474d6f7b0902489e53cefb..78156dbc8964774d7d17b6c24a44ef732be1dd9f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt @@ -30,6 +30,4 @@ object CommunalTransitionKeys { val ToEditMode = TransitionKey("ToEditMode") /** Transition to the glanceable hub after exiting edit mode */ val FromEditMode = TransitionKey("FromEditMode") - /** Immediately transitions without any delay */ - val Immediately = TransitionKey("Immediately") } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 8818c3af4916143ed5896c2838c9faff70b67ba4..78a8a42e2743c2c99c51693623e2926c87c2566a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -113,6 +113,7 @@ import android.view.accessibility.CaptioningManager; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassificationManager; +import androidx.annotation.NonNull; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import androidx.core.app.NotificationManagerCompat; @@ -716,6 +717,19 @@ public class FrameworkServicesModule { /* isViewCaptureEnabled= */ enableViewCaptureTracing()); } + @Provides + @Singleton + static ViewCaptureAwareWindowManager.Factory viewCaptureAwareWindowManagerFactory( + Lazy daggerLazyViewCapture) { + return new ViewCaptureAwareWindowManager.Factory() { + @NonNull + @Override + public ViewCaptureAwareWindowManager create(@NonNull WindowManager windowManager) { + return provideViewCaptureAwareWindowManager(windowManager, daggerLazyViewCapture); + } + }; + } + @Provides @Singleton static PermissionManager providePermissionManager(Context context) { @@ -733,9 +747,8 @@ public class FrameworkServicesModule { } @Provides - @Singleton - static ClipboardManager provideClipboardManager(Context context) { - return context.getSystemService(ClipboardManager.class); + static UserScopedService provideClipboardManager(Context context) { + return new UserScopedServiceImpl<>(context, ClipboardManager.class); } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index e17e530a03d945ec2bb1cbd2f16ab0f74c5d0ee2..5259c5dca39ff1c4e78f5fd50a8248b2e6fa476d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -26,7 +26,9 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus import com.android.systemui.flags.SystemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.TrustInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject @@ -36,6 +38,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -57,6 +60,7 @@ constructor( private val powerInteractor: PowerInteractor, private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, private val systemPropertiesHelper: SystemPropertiesHelper, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { private val deviceUnlockSource = @@ -74,7 +78,7 @@ constructor( trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent }, authenticationInteractor.onAuthenticationResult .filter { it } - .map { DeviceUnlockSource.BouncerInput } + .map { DeviceUnlockSource.BouncerInput }, ) private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled @@ -170,10 +174,20 @@ constructor( combine( powerInteractor.isAsleep, isInLockdown, - ::Pair, + keyguardTransitionInteractor + .transitionValue(KeyguardState.AOD) + .map { it == 1f } + .distinctUntilChanged(), + ::Triple, ) - .flatMapLatestConflated { (isAsleep, isInLockdown) -> - if (isAsleep || isInLockdown) { + .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) -> + val isForceLocked = + when { + isAsleep && !isAod -> true + isInLockdown -> true + else -> false + } + if (isForceLocked) { flowOf(DeviceUnlockStatus(false, null)) } else { deviceUnlockSource.map { DeviceUnlockStatus(true, it) } diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt index 81ea2e74d79940b688266330832219aac4b27ff2..62720a5b1377d1b592426e27d85fdc9af7357cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt @@ -79,7 +79,7 @@ constructor( .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress -> if (pendingDisplay == null) { - hideDialog() + dismissDialog() } else { showDialog(pendingDisplay, concurrentDisplaysInProgress) } @@ -88,17 +88,17 @@ constructor( } private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) { - hideDialog() + dismissDialog() dialog = bottomSheetFactory .createDialog( onStartMirroringClickListener = { scope.launch(bgDispatcher) { pendingDisplay.enable() } - hideDialog() + dismissDialog() }, onCancelMirroring = { scope.launch(bgDispatcher) { pendingDisplay.ignore() } - hideDialog() + dismissDialog() }, navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom }, showConcurrentDisplayInfo = concurrentDisplaysInProgess @@ -106,8 +106,8 @@ constructor( .apply { show() } } - private fun hideDialog() { - dialog?.hide() + private fun dismissDialog() { + dialog?.dismiss() dialog = null } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index 77c54ec1eac3a3bc3c32bcd773c451d88de35f03..3992c3fb70b0b0f7c28a6a75ea6da60c74a81a3d 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import com.android.app.tracing.coroutines.createCoroutineTracingContext class HomeControlsDreamService @Inject @@ -53,7 +54,7 @@ constructor( ) : DreamService() { private val serviceJob = SupervisorJob() - private val serviceScope = CoroutineScope(bgDispatcher + serviceJob) + private val serviceScope = CoroutineScope(bgDispatcher + serviceJob + createCoroutineTracingContext("HomeControlsDreamService")) private val logger = DreamLogger(logBuffer, TAG) private lateinit var taskFragmentComponent: TaskFragmentComponent private val wakeLock: WakeLock by lazy { diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt index 7e2c9f89fa679a8fb7681ace773d9f17e0c00b63..4caf95b707b18d10972a9920af2c3d734b1356c5 100644 --- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.education.dagger +import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.CoreStartable import com.android.systemui.Flags import com.android.systemui.contextualeducation.GestureType @@ -56,7 +57,7 @@ interface ContextualEducationModule { fun provideEduDataStoreScope( @Background bgDispatcher: CoroutineDispatcher ): CoroutineScope { - return CoroutineScope(bgDispatcher + SupervisorJob()) + return CoroutineScope(bgDispatcher + SupervisorJob() + createCoroutineTracingContext("EduDataStoreScope")) } @EduClock diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt index 1daaa1128ba0fee30b20f37949df3c8f43eb7057..500c5b387ac6fb374d3724048219b0509eb9f54c 100644 --- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt +++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.education.data.model +import com.android.systemui.contextualeducation.GestureType import java.time.Instant /** @@ -23,6 +24,7 @@ import java.time.Instant * gesture stores its own model separately. */ data class GestureEduModel( + val gestureType: GestureType, val signalCount: Int = 0, val educationShownCount: Int = 0, val lastShortcutTriggeredTime: Instant? = null, diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt index 01f838ff1ea76eb9781578fecda378f83cc147aa..29785959de18c35cb9bf60b1157b643c1cd94f90 100644 --- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt @@ -17,6 +17,8 @@ package com.android.systemui.education.data.repository import android.content.Context +import android.hardware.input.InputManager +import android.hardware.input.KeyGestureEvent import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory @@ -25,23 +27,31 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.preferencesDataStoreFile +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope import com.android.systemui.education.data.model.EduDeviceConnectionTime import com.android.systemui.education.data.model.GestureEduModel +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Instant +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Provider import kotlin.properties.Delegates.notNull +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map /** @@ -64,6 +74,8 @@ interface ContextualEducationRepository { suspend fun updateEduDeviceConnectionTime( transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime ) + + val keyboardShortcutTriggered: Flow } /** @@ -75,9 +87,13 @@ class UserContextualEducationRepository @Inject constructor( @Application private val applicationContext: Context, - @EduDataStoreScope private val dataStoreScopeProvider: Provider + @EduDataStoreScope private val dataStoreScopeProvider: Provider, + private val inputManager: InputManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, ) : ContextualEducationRepository { companion object { + const val TAG = "UserContextualEducationRepository" + const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT" const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN" const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME" @@ -98,6 +114,30 @@ constructor( @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) private val prefData: Flow = datastore.filterNotNull().flatMapLatest { it.data } + override val keyboardShortcutTriggered: Flow = + conflatedCallbackFlow { + val listener = + InputManager.KeyGestureEventListener { event -> + // Only store keyboard shortcut time for gestures providing keyboard + // education + val shortcutType = + when (event.keyGestureType) { + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS + + else -> null + } + + if (shortcutType != null) { + trySendWithFailureLogging(shortcutType, TAG) + } + } + + inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener) + awaitClose { inputManager.unregisterKeyGestureEventListener(listener) } + } + .flowOn(backgroundDispatcher) + override fun setUser(userId: Int) { dataStoreScope?.cancel() val newDsScope = dataStoreScopeProvider.get() @@ -136,7 +176,8 @@ constructor( preferences[getLastEducationTimeKey(gestureType)]?.let { Instant.ofEpochSecond(it) }, - userId = userId + userId = userId, + gestureType = gestureType, ) } diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt index 10be26e1ce0fa97411f04e0bf68859149ad528cf..c88b36495ac2eb918cf7edc1a211b9485e645b32 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt @@ -18,7 +18,10 @@ package com.android.systemui.education.domain.interactor import com.android.systemui.CoreStartable import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.dagger.ContextualEducationModule.EduClock @@ -53,6 +56,13 @@ constructor( ) : CoreStartable { val backGestureModelFlow = readEduModelsOnSignalCountChanged(BACK) + val homeGestureModelFlow = readEduModelsOnSignalCountChanged(HOME) + val overviewGestureModelFlow = readEduModelsOnSignalCountChanged(OVERVIEW) + val allAppsGestureModelFlow = readEduModelsOnSignalCountChanged(ALL_APPS) + val eduDeviceConnectionTimeFlow = + repository.readEduDeviceConnectionTime().distinctUntilChanged() + + val keyboardShortcutTriggered = repository.keyboardShortcutTriggered override fun start() { backgroundScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt index 43855d971a2a34b7f6a10e2cd1ff2fdbe4195059..faee326949645d3c366ded1086141dbae6732a1e 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt @@ -16,15 +16,8 @@ package com.android.systemui.education.domain.interactor -import android.hardware.input.InputManager -import android.hardware.input.InputManager.KeyGestureEventListener -import android.hardware.input.KeyGestureEvent import android.os.SystemProperties import com.android.systemui.CoreStartable -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.contextualeducation.GestureType -import com.android.systemui.contextualeducation.GestureType.ALL_APPS -import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.dagger.ContextualEducationModule.EduClock @@ -32,19 +25,19 @@ import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.education.shared.model.EducationInfo import com.android.systemui.education.shared.model.EducationUiType import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Clock -import java.util.concurrent.Executor import javax.inject.Inject import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.DurationUnit import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch /** Allow listening to new contextual education triggered */ @@ -55,7 +48,6 @@ constructor( @Background private val backgroundScope: CoroutineScope, private val contextualEducationInteractor: ContextualEducationInteractor, private val userInputDeviceRepository: UserInputDeviceRepository, - private val inputManager: InputManager, @EduClock private val clock: Clock, ) : CoreStartable { @@ -82,34 +74,32 @@ constructor( private val _educationTriggered = MutableStateFlow(null) val educationTriggered = _educationTriggered.asStateFlow() - private val keyboardShortcutTriggered: Flow = conflatedCallbackFlow { - val listener = KeyGestureEventListener { event -> - // Only store keyboard shortcut time for gestures providing keyboard education - val shortcutType = - when (event.keyGestureType) { - KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS - else -> null - } - - if (shortcutType != null) { - trySendWithFailureLogging(shortcutType, TAG) - } - } - - inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener) - awaitClose { inputManager.unregisterKeyGestureEventListener(listener) } - } - + @OptIn(ExperimentalCoroutinesApi::class) override fun start() { backgroundScope.launch { - contextualEducationInteractor.backGestureModelFlow.collect { - if (isUsageSessionExpired(it)) { - contextualEducationInteractor.startNewUsageSession(BACK) - } else if (isEducationNeeded(it)) { - _educationTriggered.value = EducationInfo(BACK, getEduType(it), it.userId) - contextualEducationInteractor.updateOnEduTriggered(BACK) + contextualEducationInteractor.eduDeviceConnectionTimeFlow + .flatMapLatest { + val gestureFlows = mutableListOf>() + if (it.touchpadFirstConnectionTime != null) { + gestureFlows.add(contextualEducationInteractor.backGestureModelFlow) + gestureFlows.add(contextualEducationInteractor.homeGestureModelFlow) + gestureFlows.add(contextualEducationInteractor.overviewGestureModelFlow) + } + + if (it.keyboardFirstConnectionTime != null) { + gestureFlows.add(contextualEducationInteractor.allAppsGestureModelFlow) + } + gestureFlows.merge() + } + .collect { + if (isUsageSessionExpired(it)) { + contextualEducationInteractor.startNewUsageSession(it.gestureType) + } else if (isEducationNeeded(it)) { + _educationTriggered.value = + EducationInfo(it.gestureType, getEduType(it), it.userId) + contextualEducationInteractor.updateOnEduTriggered(it.gestureType) + } } - } } backgroundScope.launch { @@ -139,7 +129,7 @@ constructor( } backgroundScope.launch { - keyboardShortcutTriggered.collect { + contextualEducationInteractor.keyboardShortcutTriggered.collect { contextualEducationInteractor.updateShortcutTriggerTime(it) } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 6318dc000c2118c98c2f06c70c8ca6af6eed26b0..0b775ab486bd84e2ddc1fb3c11c6e9a16060ceb0 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -31,9 +31,7 @@ import com.android.systemui.Flags.statusBarCallChipNotificationIcon import com.android.systemui.Flags.statusBarScreenSharingChips import com.android.systemui.Flags.statusBarUseReposForCallChip import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag @@ -62,10 +60,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha // SceneContainer dependencies SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta } - // ComposeLockscreen dependencies - ComposeLockscreen.token dependsOn KeyguardBottomAreaRefactor.token - ComposeLockscreen.token dependsOn MigrateClocksToBlueprint.token - // CommunalHub dependencies communalHub dependsOn MigrateClocksToBlueprint.token @@ -99,7 +93,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha get() = FlagToken( FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, - statusBarCallChipNotificationIcon() + statusBarCallChipNotificationIcon(), ) private inline val statusBarScreenSharingChipsToken diff --git a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt new file mode 100644 index 0000000000000000000000000000000000000000..62ab18bbb738d2a7505c48996d41ce9eb2f133ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.grid.ui.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.semantics.CollectionInfo +import androidx.compose.ui.semantics.CollectionItemInfo +import androidx.compose.ui.semantics.collectionInfo +import androidx.compose.ui.semantics.collectionItemInfo +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.max + +/** + * Horizontal (non lazy) grid that supports [spans] for its elements. + * + * The elements will be laid down vertically first, and then by columns. So assuming LTR layout, it + * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 rows): + * ``` + * 0 2 5 + * 0 2 6 + * 1 3 7 + * 4 + * ``` + * + * where repeated numbers show larger span. If an element doesn't fit in a column due to its span, + * it will start a new column. + * + * Elements in [spans] must be in the interval `[1, rows]` ([rows] > 0), and the composables are + * associated with the corresponding span based on their index. + * + * Due to the fact that elements are seen as a linear list that's laid out in a grid, the semantics + * represent the collection as a list of elements. + */ +@Composable +fun HorizontalSpannedGrid( + rows: Int, + columnSpacing: Dp, + rowSpacing: Dp, + spans: List, + modifier: Modifier = Modifier, + composables: @Composable BoxScope.(spanIndex: Int) -> Unit, +) { + SpannedGrid( + primarySpaces = rows, + crossAxisSpacing = rowSpacing, + mainAxisSpacing = columnSpacing, + spans = spans, + isVertical = false, + modifier = modifier, + composables = composables, + ) +} + +/** + * Horizontal (non lazy) grid that supports [spans] for its elements. + * + * The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it + * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns): + * ``` + * 0 0 1 + * 2 2 3 4 + * 5 6 7 + * ``` + * + * where repeated numbers show larger span. If an element doesn't fit in a row due to its span, it + * will start a new row. + * + * Elements in [spans] must be in the interval `[1, columns]` ([columns] > 0), and the composables + * are associated with the corresponding span based on their index. + * + * Due to the fact that elements are seen as a linear list that's laid out in a grid, the semantics + * represent the collection as a list of elements. + */ +@Composable +fun VerticalSpannedGrid( + columns: Int, + columnSpacing: Dp, + rowSpacing: Dp, + spans: List, + modifier: Modifier = Modifier, + composables: @Composable BoxScope.(spanIndex: Int) -> Unit, +) { + SpannedGrid( + primarySpaces = columns, + crossAxisSpacing = columnSpacing, + mainAxisSpacing = rowSpacing, + spans = spans, + isVertical = true, + modifier = modifier, + composables = composables, + ) +} + +@Composable +private fun SpannedGrid( + primarySpaces: Int, + crossAxisSpacing: Dp, + mainAxisSpacing: Dp, + spans: List, + isVertical: Boolean, + modifier: Modifier = Modifier, + composables: @Composable BoxScope.(spanIndex: Int) -> Unit, +) { + val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing) + spans.forEachIndexed { index, span -> + check(span in 1..primarySpaces) { + "Span out of bounds. Span at index $index has value of $span which is outside of the " + + "expected rance of [1, $primarySpaces]" + } + } + + if (isVertical) { + check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" } + check(mainAxisSpacing >= 0.dp) { "Negative rowSpacing $mainAxisSpacing" } + } else { + check(mainAxisSpacing >= 0.dp) { "Negative columnSpacing $mainAxisSpacing" } + check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" } + } + + val totalMainAxisGroups: Int = + remember(primarySpaces, spans) { + var currentAccumulated = 0 + var groups = 1 + spans.forEach { span -> + if (currentAccumulated + span <= primarySpaces) { + currentAccumulated += span + } else { + groups += 1 + currentAccumulated = span + } + } + groups + } + + val slotPositionsAndSizesCache = remember { + object { + var sizes = IntArray(0) + var positions = IntArray(0) + } + } + + Layout( + { + (0 until spans.size).map { spanIndex -> + Box( + Modifier.semantics { + collectionItemInfo = + if (isVertical) { + CollectionItemInfo(spanIndex, 1, 0, 1) + } else { + CollectionItemInfo(0, 1, spanIndex, 1) + } + } + ) { + composables(spanIndex) + } + } + }, + modifier.semantics { collectionInfo = CollectionInfo(spans.size, 1) }, + ) { measurables, constraints -> + check(measurables.size == spans.size) + val crossAxisSize = if (isVertical) constraints.maxWidth else constraints.maxHeight + check(crossAxisSize != Constraints.Infinity) { "Width must be constrained" } + if (slotPositionsAndSizesCache.sizes.size != primarySpaces) { + slotPositionsAndSizesCache.sizes = IntArray(primarySpaces) + slotPositionsAndSizesCache.positions = IntArray(primarySpaces) + } + calculateCellsCrossAxisSize( + crossAxisSize, + primarySpaces, + crossAxisSpacing.roundToPx(), + slotPositionsAndSizesCache.sizes, + ) + val cellSizesInCrossAxis = slotPositionsAndSizesCache.sizes + + // with is needed because of the double receiver (Density, Arrangement). + with(crossAxisArrangement) { + arrange( + crossAxisSize, + slotPositionsAndSizesCache.sizes, + LayoutDirection.Ltr, + slotPositionsAndSizesCache.positions, + ) + } + val startPositions = slotPositionsAndSizesCache.positions + + val mainAxisSpacingPx = mainAxisSpacing.roundToPx() + val mainAxisTotalGaps = (totalMainAxisGroups - 1) * mainAxisSpacingPx + val mainAxisSize = if (isVertical) constraints.maxHeight else constraints.maxWidth + val mainAxisElementConstraint = + if (mainAxisSize == Constraints.Infinity) { + Constraints.Infinity + } else { + max(0, (mainAxisSize - mainAxisTotalGaps) / totalMainAxisGroups) + } + + val mainAxisSizes = IntArray(totalMainAxisGroups) { 0 } + + var currentSlot = 0 + var mainAxisGroup = 0 + val placeables = + measurables.mapIndexed { index, measurable -> + val span = spans[index] + if (currentSlot + span > primarySpaces) { + currentSlot = 0 + mainAxisGroup += 1 + } + val crossAxisConstraint = + calculateWidth(cellSizesInCrossAxis, startPositions, currentSlot, span) + PlaceResult( + measurable.measure( + makeConstraint( + isVertical, + mainAxisElementConstraint, + crossAxisConstraint, + ) + ), + currentSlot, + mainAxisGroup, + ) + .also { + currentSlot += span + mainAxisSizes[mainAxisGroup] = + max( + mainAxisSizes[mainAxisGroup], + if (isVertical) it.placeable.height else it.placeable.width, + ) + } + } + + val mainAxisTotalSize = mainAxisTotalGaps + mainAxisSizes.sum() + val mainAxisStartingPoints = + mainAxisSizes.runningFold(0) { acc, value -> acc + value + mainAxisSpacingPx } + val height = if (isVertical) mainAxisTotalSize else crossAxisSize + val width = if (isVertical) crossAxisSize else mainAxisTotalSize + + layout(width, height) { + placeables.forEach { (placeable, slot, mainAxisGroup) -> + val x = + if (isVertical) { + startPositions[slot] + } else { + mainAxisStartingPoints[mainAxisGroup] + } + val y = + if (isVertical) { + mainAxisStartingPoints[mainAxisGroup] + } else { + startPositions[slot] + } + placeable.placeRelative(x, y) + } + } + } +} + +fun makeConstraint(isVertical: Boolean, mainAxisSize: Int, crossAxisSize: Int): Constraints { + return if (isVertical) { + Constraints(maxHeight = mainAxisSize, minWidth = crossAxisSize, maxWidth = crossAxisSize) + } else { + Constraints(maxWidth = mainAxisSize, minHeight = crossAxisSize, maxHeight = crossAxisSize) + } +} + +private fun calculateWidth(sizes: IntArray, positions: IntArray, startSlot: Int, span: Int): Int { + val crossAxisSize = + if (span == 1) { + sizes[startSlot] + } else { + val endSlot = startSlot + span - 1 + positions[endSlot] + sizes[endSlot] - positions[startSlot] + } + .coerceAtLeast(0) + return crossAxisSize +} + +private fun calculateCellsCrossAxisSize( + gridSize: Int, + slotCount: Int, + spacingPx: Int, + outArray: IntArray, +) { + check(outArray.size == slotCount) + val gridSizeWithoutSpacing = gridSize - spacingPx * (slotCount - 1) + val slotSize = gridSizeWithoutSpacing / slotCount + val remainingPixels = gridSizeWithoutSpacing % slotCount + outArray.indices.forEach { index -> + outArray[index] = slotSize + if (index < remainingPixels) 1 else 0 + } +} + +private data class PlaceResult( + val placeable: Placeable, + val slotIndex: Int, + val mainAxisGroup: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt index e50c05c7f6eded653f14b51734744b068527d425..e09e1987698d5dd522d59f33b2de1362b0622447 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -29,6 +29,7 @@ import com.android.systemui.animation.Expandable import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.QSLog +import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController @@ -44,12 +45,12 @@ import javax.inject.Inject * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects. * @property[effectDuration] The duration of the effect in ms. */ -// TODO(b/332902869): In addition from being injectable, we can consider making it a singleton class QSLongPressEffect @Inject constructor( private val vibratorHelper: VibratorHelper?, private val keyguardStateController: KeyguardStateController, + private val falsingManager: FalsingManager, @QSLog private val logBuffer: LogBuffer, ) { @@ -72,7 +73,7 @@ constructor( private val durations = vibratorHelper?.getPrimitiveDurations( VibrationEffect.Composition.PRIMITIVE_LOW_TICK, - VibrationEffect.Composition.PRIMITIVE_SPIN + VibrationEffect.Composition.PRIMITIVE_SPIN, ) private var longPressHint: VibrationEffect? = null @@ -152,15 +153,27 @@ constructor( logEvent(qsTile?.tileSpec, state, "animation completed") when (state) { State.RUNNING_FORWARD -> { - vibrate(snapEffect) - if (keyguardStateController.isUnlocked) { + val wasFalseLongTap = falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) + if (wasFalseLongTap) { + callback?.onResetProperties() + setState(State.IDLE) + logEvent(qsTile?.tileSpec, state, "false long click. No action triggered") + } else if (keyguardStateController.isUnlocked) { + vibrate(snapEffect) setState(State.LONG_CLICKED) + qsTile?.longClick(expandable) + logEvent(qsTile?.tileSpec, state, "long click action triggered") } else { + vibrate(snapEffect) callback?.onResetProperties() setState(State.IDLE) + qsTile?.longClick(expandable) + logEvent( + qsTile?.tileSpec, + state, + "properties reset and long click action triggered", + ) } - logEvent(qsTile?.tileSpec, state, "long click action triggered") - qsTile?.longClick(expandable) } State.RUNNING_BACKWARDS_FROM_UP -> { callback?.onEffectFinishedReversing() @@ -236,7 +249,7 @@ constructor( LongPressHapticBuilder.createLongPressHint( durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION, durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION, - effectDuration + effectDuration, ) setState(State.IDLE) return true @@ -265,7 +278,7 @@ constructor( } override fun dialogTransitionController( - cuj: DialogCuj?, + cuj: DialogCuj? ): DialogTransitionAnimator.Controller? = DialogTransitionAnimator.Controller.fromView(view, cuj) } @@ -298,7 +311,7 @@ constructor( str2 = event str3 = state.name }, - { "[long-press effect on $str1 tile] $str2 on state: $str3" } + { "[long-press effect on $str1 tile] $str2 on state: $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt index 95251749132d6a112ba3aace5ef78916de919bd0..48f5cb6dc21950dab5dff94a1858b492ec39d7ae 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt @@ -16,27 +16,27 @@ package com.android.systemui.inputdevice.tutorial +import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState +import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen as KeyboardTouchpadTutorialScreen +import com.android.systemui.log.ConstantStringsLogger +import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.MessageInitializer +import com.android.systemui.log.core.MessagePrinter import com.android.systemui.log.dagger.InputDeviceTutorialLog -import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen -import com.google.errorprone.annotations.CompileTimeConstant +import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen as TouchpadTutorialScreen import javax.inject.Inject private const val TAG = "InputDeviceTutorial" class InputDeviceTutorialLogger @Inject -constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) { +constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) : + ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { - fun log(@CompileTimeConstant s: String) { - buffer.log(TAG, LogLevel.INFO, message = s) - } - - fun logGoingToScreen(screen: Screen, context: TutorialContext) { - buffer.log( - TAG, - LogLevel.INFO, + fun logGoingToScreen(screen: TouchpadTutorialScreen, context: TutorialContext) { + logInfo( { str1 = screen.toString() str2 = context.string @@ -46,7 +46,58 @@ constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) { } fun logCloseTutorial(context: TutorialContext) { - buffer.log(TAG, LogLevel.INFO, { str1 = context.string }, { "Closing $str1" }) + logInfo({ str1 = context.string }, { "Closing $str1" }) + } + + fun logOpenTutorial(context: TutorialContext) { + logInfo({ str1 = context.string }, { "Opening $str1" }) + } + + fun logNextScreenMissingHardware(nextScreen: KeyboardTouchpadTutorialScreen) { + buffer.log( + TAG, + LogLevel.WARNING, + { str1 = nextScreen.toString() }, + { "next screen should be $str1 but required hardware is missing" } + ) + } + + fun logNextScreen(nextScreen: KeyboardTouchpadTutorialScreen) { + logInfo({ str1 = nextScreen.toString() }, { "going to $str1 screen" }) + } + + fun logNewConnectionState(connectionState: ConnectionState) { + logInfo( + { + bool1 = connectionState.touchpadConnected + bool2 = connectionState.keyboardConnected + }, + { "Received connection state: touchpad connected: $bool1 keyboard connected: $bool2" } + ) + } + + fun logMovingBetweenScreens( + previousScreen: KeyboardTouchpadTutorialScreen?, + currentScreen: KeyboardTouchpadTutorialScreen + ) { + logInfo( + { + str1 = previousScreen?.toString() ?: "NO_SCREEN" + str2 = currentScreen.toString() + }, + { "Moving from $str1 screen to $str2 screen" } + ) + } + + fun logGoingBack(previousScreen: KeyboardTouchpadTutorialScreen) { + logInfo({ str1 = previousScreen.toString() }, { "Going back to $str1 screen" }) + } + + private inline fun logInfo( + messageInitializer: MessageInitializer, + noinline messagePrinter: MessagePrinter + ) { + buffer.log(TAG, LogLevel.INFO, messageInitializer, messagePrinter) } enum class TutorialContext(val string: String) { diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt index b27135674fb1e9a1975b9a7d83d71cc2faf62360..f32c94b2bc0162a705c7381c47b0f6ced86e1663 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt @@ -22,6 +22,7 @@ import android.graphics.PorterDuffColorFilter import androidx.annotation.RawRes import androidx.annotation.StringRes import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.core.LinearEasing @@ -40,6 +41,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -67,21 +69,22 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionSta enum class TutorialActionState { NOT_STARTED, IN_PROGRESS, - FINISHED + FINISHED, } @Composable fun ActionTutorialContent( actionState: TutorialActionState, onDoneButtonClicked: () -> Unit, - config: TutorialScreenConfig + config: TutorialScreenConfig, ) { Column( verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize() .background(config.colors.background) - .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp) + .safeDrawingPadding() + .padding(start = 48.dp, top = 100.dp, end = 48.dp, bottom = 8.dp), ) { Row(modifier = Modifier.fillMaxWidth().weight(1f)) { TutorialDescription( @@ -92,16 +95,18 @@ fun ActionTutorialContent( bodyTextId = if (actionState == FINISHED) config.strings.bodySuccessResId else config.strings.bodyResId, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) Spacer(modifier = Modifier.width(76.dp)) TutorialAnimation( actionState, config, - modifier = Modifier.weight(1f).padding(top = 8.dp) + modifier = Modifier.weight(1f).padding(top = 8.dp), ) } - DoneButton(onDoneButtonClicked = onDoneButtonClicked) + AnimatedVisibility(visible = actionState == FINISHED, enter = fadeIn()) { + DoneButton(onDoneButtonClicked = onDoneButtonClicked) + } } } @@ -110,19 +115,19 @@ fun TutorialDescription( @StringRes titleTextId: Int, titleColor: Color, @StringRes bodyTextId: Int, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Column(verticalArrangement = Arrangement.Top, modifier = modifier) { Text( text = stringResource(id = titleTextId), style = MaterialTheme.typography.displayLarge, - color = titleColor + color = titleColor, ) Spacer(modifier = Modifier.height(16.dp)) Text( text = stringResource(id = bodyTextId), style = MaterialTheme.typography.bodyLarge, - color = Color.White + color = Color.White, ) } } @@ -131,7 +136,7 @@ fun TutorialDescription( fun TutorialAnimation( actionState: TutorialActionState, config: TutorialScreenConfig, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box(modifier = modifier.fillMaxWidth()) { AnimatedContent( @@ -152,18 +157,18 @@ fun TutorialAnimation( // state which shares initial animation frame with both FINISHED and NOT_STARTED EnterTransition.None togetherWith ExitTransition.None } - } + }, ) { state -> when (state) { NOT_STARTED -> EducationAnimation( config.animations.educationResId, - config.colors.animationColors + config.colors.animationColors, ) IN_PROGRESS -> FrozenSuccessAnimation( config.animations.successResId, - config.colors.animationColors + config.colors.animationColors, ) FINISHED -> SuccessAnimation(config.animations.successResId, config.colors.animationColors) @@ -175,7 +180,7 @@ fun TutorialAnimation( @Composable private fun FrozenSuccessAnimation( @RawRes successAnimationId: Int, - animationProperties: LottieDynamicProperties + animationProperties: LottieDynamicProperties, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) LottieAnimation( @@ -188,7 +193,7 @@ private fun FrozenSuccessAnimation( @Composable private fun EducationAnimation( @RawRes educationAnimationId: Int, - animationProperties: LottieDynamicProperties + animationProperties: LottieDynamicProperties, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId)) val progress by @@ -203,7 +208,7 @@ private fun EducationAnimation( @Composable private fun SuccessAnimation( @RawRes successAnimationId: Int, - animationProperties: LottieDynamicProperties + animationProperties: LottieDynamicProperties, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) val progress by animateLottieCompositionAsState(composition, iterations = 1) @@ -217,13 +222,13 @@ private fun SuccessAnimation( @Composable fun rememberColorFilterProperty( layerName: String, - color: Color + color: Color, ): LottieDynamicProperty { return rememberLottieDynamicProperty( LottieProperty.COLOR_FILTER, value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP), // "**" below means match zero or more layers, so ** layerName ** means find layer with that // name at any depth - keyPath = arrayOf("**", layerName, "**") + keyPath = arrayOf("**", layerName, "**"), ) } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt index 1adc285e6bb56be58bb513ad25c6fcd4d0b54f2e..c130c6c7fe127e15deb3d1db47e31e2427904a58 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt @@ -28,6 +28,8 @@ import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import com.android.compose.theme.PlatformTheme +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel @@ -48,6 +50,7 @@ class KeyboardTouchpadTutorialActivity constructor( private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider, private val touchpadTutorialScreensProvider: Optional, + private val logger: InputDeviceTutorialLogger, ) : ComponentActivity() { companion object { @@ -74,6 +77,7 @@ constructor( lifecycleScope.launch { vm.closeActivity.collect { finish -> if (finish) { + logger.logCloseTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL) finish() } } @@ -81,6 +85,9 @@ constructor( setContent { PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) } } + if (savedInstanceState == null) { + logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt index 315c102e94d035ca1122b5728836b68034233940..5cf19677a98e2325256d2b33e8c2342433fa8d3e 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY @@ -47,6 +48,7 @@ class KeyboardTouchpadTutorialViewModel( private val gesturesInteractor: Optional, private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor, private val hasTouchpadTutorialScreens: Boolean, + private val logger: InputDeviceTutorialLogger, handle: SavedStateHandle ) : ViewModel(), DefaultLifecycleObserver { @@ -68,7 +70,10 @@ class KeyboardTouchpadTutorialViewModel( init { viewModelScope.launch { - keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it } + keyboardTouchpadConnectionInteractor.connectionState.collect { + logger.logNewConnectionState(connectionState) + connectionState = it + } } viewModelScope.launch { @@ -89,7 +94,14 @@ class KeyboardTouchpadTutorialViewModel( viewModelScope.launch { // close activity if screen requires touchpad but we don't have it. This can only happen // when current sysui build doesn't contain touchpad module dependency - _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true } + _screen + .filterNot { it.canBeShown() } + .collect { + logger.e( + "Touchpad is connected but touchpad module is missing, something went wrong" + ) + _closeActivity.value = true + } } } @@ -114,11 +126,14 @@ class KeyboardTouchpadTutorialViewModel( if (requiredHardwarePresent(nextScreen)) { break } + logger.logNextScreenMissingHardware(nextScreen) nextScreen = nextScreen.next() } if (nextScreen == null) { + logger.d("Final screen reached, closing tutorial") _closeActivity.value = true } else { + logger.logNextScreen(nextScreen) _screen.value = nextScreen screensBackStack.add(nextScreen) } @@ -127,6 +142,7 @@ class KeyboardTouchpadTutorialViewModel( private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) { + logger.logMovingBetweenScreens(previousScreen, currentScreen) if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return previousScreen?.let { clearDeviceStateForScreen(it) } when (currentScreen.requiredHardware) { @@ -153,6 +169,7 @@ class KeyboardTouchpadTutorialViewModel( _closeActivity.value = true } else { screensBackStack.removeLast() + logger.logGoingBack(screensBackStack.last()) _screen.value = screensBackStack.last() } } @@ -162,6 +179,7 @@ class KeyboardTouchpadTutorialViewModel( constructor( private val gesturesInteractor: Optional, private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor, + private val logger: InputDeviceTutorialLogger, @Assisted private val hasTouchpadTutorialScreens: Boolean, ) : AbstractSavedStateViewModelFactory() { @@ -180,6 +198,7 @@ class KeyboardTouchpadTutorialViewModel( gesturesInteractor, keyboardTouchpadConnected, hasTouchpadTutorialScreens, + logger, handle ) as T diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index 11a0543d97d1c944b48289f5704a1beb16248ec4..93cd1cf487b0517fb867e4254488641e35b691f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRowScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -71,8 +72,6 @@ import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -116,7 +115,6 @@ import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.zIndex import com.android.compose.ui.graphics.painter.rememberDrawablePainter -import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType @@ -188,10 +186,7 @@ private fun ActiveShortcutHelper( } } -@Composable -private fun shouldUseSinglePane() = - LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact || - LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact +@Composable private fun shouldUseSinglePane() = hasCompactWindowSize() @Composable private fun ShortcutHelperSinglePane( @@ -286,7 +281,7 @@ private fun CategoryItemSinglePane( Column { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp) + modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp), ) { ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.icon) Spacer(modifier = Modifier.width(16.dp)) @@ -425,7 +420,7 @@ private fun ShortcutHelperTwoPane( onKeyboardSettingsClicked: () -> Unit, ) { val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType } - Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) { + Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) { TitleBar() Spacer(modifier = Modifier.height(12.dp)) Row(Modifier.fillMaxWidth()) { @@ -717,25 +712,24 @@ private fun CategoryItemTwoPane( colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent), ) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - SelectableShortcutSurface( selected = selected, onClick = onClick, - modifier = - Modifier.semantics { role = Role.Tab } - .heightIn(min = 64.dp) - .fillMaxWidth() - .outlineFocusModifier( - isFocused = isFocused, - focusColor = MaterialTheme.colorScheme.secondary, - padding = 2.dp, - cornerRadius = 33.dp, - ), + modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = colors.containerColor(selected).value, - interactionSource = interactionSource + interactionsConfig = + InteractionsConfig( + hoverOverlayColor = MaterialTheme.colorScheme.onSurface, + hoverOverlayAlpha = 0.11f, + pressedOverlayColor = MaterialTheme.colorScheme.onSurface, + pressedOverlayAlpha = 0.15f, + focusOutlineColor = MaterialTheme.colorScheme.secondary, + focusOutlineStrokeWidth = 3.dp, + focusOutlinePadding = 2.dp, + surfaceCornerRadius = 28.dp, + focusOutlineCornerRadius = 33.dp, + ), ) { Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) { ShortcutCategoryIcon( @@ -802,6 +796,8 @@ private fun TitleBar() { style = MaterialTheme.typography.headlineSmall, ) }, + windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp), + expandedHeight = 64.dp, ) } @@ -836,6 +832,7 @@ private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) { onSearch = {}, leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) }, + windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp), content = {}, ) } @@ -843,25 +840,28 @@ private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) { @Composable private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) { val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() ClickableShortcutSurface( onClick = onClick, shape = RoundedCornerShape(24.dp), color = Color.Transparent, - modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth(), - interactionSource = interactionSource + modifier = + Modifier.semantics { role = Role.Button }.fillMaxWidth().padding(horizontal = 12.dp), + interactionSource = interactionSource, + interactionsConfig = + InteractionsConfig( + hoverOverlayColor = MaterialTheme.colorScheme.onSurface, + hoverOverlayAlpha = 0.11f, + pressedOverlayColor = MaterialTheme.colorScheme.onSurface, + pressedOverlayAlpha = 0.15f, + focusOutlineColor = MaterialTheme.colorScheme.secondary, + focusOutlinePadding = 8.dp, + focusOutlineStrokeWidth = 3.dp, + surfaceCornerRadius = 24.dp, + focusOutlineCornerRadius = 28.dp, + hoverPadding = 8.dp, + ), ) { - Row( - modifier = - Modifier.padding(horizontal = 12.dp, vertical = 16.dp) - .outlineFocusModifier( - isFocused = isFocused, - focusColor = MaterialTheme.colorScheme.secondary, - padding = 8.dp, - cornerRadius = 28.dp, - ), - verticalAlignment = Alignment.CenterVertically, - ) { + Row(verticalAlignment = Alignment.CenterVertically) { Text( "Keyboard Settings", color = MaterialTheme.colorScheme.onSurfaceVariant, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..1f0d696eebd6743b5a3209895377f677b40205d3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.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.systemui.keyboard.shortcut.ui.composable + +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import com.android.compose.windowsizeclass.LocalWindowSizeClass + +/** + * returns true if either size of the window is compact. This represents majority of phone windows + * portrait + */ +@Composable +fun hasCompactWindowSize() = + LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact || + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt index 3ba3bd8fdc2f144706a4ef9af7cf9e4e6c3a91ef..e49ce6062be3522afbe4de958f35915a59ed8457 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt @@ -17,10 +17,16 @@ package com.android.systemui.keyboard.shortcut.ui.composable import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.IndicationNodeFactory import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.FocusInteraction +import androidx.compose.foundation.interaction.HoverInteraction +import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.selection.selectable import androidx.compose.material3.ColorScheme @@ -35,17 +41,27 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import com.android.compose.modifiers.thenIf +import kotlinx.coroutines.launch /** * A selectable surface with no default focus/hover indications. @@ -67,15 +83,17 @@ fun SelectableShortcutSurface( shadowElevation: Dp = 0.dp, border: BorderStroke? = null, interactionSource: MutableInteractionSource? = null, - content: @Composable () -> Unit + interactionsConfig: InteractionsConfig = InteractionsConfig(), + content: @Composable () -> Unit, ) { @Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation CompositionLocalProvider( LocalContentColor provides contentColor, - LocalAbsoluteTonalElevation provides absoluteElevation + LocalAbsoluteTonalElevation provides absoluteElevation, ) { + val isFocused = interactionSource.collectIsFocusedAsState() Box( modifier = modifier @@ -85,16 +103,18 @@ fun SelectableShortcutSurface( backgroundColor = surfaceColorAtElevation(color = color, elevation = absoluteElevation), border = border, - shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() } + shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }, ) .selectable( selected = selected, interactionSource = interactionSource, - indication = null, + indication = + ShortcutHelperIndication(interactionSource, interactionsConfig), enabled = enabled, - onClick = onClick - ), - propagateMinConstraints = true + onClick = onClick, + ) + .thenIf(isFocused.value) { Modifier.zIndex(1f) }, + propagateMinConstraints = true, ) { content() } @@ -120,14 +140,15 @@ fun ClickableShortcutSurface( shadowElevation: Dp = 0.dp, border: BorderStroke? = null, interactionSource: MutableInteractionSource? = null, - content: @Composable () -> Unit + interactionsConfig: InteractionsConfig = InteractionsConfig(), + content: @Composable () -> Unit, ) { @Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation CompositionLocalProvider( LocalContentColor provides contentColor, - LocalAbsoluteTonalElevation provides absoluteElevation + LocalAbsoluteTonalElevation provides absoluteElevation, ) { Box( modifier = @@ -138,15 +159,16 @@ fun ClickableShortcutSurface( backgroundColor = surfaceColorAtElevation(color = color, elevation = absoluteElevation), border = border, - shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() } + shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }, ) .clickable( interactionSource = interactionSource, - indication = null, + indication = + ShortcutHelperIndication(interactionSource, interactionsConfig), enabled = enabled, - onClick = onClick + onClick = onClick, ), - propagateMinConstraints = true + propagateMinConstraints = true, ) { content() } @@ -195,5 +217,105 @@ private fun Modifier.surface( } .thenIf(border != null) { Modifier.border(border!!, shape) } .background(color = backgroundColor, shape = shape) - .clip(shape) } + +private class ShortcutHelperInteractionsNode( + private val interactionSource: InteractionSource, + private val interactionsConfig: InteractionsConfig, +) : Modifier.Node(), DrawModifierNode { + + var isFocused = mutableStateOf(false) + var isHovered = mutableStateOf(false) + var isPressed = mutableStateOf(false) + + override fun onAttach() { + coroutineScope.launch { + val hoverInteractions = mutableListOf() + val focusInteractions = mutableListOf() + val pressInteractions = mutableListOf() + + interactionSource.interactions.collect { interaction -> + when (interaction) { + is FocusInteraction.Focus -> focusInteractions.add(interaction) + is FocusInteraction.Unfocus -> focusInteractions.remove(interaction.focus) + is HoverInteraction.Enter -> hoverInteractions.add(interaction) + is HoverInteraction.Exit -> hoverInteractions.remove(interaction.enter) + is PressInteraction.Press -> pressInteractions.add(interaction) + is PressInteraction.Release -> pressInteractions.remove(interaction.press) + is PressInteraction.Cancel -> pressInteractions.add(interaction.press) + } + isHovered.value = hoverInteractions.isNotEmpty() + isPressed.value = pressInteractions.isNotEmpty() + isFocused.value = focusInteractions.isNotEmpty() + } + } + } + + override fun ContentDrawScope.draw() { + + fun getRectangleWithPadding(padding: Dp, size: Size): Rect { + return Rect(Offset.Zero, size).let { + if (interactionsConfig.focusOutlinePadding > 0.dp) { + it.inflate(padding.toPx()) + } else { + it.deflate(padding.unaryMinus().toPx()) + } + } + } + + drawContent() + if (isHovered.value) { + val hoverRect = getRectangleWithPadding(interactionsConfig.pressedPadding, size) + drawRoundRect( + color = interactionsConfig.hoverOverlayColor, + alpha = interactionsConfig.hoverOverlayAlpha, + cornerRadius = CornerRadius(interactionsConfig.surfaceCornerRadius.toPx()), + topLeft = hoverRect.topLeft, + size = hoverRect.size, + ) + } + if (isPressed.value) { + val pressedRect = getRectangleWithPadding(interactionsConfig.pressedPadding, size) + drawRoundRect( + color = interactionsConfig.pressedOverlayColor, + alpha = interactionsConfig.pressedOverlayAlpha, + cornerRadius = CornerRadius(interactionsConfig.surfaceCornerRadius.toPx()), + topLeft = pressedRect.topLeft, + size = pressedRect.size, + ) + } + if (isFocused.value) { + val focusOutline = getRectangleWithPadding(interactionsConfig.focusOutlinePadding, size) + drawRoundRect( + color = interactionsConfig.focusOutlineColor, + style = Stroke(width = interactionsConfig.focusOutlineStrokeWidth.toPx()), + topLeft = focusOutline.topLeft, + size = focusOutline.size, + cornerRadius = CornerRadius(interactionsConfig.focusOutlineCornerRadius.toPx()), + ) + } + } +} + +data class ShortcutHelperIndication( + private val interactionSource: InteractionSource, + private val interactionsConfig: InteractionsConfig, +) : IndicationNodeFactory { + override fun create(interactionSource: InteractionSource): DelegatableNode { + return ShortcutHelperInteractionsNode(interactionSource, interactionsConfig) + } +} + +data class InteractionsConfig( + val hoverOverlayColor: Color = Color.Transparent, + val hoverOverlayAlpha: Float = 0.0f, + val pressedOverlayColor: Color = Color.Transparent, + val pressedOverlayAlpha: Float = 0.0f, + val focusOutlineColor: Color = Color.Transparent, + val focusOutlineStrokeWidth: Dp = 0.dp, + val focusOutlinePadding: Dp = 0.dp, + val surfaceCornerRadius: Dp = 0.dp, + val focusOutlineCornerRadius: Dp = 0.dp, + val hoverPadding: Dp = 0.dp, + val pressedPadding: Dp = hoverPadding, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt index 799999aff29b419c89bcfe2e5c8ea989a1028f65..b9a16c402e59c2b1af04844c5b090d8abd51c3c2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt @@ -18,31 +18,43 @@ package com.android.systemui.keyboard.shortcut.ui.view import android.content.ActivityNotFoundException import android.content.Intent -import android.graphics.Insets +import android.content.res.Configuration import android.os.Bundle import android.provider.Settings -import android.view.View -import android.view.WindowInsets -import androidx.activity.BackEventCompat import androidx.activity.ComponentActivity -import androidx.activity.OnBackPressedCallback +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Surface +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext -import androidx.core.view.updatePadding +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import com.android.compose.theme.PlatformTheme import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper +import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel import com.android.systemui.res.R import com.android.systemui.settings.UserTracker -import com.android.systemui.util.dpToPx -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback -import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN import javax.inject.Inject import kotlinx.coroutines.launch @@ -55,52 +67,58 @@ class ShortcutHelperActivity constructor(private val userTracker: UserTracker, private val viewModel: ShortcutHelperViewModel) : ComponentActivity() { - private val bottomSheetContainer - get() = requireViewById(R.id.shortcut_helper_sheet_container) - - private val bottomSheet - get() = requireViewById(R.id.shortcut_helper_sheet) - - private val bottomSheetBehavior - get() = BottomSheetBehavior.from(bottomSheet) - override fun onCreate(savedInstanceState: Bundle?) { setupEdgeToEdge() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_keyboard_shortcut_helper) - setUpWidth() - expandBottomSheet() - setUpInsets() - setUpPredictiveBack() - setUpSheetDismissListener() - setUpDismissOnTouchOutside() - setUpComposeView() + setContent { Content() } observeFinishRequired() viewModel.onViewOpened() } - private fun setUpWidth() { - // we override this because when maxWidth isn't specified, material imposes a max width - // constraint on bottom sheets on larger screens which is smaller than our desired width. - bottomSheetBehavior.maxWidth = - resources.getDimension(R.dimen.shortcut_helper_width).dpToPx(resources).toInt() + @Composable + private fun Content() { + CompositionLocalProvider(LocalContext provides userTracker.userContext) { + PlatformTheme { BottomSheet { finish() } } + } } - private fun setUpComposeView() { - requireViewById(R.id.shortcut_helper_compose_container).apply { - setContent { - CompositionLocalProvider(LocalContext provides userTracker.userContext) { - PlatformTheme { - val shortcutsUiState by - viewModel.shortcutsUiState.collectAsStateWithLifecycle() - ShortcutHelper( - shortcutsUiState = shortcutsUiState, - onKeyboardSettingsClicked = ::onKeyboardSettingsClicked, - onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) }, - ) - } - } - } + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun BottomSheet(onDismiss: () -> Unit) { + ModalBottomSheet( + onDismissRequest = { onDismiss() }, + modifier = + Modifier.width(getWidth()).padding(top = getTopPadding()).onKeyEvent { + if (it.key == Key.Escape) { + onDismiss() + true + } else false + }, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + dragHandle = { DragHandle() }, + ) { + val shortcutsUiState by viewModel.shortcutsUiState.collectAsStateWithLifecycle() + ShortcutHelper( + shortcutsUiState = shortcutsUiState, + onKeyboardSettingsClicked = ::onKeyboardSettingsClicked, + onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) }, + ) + } + } + + @Composable + fun DragHandle() { + val dragHandleContentDescription = + stringResource(id = R.string.shortcut_helper_content_description_drag_handle) + Surface( + modifier = + Modifier.padding(top = 16.dp, bottom = 6.dp).semantics { + contentDescription = dragHandleContentDescription + }, + color = MaterialTheme.colorScheme.outlineVariant, + shape = MaterialTheme.shapes.extraLarge, + ) { + Box(Modifier.size(width = 32.dp, height = 4.dp)) } } @@ -139,81 +157,27 @@ constructor(private val userTracker: UserTracker, private val viewModel: Shortcu window.setDecorFitsSystemWindows(false) } - private fun setUpInsets() { - bottomSheetContainer.setOnApplyWindowInsetsListener { _, insets -> - val safeDrawingInsets = insets.safeDrawing - // Make sure the bottom sheet is not covered by the status bar. - bottomSheetBehavior.maxHeight = - windowManager.maximumWindowMetrics.bounds.height() - safeDrawingInsets.top - // Make sure the contents inside of the bottom sheet are not hidden by system bars, or - // cutouts. - bottomSheet.updatePadding( - left = safeDrawingInsets.left, - right = safeDrawingInsets.right, - bottom = safeDrawingInsets.bottom, - ) - // The bottom sheet has to be expanded only after setting up insets, otherwise there is - // a bug and it will not use full height. - expandBottomSheet() - - // Return CONSUMED if you don't want want the window insets to keep passing - // down to descendant views. - WindowInsets.CONSUMED - } - } - - private fun expandBottomSheet() { - bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED - bottomSheetBehavior.skipCollapsed = true + @Composable + private fun getTopPadding(): Dp { + return if (hasCompactWindowSize()) DefaultTopPadding else LargeScreenTopPadding } - private fun setUpPredictiveBack() { - val onBackPressedCallback = - object : OnBackPressedCallback(/* enabled= */ true) { - override fun handleOnBackStarted(backEvent: BackEventCompat) { - bottomSheetBehavior.startBackProgress(backEvent) - } - - override fun handleOnBackProgressed(backEvent: BackEventCompat) { - bottomSheetBehavior.updateBackProgress(backEvent) - } - - override fun handleOnBackPressed() { - bottomSheetBehavior.handleBackInvoked() - } - - override fun handleOnBackCancelled() { - bottomSheetBehavior.cancelBackProgress() - } + @Composable + private fun getWidth(): Dp { + return if (hasCompactWindowSize()) { + DefaultWidth + } else + when (LocalConfiguration.current.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> LargeScreenWidthLandscape + else -> LargeScreenWidthPortrait } - onBackPressedDispatcher.addCallback( - owner = this, - onBackPressedCallback = onBackPressedCallback, - ) } - private fun setUpSheetDismissListener() { - bottomSheetBehavior.addBottomSheetCallback( - object : BottomSheetCallback() { - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == STATE_HIDDEN) { - finish() - } - } - - override fun onSlide(bottomSheet: View, slideOffset: Float) {} - } - ) - } - - private fun setUpDismissOnTouchOutside() { - bottomSheetContainer.setOnClickListener { finish() } + companion object { + private val DefaultTopPadding = 64.dp + private val LargeScreenTopPadding = 72.dp + private val DefaultWidth = 412.dp + private val LargeScreenWidthPortrait = 704.dp + private val LargeScreenWidthLandscape = 864.dp } } - -private val WindowInsets.safeDrawing - get() = - getInsets(WindowInsets.Type.systemBars()) - .union(getInsets(WindowInsets.Type.displayCutout())) - -private fun Insets.union(insets: Insets): Insets = Insets.max(this, insets) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 342325ffee91ba414942d7a1ba4a266c4c0d9816..6df8355550a083becba1bcea43fb3d4e7cf875e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -54,29 +54,25 @@ class CustomizationProvider : addURI( Contract.AUTHORITY, Contract.LockScreenQuickAffordances.qualifiedTablePath( - Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME, + Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME ), MATCH_CODE_ALL_SLOTS, ) addURI( Contract.AUTHORITY, Contract.LockScreenQuickAffordances.qualifiedTablePath( - Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME, + Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME ), MATCH_CODE_ALL_AFFORDANCES, ) addURI( Contract.AUTHORITY, Contract.LockScreenQuickAffordances.qualifiedTablePath( - Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME, + Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME ), MATCH_CODE_ALL_SELECTIONS, ) - addURI( - Contract.AUTHORITY, - Contract.FlagsTable.TABLE_NAME, - MATCH_CODE_ALL_FLAGS, - ) + addURI(Contract.AUTHORITY, Contract.FlagsTable.TABLE_NAME, MATCH_CODE_ALL_FLAGS) } override fun onCreate(): Boolean { @@ -106,15 +102,15 @@ class CustomizationProvider : when (uriMatcher.match(uri)) { MATCH_CODE_ALL_SLOTS -> Contract.LockScreenQuickAffordances.qualifiedTablePath( - Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME, + Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME ) MATCH_CODE_ALL_AFFORDANCES -> Contract.LockScreenQuickAffordances.qualifiedTablePath( - Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME, + Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME ) MATCH_CODE_ALL_SELECTIONS -> Contract.LockScreenQuickAffordances.qualifiedTablePath( - Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME, + Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME ) MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME else -> null @@ -128,6 +124,7 @@ class CustomizationProvider : } override fun insert(uri: Uri, values: ContentValues?): Uri? { + if (!::mainDispatcher.isInitialized) return null if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { throw UnsupportedOperationException() } @@ -142,6 +139,7 @@ class CustomizationProvider : selectionArgs: Array?, sortOrder: String?, ): Cursor? { + if (!::mainDispatcher.isInitialized) return null return runBlocking("$TAG#query", mainDispatcher) { when (uriMatcher.match(uri)) { MATCH_CODE_ALL_AFFORDANCES -> queryAffordances() @@ -163,11 +161,8 @@ class CustomizationProvider : return 0 } - override fun delete( - uri: Uri, - selection: String?, - selectionArgs: Array?, - ): Int { + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + if (!::mainDispatcher.isInitialized) return 0 if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { throw UnsupportedOperationException() } @@ -232,11 +227,7 @@ class CustomizationProvider : throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!") } - val success = - interactor.select( - slotId = slotId, - affordanceId = affordanceId, - ) + val success = interactor.select(slotId = slotId, affordanceId = affordanceId) return if (success) { Log.d(TAG, "Successfully selected $affordanceId for slot $slotId") @@ -318,22 +309,14 @@ class CustomizationProvider : ) .apply { interactor.getSlotPickerRepresentations().forEach { representation -> - addRow( - arrayOf( - representation.id, - representation.maxSelectedAffordances, - ) - ) + addRow(arrayOf(representation.id, representation.maxSelectedAffordances)) } } } private suspend fun queryFlags(): Cursor { return MatrixCursor( - arrayOf( - Contract.FlagsTable.Columns.NAME, - Contract.FlagsTable.Columns.VALUE, - ) + arrayOf(Contract.FlagsTable.Columns.NAME, Contract.FlagsTable.Columns.VALUE) ) .apply { interactor.getPickerFlags().forEach { flag -> @@ -351,10 +334,7 @@ class CustomizationProvider : } } - private suspend fun deleteSelection( - uri: Uri, - selectionArgs: Array?, - ): Int { + private suspend fun deleteSelection(uri: Uri, selectionArgs: Array?): Int { if (selectionArgs == null) { throw IllegalArgumentException( "Cannot delete selection, selection arguments not included!" @@ -372,11 +352,7 @@ class CustomizationProvider : ) } - val deleted = - interactor.unselect( - slotId = slotId, - affordanceId = affordanceId, - ) + val deleted = interactor.unselect(slotId = slotId, affordanceId = affordanceId) return if (deleted) { Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index df0f10acac1f8bbff70c61e6c536c9fceb13fa4d..416eabae78eb1949115b9e1b2cddf06d54bd2f90 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -24,12 +24,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.END -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import androidx.constraintlayout.widget.ConstraintSet.START -import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout @@ -47,7 +41,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder @@ -128,7 +121,7 @@ constructor( keyguardStatusViewComponentFactory.build( LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, null) as KeyguardStatusView, - context.display + context.display, ) val controller = statusViewComponent.keyguardStatusViewController controller.init() @@ -143,29 +136,12 @@ constructor( initializeViews() if (!SceneContainerFlag.isEnabled) { - if (ComposeLockscreen.isEnabled) { - val composeView = - createLockscreen( - context = context, - viewModelFactory = lockscreenContentViewModelFactory, - blueprints = lockscreenSceneBlueprintsLazy.get(), - ) - composeView.id = View.generateViewId() - val cs = ConstraintSet() - cs.clone(keyguardRootView) - cs.connect(composeView.id, START, PARENT_ID, START) - cs.connect(composeView.id, END, PARENT_ID, END) - cs.connect(composeView.id, TOP, PARENT_ID, TOP) - cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM) - keyguardRootView.addView(composeView) - } else { - KeyguardBlueprintViewBinder.bind( - keyguardRootView, - keyguardBlueprintViewModel, - keyguardClockViewModel, - smartspaceViewModel, - ) - } + KeyguardBlueprintViewBinder.bind( + keyguardRootView, + keyguardBlueprintViewModel, + keyguardClockViewModel, + smartspaceViewModel, + ) } if (deviceEntryUnlockTrackerViewBinder.isPresent) { deviceEntryUnlockTrackerViewBinder.get().bind(keyguardRootView) @@ -247,7 +223,7 @@ constructor( LockscreenContent( viewModelFactory = viewModelFactory, blueprints = sceneBlueprints, - clockInteractor = clockInteractor + clockInteractor = clockInteractor, ) ) { Content(modifier = Modifier.fillMaxSize()) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 3b1569d7f79b95267f981dc345ce1b1f43f3e92b..ba23eb341b89b918ddf04d3dd91f94194e90b70a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -42,6 +42,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground; import static com.android.systemui.Flags.relockWithPowerButtonImmediately; +import static com.android.systemui.Flags.simPinBouncerReset; import static com.android.systemui.Flags.translucentOccludingActivityFix; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; @@ -238,7 +239,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000; private static final boolean DEBUG = KeyguardConstants.DEBUG; - private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES; private final static String TAG = "KeyguardViewMediator"; @@ -346,6 +346,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private boolean mShuttingDown; private boolean mDozing; private boolean mAnimatingScreenOff; + private boolean mIgnoreDismiss; private final Context mContext; private final FalsingCollector mFalsingCollector; @@ -627,18 +628,20 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onUserSwitching(int userId) { Log.d(TAG, String.format("onUserSwitching %d", userId)); synchronized (KeyguardViewMediator.this) { + mIgnoreDismiss = true; notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); resetKeyguardDonePendingLocked(); - dismiss(null /* callback */, null /* message */); + resetStateLocked(); adjustStatusBarLocked(); } } @Override public void onUserSwitchComplete(int userId) { + mIgnoreDismiss = false; Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - // We are calling dismiss again and with a delay as there are race conditions - // in some scenarios caused by async layout listeners + // We are calling dismiss with a delay as there are race conditions in some scenarios + // caused by async layout listeners mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); } @@ -649,11 +652,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onSimStateChanged(int subId, int slotId, int simState) { - - if (DEBUG_SIM_STATES) { - Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId - + ",state=" + simState + ")"); - } + Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId + + ",state=" + TelephonyManager.simStateToString(simState) + ")"); int size = mKeyguardStateCallbacks.size(); boolean simPinSecure = mUpdateMonitor.isSimPinSecure(); @@ -686,7 +686,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, synchronized (KeyguardViewMediator.this) { if (shouldWaitForProvisioning()) { if (!mShowing) { - if (DEBUG_SIM_STATES) Log.d(TAG, "ICC_ABSENT isn't showing," + Log.d(TAG, "ICC_ABSENT isn't showing," + " we need to show the keyguard since the " + "device isn't provisioned yet."); doKeyguardLocked(null); @@ -698,11 +698,21 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // MVNO SIMs can become transiently NOT_READY when switching networks, // so we should only lock when they are ABSENT. if (lastSimStateWasLocked) { - if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the " + Log.d(TAG, "SIM moved to ABSENT when the " + "previous state was locked. Reset the state."); resetStateLocked(); } mSimWasLocked.append(slotId, false); + } else if (simState == TelephonyManager.SIM_STATE_NOT_READY) { + if (simPinBouncerReset()) { + // Support eSIM disablement, and do not clear `mSimWasLocked`. + // NOT_READY could just be a temporary state + if (lastSimStateWasLocked) { + Log.d(TAG, "SIM moved to NOT_READY when the " + + "previous state was locked. Reset the state."); + resetStateLocked(); + } + } } } break; @@ -712,7 +722,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mSimWasLocked.append(slotId, true); mPendingPinLock = true; if (!mShowing) { - if (DEBUG_SIM_STATES) Log.d(TAG, + Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't " + "showing; need to show keyguard so user can enter sim pin"); doKeyguardLocked(null); @@ -724,11 +734,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, case TelephonyManager.SIM_STATE_PERM_DISABLED: synchronized (KeyguardViewMediator.this) { if (!mShowing) { - if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED and " + Log.d(TAG, "PERM_DISABLED and " + "keygaurd isn't showing."); doKeyguardLocked(null); } else { - if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED, resetStateLocked to" + Log.d(TAG, "PERM_DISABLED, resetStateLocked to" + "show permanently disabled message in lockscreen."); resetStateLocked(); } @@ -736,9 +746,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, break; case TelephonyManager.SIM_STATE_READY: synchronized (KeyguardViewMediator.this) { - if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing); + Log.d(TAG, "READY, reset state? " + mShowing); if (mShowing && mSimWasLocked.get(slotId, false)) { - if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the " + Log.d(TAG, "SIM moved to READY when the " + "previously was locked. Reset the state."); mSimWasLocked.append(slotId, false); resetStateLocked(); @@ -746,7 +756,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } break; default: - if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState); + Log.v(TAG, "Unspecific state: " + simState); break; } } @@ -1682,6 +1692,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, }); mJavaAdapter.alwaysCollectFlow(communalViewModel.getTransitionFromOccludedEnded(), getFinishedCallbackConsumer()); + + // System ready can be invoked in the middle of user switching, so check for this state + // and issue the call manually as that important event was missed. + if (mUserTracker.isUserSwitching()) { + mUpdateCallback.onUserSwitching(mUserTracker.getUserId()); + } } // Most services aren't available until the system reaches the ready state, so we // send it here when the device first boots. @@ -2429,6 +2445,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { + if (mIgnoreDismiss) { + android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)"); + return; + } mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt index 406b9f6e6a0d0c18645256acce09441ff19be05e..be873344b719fa5f359e0a178072db9cad9f15a5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt @@ -25,7 +25,9 @@ import android.provider.Settings.Global.ZEN_MODE_OFF import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import android.service.notification.ZenModeConfig +import android.util.Log import com.android.settingslib.notification.modes.EnableZenModeDialog +import com.android.settingslib.notification.modes.ZenMode import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging @@ -35,30 +37,38 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.modes.shared.ModesUi import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn @SysUISingleton class DoNotDisturbQuickAffordanceConfig constructor( private val context: Context, private val controller: ZenModeController, + private val interactor: ZenModeInteractor, private val secureSettings: SecureSettings, private val userTracker: UserTracker, @Background private val backgroundDispatcher: CoroutineDispatcher, + @Background private val backgroundScope: CoroutineScope, private val testConditionId: Uri?, testDialog: EnableZenModeDialog?, ) : KeyguardQuickAffordanceConfig { @@ -67,15 +77,45 @@ constructor( constructor( context: Context, controller: ZenModeController, + interactor: ZenModeInteractor, secureSettings: SecureSettings, userTracker: UserTracker, @Background backgroundDispatcher: CoroutineDispatcher, - ) : this(context, controller, secureSettings, userTracker, backgroundDispatcher, null, null) + @Background backgroundScope: CoroutineScope, + ) : this( + context, + controller, + interactor, + secureSettings, + userTracker, + backgroundDispatcher, + backgroundScope, + null, + null, + ) - private var dndMode: Int = 0 - private var isAvailable = false + private var zenMode: Int = 0 + private var oldIsAvailable = false private var settingsValue: Int = 0 + private val dndMode: StateFlow by lazy { + ModesUi.assertInNewMode() + interactor.dndMode.stateIn( + scope = backgroundScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + } + + private val isAvailable: StateFlow by lazy { + ModesUi.assertInNewMode() + interactor.isZenAvailable.stateIn( + scope = backgroundScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + } + private val conditionUri: Uri get() = testConditionId @@ -104,42 +144,68 @@ constructor( override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb override val lockScreenState: Flow = - combine( - conflatedCallbackFlow { - val callback = - object : ZenModeController.Callback { - override fun onZenChanged(zen: Int) { - dndMode = zen - trySendWithFailureLogging(updateState(), TAG) - } + if (ModesUi.isEnabled) { + combine(isAvailable, dndMode) { isAvailable, dndMode -> + if (!isAvailable) { + KeyguardQuickAffordanceConfig.LockScreenState.Hidden + } else if (dndMode?.isActive == true) { + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_on, + ContentDescription.Resource(R.string.dnd_is_on), + ), + ActivationState.Active, + ) + } else { + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_off, + ContentDescription.Resource(R.string.dnd_is_off), + ), + ActivationState.Inactive, + ) + } + } + } else { + combine( + conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onZenChanged(zen: Int) { + zenMode = zen + trySendWithFailureLogging(updateState(), TAG) + } - override fun onZenAvailableChanged(available: Boolean) { - isAvailable = available - trySendWithFailureLogging(updateState(), TAG) + override fun onZenAvailableChanged(available: Boolean) { + oldIsAvailable = available + trySendWithFailureLogging(updateState(), TAG) + } } - } - dndMode = controller.zen - isAvailable = controller.isZenAvailable - trySendWithFailureLogging(updateState(), TAG) - - controller.addCallback(callback) - - awaitClose { controller.removeCallback(callback) } - }, - secureSettings - .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION) - .onStart { emit(Unit) } - .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) } - .flowOn(backgroundDispatcher) - .distinctUntilChanged() - .onEach { settingsValue = it } - ) { callbackFlowValue, _ -> - callbackFlowValue + zenMode = controller.zen + oldIsAvailable = controller.isZenAvailable + trySendWithFailureLogging(updateState(), TAG) + + controller.addCallback(callback) + + awaitClose { controller.removeCallback(callback) } + }, + secureSettings + .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION) + .onStart { emit(Unit) } + .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) } + .flowOn(backgroundDispatcher) + .distinctUntilChanged() + .onEach { settingsValue = it }, + ) { callbackFlowValue, _ -> + callbackFlowValue + } } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { - return if (controller.isZenAvailable) { + val isZenAvailable = if (ModesUi.isEnabled) isAvailable.value else controller.isZenAvailable + + return if (isZenAvailable) { KeyguardQuickAffordanceConfig.PickerScreenState.Default( configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) ) @@ -151,32 +217,63 @@ constructor( override fun onTriggered( expandable: Expandable? ): KeyguardQuickAffordanceConfig.OnTriggeredResult { - return when { - !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled - dndMode != ZEN_MODE_OFF -> { - controller.setZen(ZEN_MODE_OFF, null, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled - } - settingsValue == ZEN_DURATION_PROMPT -> - KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( - dialog.createDialog(), - expandable - ) - settingsValue == ZEN_DURATION_FOREVER -> { - controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG) + return if (ModesUi.isEnabled) { + if (!isAvailable.value) { KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } else { + val dnd = dndMode.value + if (dnd == null) { + Log.wtf(TAG, "Triggered DND but it's null!?") + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + if (dnd.isActive) { + interactor.deactivateMode(dnd) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } else { + if (interactor.shouldAskForZenDuration(dnd)) { + // NOTE: The dialog handles turning on the mode itself. + return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( + dialog.createDialog(), + expandable, + ) + } else { + interactor.activateMode(dnd) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + } } - else -> { - controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } else { + when { + !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + zenMode != ZEN_MODE_OFF -> { + controller.setZen(ZEN_MODE_OFF, null, TAG) + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + settingsValue == ZEN_DURATION_PROMPT -> + KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( + dialog.createDialog(), + expandable, + ) + + settingsValue == ZEN_DURATION_FOREVER -> { + controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG) + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + else -> { + controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG) + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } } } } private fun updateState(): KeyguardQuickAffordanceConfig.LockScreenState { - return if (!isAvailable) { + ModesUi.assertInLegacyMode() + return if (!oldIsAvailable) { KeyguardQuickAffordanceConfig.LockScreenState.Hidden - } else if (dndMode == ZEN_MODE_OFF) { + } else if (zenMode == ZEN_MODE_OFF) { KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_dnd_icon_off, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 80a0cee4f3191fa103185655f2f0977bc57b4fe6..b0820a747e172ea9f3cfe6cd2af6ee48be647399 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -24,7 +24,6 @@ import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -95,10 +94,7 @@ constructor( scope.launch { powerInteractor.isAwake .filterRelevantKeyguardStateAnd { isAwake -> isAwake } - .sample( - keyguardInteractor.biometricUnlockState, - ::Pair, - ) + .sample(keyguardInteractor.biometricUnlockState, ::Pair) .collect { ( _, @@ -203,21 +199,21 @@ constructor( if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.GONE, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (primaryBouncerShowing) { if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.PRIMARY_BOUNCER, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (!SceneContainerFlag.isEnabled) { startTransitionTo( KeyguardState.GLANCEABLE_HUB, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) { @@ -227,7 +223,7 @@ constructor( } else { startTransitionTo( KeyguardState.LOCKSCREEN, - ownerReason = "waking from dozing" + ownerReason = "waking from dozing", ) } } @@ -237,11 +233,9 @@ constructor( private suspend fun transitionToGlanceableHub() { if (communalSceneKtfRefactor()) { - communalSceneInteractor.changeScene( + communalSceneInteractor.snapToScene( newScene = CommunalScenes.Communal, loggingReason = "from dozing to hub", - // Immediately show the hub when transitioning from dozing to hub. - transitionKey = CommunalTransitionKeys.Immediately, ) } else { startTransitionTo(KeyguardState.GLANCEABLE_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index 199caa168e3145edcedd715f75413fe19a7c20e3..7759298cb32a8ff98628342999b10fde341bebfa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -162,10 +162,9 @@ constructor( .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep } .collect { if (communalSceneKtfRefactor()) { - communalSceneInteractor.changeScene( + communalSceneInteractor.snapToScene( newScene = CommunalScenes.Blank, loggingReason = "hub to dozing", - transitionKey = CommunalTransitionKeys.Immediately, keyguardState = KeyguardState.DOZING, ) } else { @@ -210,12 +209,12 @@ constructor( // ends, to avoid transitioning to OCCLUDED erroneously when exiting // the dream. .debounce(100.milliseconds), - ::Pair + ::Pair, ) .sampleFilter( // When launching activities from widgets on the hub, we have a // custom occlusion animation. - communalSceneInteractor.isLaunchingWidget, + communalSceneInteractor.isLaunchingWidget ) { launchingWidget -> !launchingWidget } @@ -253,7 +252,7 @@ constructor( noneOf( // When launching activities from widgets on the hub, we wait to change // scenes until the activity launch is complete. - communalSceneInteractor.isLaunchingWidget, + communalSceneInteractor.isLaunchingWidget ), ) .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } @@ -270,7 +269,7 @@ constructor( newScene = CommunalScenes.Blank, loggingReason = "hub to gone", transitionKey = CommunalTransitionKeys.SimpleFade, - keyguardState = KeyguardState.GONE + keyguardState = KeyguardState.GONE, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 52323a51b0117b2d4f94ddc1505362c893cba454..30babe6d47c7be18bfca69c5c2dff39b0e34016c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator +import android.util.Log import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -76,7 +77,7 @@ constructor( override fun start() { listenForPrimaryBouncerToGone() listenForPrimaryBouncerToAsleep() - listenForPrimaryBouncerToLockscreenHubOrOccluded() + listenForPrimaryBouncerNotShowing() listenForPrimaryBouncerToDreamingLockscreenHosted() listenForTransitionToCamera(scope, keyguardInteractor) } @@ -86,7 +87,7 @@ constructor( .transition( edge = Edge.INVALID, edgeWithoutSceneContainer = - Edge.create(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE) + Edge.create(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE), ) .map { it.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD } .onStart { @@ -102,7 +103,7 @@ constructor( } } - private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() { + private fun listenForPrimaryBouncerNotShowing() { if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { scope.launch { @@ -110,7 +111,7 @@ constructor( .sample( powerInteractor.isAwake, keyguardInteractor.isActiveDreamLockscreenHosted, - communalSceneInteractor.isIdleOnCommunal + communalSceneInteractor.isIdleOnCommunal, ) .filterRelevantKeyguardStateAnd { (isBouncerShowing, _, _, _) -> // TODO(b/307976454) - See if we need to listen for SHOW_WHEN_LOCKED @@ -138,27 +139,34 @@ constructor( } else { scope.launch { keyguardInteractor.primaryBouncerShowing + .filterRelevantKeyguardStateAnd { isBouncerShowing -> !isBouncerShowing } .sample( powerInteractor.isAwake, - keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, - keyguardInteractor.isActiveDreamLockscreenHosted, communalSceneInteractor.isIdleOnCommunal, ) - .filterRelevantKeyguardStateAnd { - (isBouncerShowing, isAwake, _, _, isActiveDreamLockscreenHosted, _) -> - !isBouncerShowing && isAwake && !isActiveDreamLockscreenHosted - } - .collect { (_, _, occluded, isDreaming, _, isIdleOnCommunal) -> + .collect { (_, isAwake, isDreaming, isIdleOnCommunal) -> + val isOccluded = keyguardInteractor.isKeyguardOccluded.value val toState = - if (occluded && !isDreaming) { - KeyguardState.OCCLUDED - } else if (isIdleOnCommunal) { - KeyguardState.GLANCEABLE_HUB - } else if (isDreaming) { - KeyguardState.DREAMING + if (isAwake) { + if (isOccluded && !isDreaming) { + KeyguardState.OCCLUDED + } else if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else if (isDreaming) { + KeyguardState.DREAMING + } else { + KeyguardState.LOCKSCREEN + } } else { - KeyguardState.LOCKSCREEN + // This shouldn't necessarily happen, but there's a bug in the + // bouncer logic which is incorrectly showing/hiding rapidly + Log.i( + TAG, + "Going back to sleeping state to correct an attempt to " + + "show bouncer", + ) + keyguardInteractor.asleepKeyguardState.value } startTransitionTo(toState) } @@ -255,6 +263,7 @@ constructor( } companion object { + private const val TAG = "FromPrimaryBouncerTransitionInteractor" private val DEFAULT_DURATION = 300.milliseconds val TO_AOD_DURATION = DEFAULT_DURATION val TO_DOZING_DURATION = DEFAULT_DURATION diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 7afc7596a994c08d74ef4330634f39fe15db6d40..6932eb51e14176ac3c1760edbcd71e1639772eba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -25,13 +25,13 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -64,7 +64,7 @@ constructor( /** Current BlueprintId */ val blueprintId = shadeInteractor.isShadeLayoutWide.map { isShadeLayoutWide -> - val useSplitShade = isShadeLayoutWide && !ComposeLockscreen.isEnabled + val useSplitShade = isShadeLayoutWide && !SceneContainerFlag.isEnabled when { useSplitShade -> SplitShadeKeyguardBlueprint.ID else -> DefaultKeyguardBlueprint.DEFAULT diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt index eb9b07add12d30854bc640a966a9ccb974170ca7..ea80911335fa1a009373f7ff3603804b91298434 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt @@ -65,6 +65,7 @@ constructor( powerInteractor: PowerInteractor, alternateBouncerInteractor: AlternateBouncerInteractor, shadeInteractor: Lazy, + keyguardInteractor: Lazy, ) { val dismissAction: Flow = repository.dismissAction @@ -111,9 +112,9 @@ constructor( } else if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) { combine( shadeInteractor.get().isAnyExpanded, - deviceUnlockedInteractor.get().deviceUnlockStatus, - ) { isAnyExpanded, deviceUnlockStatus -> - isAnyExpanded && deviceUnlockStatus.isUnlocked + keyguardInteractor.get().isKeyguardDismissible, + ) { isAnyExpanded, keyguardDismissible -> + isAnyExpanded && keyguardDismissible } } else { flow { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt index d7e6bdb8f02c0598a5b963d1505a0f4787235599..4457f1dfaf099e923a9380982b45a9498db2884b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt @@ -23,10 +23,12 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.keyguard.shared.model.DismissAction import com.android.systemui.keyguard.shared.model.KeyguardDone +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.toQuad @@ -35,6 +37,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -56,6 +59,7 @@ constructor( trustRepository: TrustRepository, alternateBouncerInteractor: AlternateBouncerInteractor, powerInteractor: PowerInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /* * Updates when a biometric has authenticated the device and is requesting to dismiss @@ -76,9 +80,9 @@ constructor( primaryBouncerInteractor.isShowing, alternateBouncerInteractor.isVisible, powerInteractor.isInteractive, - ::Triple + ::Triple, ), - ::toQuad + ::toQuad, ) .filter { (trustModel, primaryBouncerShowing, altBouncerShowing, interactive) -> val bouncerShowing = primaryBouncerShowing || altBouncerShowing @@ -144,9 +148,7 @@ constructor( * * TODO(b/358412565): Support dismiss messages. */ - fun dismissKeyguardWithCallback( - callback: IKeyguardDismissCallback?, - ) { + fun dismissKeyguardWithCallback(callback: IKeyguardDismissCallback?) { scope.launch { withContext(mainDispatcher) { if (callback != null) { @@ -163,4 +165,14 @@ constructor( } } } + + init { + if (KeyguardWmStateRefactor.isEnabled) { + scope.launch { + keyguardTransitionInteractor.currentKeyguardState + .filter { it == KeyguardState.GONE } + .collect { dismissCallbackRegistry.notifyDismissSucceeded() } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index ed82159e61605ff1bc15e0e2ff35c4135a690884..b5f6b418e32240b279f4b0ff38e94fca10b8034b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -59,7 +59,6 @@ import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters @@ -72,6 +71,7 @@ import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.VibratorHelper @@ -241,7 +241,7 @@ object KeyguardRootViewBinder { disposables += view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - if (ComposeLockscreen.isEnabled) { + if (SceneContainerFlag.isEnabled) { view.setViewTreeOnBackPressedDispatcherOwner( object : OnBackPressedDispatcherOwner { override val onBackPressedDispatcher = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 0b8f7417a49d81213fe655801aff35270233ed22..cef9a4eaf2bd0b6ce80850fb0a647654b7ecdef8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.preview +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.app.WallpaperColors import android.content.BroadcastReceiver import android.content.Context @@ -187,7 +188,7 @@ constructor( private var themeStyle: Style? = null init { - coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job()) + coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job() + createCoroutineTracingContext("KeyguardPreviewRenderer")) disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index 73028c5cf49642a939885c767789178fcdf5bd01..36f684ee4759ef568f172b58a995005a7946bf00 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -25,10 +25,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.ui.SystemBarUtilsProxy @@ -56,10 +56,9 @@ constructor( var burnInLayer: Layer? = null val clockSize: StateFlow = - combine( - keyguardClockInteractor.selectedClockSize, - keyguardClockInteractor.clockSize, - ) { selectedSize, clockSize -> + combine(keyguardClockInteractor.selectedClockSize, keyguardClockInteractor.clockSize) { + selectedSize, + clockSize -> if (selectedSize == ClockSizeSetting.SMALL) ClockSize.SMALL else clockSize } .stateIn( @@ -80,10 +79,7 @@ constructor( val currentClock = keyguardClockInteractor.currentClock val hasCustomWeatherDataDisplay = - combine( - isLargeClockVisible, - currentClock, - ) { isLargeClock, currentClock -> + combine(isLargeClockVisible, currentClock) { isLargeClock, currentClock -> currentClock?.let { clock -> val face = if (isLargeClock) clock.largeClock else clock.smallClock face.config.hasCustomWeatherDataDisplay @@ -93,14 +89,14 @@ constructor( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = - currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false + currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false, ) val clockShouldBeCentered: StateFlow = keyguardClockInteractor.clockShouldBeCentered.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false + initialValue = false, ) // To translate elements below smartspace in weather clock to avoid overlapping between date @@ -111,7 +107,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false + initialValue = false, ) val currentClockLayout: StateFlow = @@ -145,7 +141,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = ClockLayout.SMALL_CLOCK + initialValue = ClockLayout.SMALL_CLOCK, ) val hasCustomPositionUpdatedAnimation: StateFlow = @@ -156,7 +152,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false + initialValue = false, ) /** Calculates the top margin for the small clock. */ @@ -164,10 +160,10 @@ constructor( val statusBarHeight = systemBarUtils.getStatusBarHeaderHeightKeyguard() return if (shadeInteractor.isShadeLayoutWide.value) { resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) - - if (ComposeLockscreen.isEnabled) statusBarHeight else 0 + if (SceneContainerFlag.isEnabled) statusBarHeight else 0 } else { resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + - if (!ComposeLockscreen.isEnabled) statusBarHeight else 0 + if (!SceneContainerFlag.isEnabled) statusBarHeight else 0 } } diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt index c2b5d98699b4e8e4f1e8d1edf7c8aec659654e14..555969859a1f2abd54d845cadc84462c880b339d 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt @@ -26,7 +26,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.lifecycleScope import com.android.app.tracing.coroutines.createCoroutineTracingContext -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.traceCoroutine import com.android.systemui.Flags.coroutineTracing import com.android.systemui.util.Assert import com.android.systemui.util.Compile @@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch /** * Runs the given [block] every time the [View] becomes attached (or immediately after calling this @@ -137,7 +138,7 @@ private fun createLifecycleOwnerAndRun( ): ViewLifecycleOwner { return ViewLifecycleOwner(view).apply { onCreate() - lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) } + lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } } } } @@ -367,7 +368,8 @@ private val ViewTreeObserver.isWindowVisible * an extension function, and plumbing dagger-injected instances for static usage has little * benefit. */ -private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + createCoroutineTracingContext() +private val MAIN_DISPATCHER_SINGLETON = + Dispatchers.Main + createCoroutineTracingContext("RepeatWhenAttached") private const val DEFAULT_TRACE_NAME = "repeatWhenAttached" private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt" private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt index 53cc15b8c588e100391bae7ab32ca6dd053bd3d0..7b55dac8eee1974359799611e277ca37600f8ece 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt @@ -42,7 +42,6 @@ import android.service.notification.StatusBarNotification import android.support.v4.media.MediaMetadataCompat import android.text.TextUtils import android.util.Log -import android.util.Pair import androidx.media.utils.MediaConstants import com.android.app.tracing.coroutines.traceCoroutine import com.android.systemui.dagger.SysUISingleton @@ -70,6 +69,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive /** Loads media information from media style [StatusBarNotification] classes. */ @@ -85,7 +85,7 @@ constructor( private val imageLoader: ImageLoader, private val statusBarManager: StatusBarManager, ) { - private val mediaProcessingJobs = ConcurrentHashMap() + private val mediaProcessingJobs = ConcurrentHashMap() private val artworkWidth: Int = context.resources.getDimensionPixelSize( @@ -97,7 +97,7 @@ constructor( private val themeText = com.android.settingslib.Utils.getColorAttr( context, - com.android.internal.R.attr.textColorPrimary + com.android.internal.R.attr.textColorPrimary, ) .defaultColor @@ -112,11 +112,14 @@ constructor( * load will be cancelled. */ suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? { - logD(TAG) { "Loading media data for $key..." } - val jobKey = JobKey(key, sbn) val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) } - loadMediaJob.invokeOnCompletion { mediaProcessingJobs.remove(jobKey) } - val existingJob = mediaProcessingJobs.put(jobKey, loadMediaJob) + loadMediaJob.invokeOnCompletion { + // We need to make sure we're removing THIS job after cancellation, not + // a job that we created later. + mediaProcessingJobs.remove(key, loadMediaJob) + } + val existingJob = mediaProcessingJobs.put(key, loadMediaJob) + logD(TAG) { "Loading media data for $key... / existing job: $existingJob" } existingJob?.cancel("New processing job incoming.") return loadMediaJob.await() } @@ -128,10 +131,15 @@ constructor( sbn: StatusBarNotification, ): MediaDataLoaderResult? = traceCoroutine("MediaDataLoader#loadMediaData") { + // We have apps spamming us with quick notification updates which can cause + // us to spend significant CPU time loading duplicate data. This debounces + // those requests at the cost of a bit of latency. + delay(DEBOUNCE_DELAY_MS) + val token = sbn.notification.extras.getParcelable( Notification.EXTRA_MEDIA_SESSION, - MediaSession.Token::class.java + MediaSession.Token::class.java, ) if (token == null) { Log.i(TAG, "Token was null, not loading media info") @@ -144,7 +152,7 @@ constructor( val appInfo = notification.extras.getParcelable( Notification.EXTRA_BUILDER_APPLICATION_INFO, - ApplicationInfo::class.java + ApplicationInfo::class.java, ) ?: getAppInfoFromPackage(sbn.packageName) // App name @@ -240,7 +248,7 @@ constructor( playbackLocation = playbackLocation, isPlaying = isPlaying, appUid = appUid, - isExplicit = isExplicit + isExplicit = isExplicit, ) } @@ -258,7 +266,7 @@ constructor( token: MediaSession.Token, appName: String, appIntent: PendingIntent, - packageName: String + packageName: String, ): MediaDataLoaderResult? { val mediaData = backgroundScope.async { @@ -270,7 +278,7 @@ constructor( token, appName, appIntent, - packageName + packageName, ) } return mediaData.await() @@ -286,7 +294,7 @@ constructor( token: MediaSession.Token, appName: String, appIntent: PendingIntent, - packageName: String + packageName: String, ): MediaDataLoaderResult? = traceCoroutine("MediaDataLoader#loadMediaDataForResumption") { if (desc.title.isNullOrBlank()) { @@ -338,14 +346,14 @@ constructor( appUid = appUid, isExplicit = isExplicit, resumeAction = resumeAction, - resumeProgress = progress + resumeProgress = progress, ) } private fun createActionsFromState( packageName: String, controller: MediaController, - user: UserHandle + user: UserHandle, ): MediaButton? { if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) { return null @@ -368,7 +376,7 @@ constructor( */ private fun getDeviceInfoForRemoteCast( key: String, - sbn: StatusBarNotification + sbn: StatusBarNotification, ): MediaDeviceData? { val extras = sbn.notification.extras val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null) @@ -388,7 +396,7 @@ constructor( deviceDrawable, deviceName, deviceIntent, - showBroadcastButton = false + showBroadcastButton = false, ) } return null @@ -439,7 +447,7 @@ constructor( listOf( ContentResolver.SCHEME_CONTENT, ContentResolver.SCHEME_ANDROID_RESOURCE, - ContentResolver.SCHEME_FILE + ContentResolver.SCHEME_FILE, ) ) { Log.w(TAG, "Invalid album art uri $uri") @@ -451,7 +459,7 @@ constructor( source, artworkWidth, artworkHeight, - allocator = ImageDecoder.ALLOCATOR_SOFTWARE + allocator = ImageDecoder.ALLOCATOR_SOFTWARE, ) } @@ -459,7 +467,7 @@ constructor( uri: Uri, userId: Int, appUid: Int, - packageName: String + packageName: String, ): Bitmap? { try { val ugm = UriGrantsManager.getService() @@ -468,7 +476,7 @@ constructor( packageName, ContentProvider.getUriWithoutUserId(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, - ContentProvider.getUserIdFromUri(uri, userId) + ContentProvider.getUserIdFromUri(uri, userId), ) return loadBitmapFromUri(uri) } catch (e: SecurityException) { @@ -488,21 +496,20 @@ constructor( .loadDrawable(context), action, context.getString(R.string.controls_media_resume), - context.getDrawable(R.drawable.ic_media_play_container) + context.getDrawable(R.drawable.ic_media_play_container), ) } - private data class JobKey(val key: String, val sbn: StatusBarNotification) : - Pair(key, sbn) - companion object { private const val TAG = "MediaDataLoader" private val ART_URIS = arrayOf( MediaMetadata.METADATA_KEY_ALBUM_ART_URI, MediaMetadata.METADATA_KEY_ART_URI, - MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI + MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, ) + + private const val DEBOUNCE_DELAY_MS = 200L } /** Returned data from loader. */ @@ -523,6 +530,6 @@ constructor( val appUid: Int, val isExplicit: Boolean, val resumeAction: Runnable? = null, - val resumeProgress: Double? = null + val resumeProgress: Double? = null, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index 130868dc3c1c3bfc9a21030c8df19426ad3072b8..1f339dddd4d1d85a0b679b8a9112ea25fca4c9c6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -33,6 +33,7 @@ import com.android.systemui.bluetooth.BroadcastDialogController import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor import com.android.systemui.media.controls.domain.pipeline.getNotificationActions +import com.android.systemui.media.controls.shared.MediaLogger import com.android.systemui.media.controls.shared.model.MediaControlModel import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.util.MediaSmartspaceLogger @@ -59,6 +60,7 @@ constructor( private val lockscreenUserManager: NotificationLockscreenUserManager, private val mediaOutputDialogManager: MediaOutputDialogManager, private val broadcastDialogController: BroadcastDialogController, + private val mediaLogger: MediaLogger, ) { val mediaControl: Flow = @@ -73,7 +75,7 @@ constructor( instanceId: InstanceId, delayMs: Long, eventId: Int, - location: Int + location: Int, ): Boolean { logSmartspaceUserEvent(eventId, location) val dismissed = @@ -81,7 +83,7 @@ constructor( if (!dismissed) { Log.w( TAG, - "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}" + "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}", ) } return dismissed @@ -120,20 +122,20 @@ constructor( expandable: Expandable, clickIntent: PendingIntent, eventId: Int, - location: Int + location: Int, ) { logSmartspaceUserEvent(eventId, location) - if (!launchOverLockscreen(clickIntent)) { + if (!launchOverLockscreen(expandable, clickIntent)) { activityStarter.postStartActivityDismissingKeyguard( clickIntent, - expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) + expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER), ) } } fun startDeviceIntent(deviceIntent: PendingIntent) { if (deviceIntent.isActivity) { - if (!launchOverLockscreen(deviceIntent)) { + if (!launchOverLockscreen(expandable = null, deviceIntent)) { activityStarter.postStartActivityDismissingKeyguard(deviceIntent) } } else { @@ -141,20 +143,33 @@ constructor( } } - private fun launchOverLockscreen(pendingIntent: PendingIntent): Boolean { + private fun launchOverLockscreen( + expandable: Expandable?, + pendingIntent: PendingIntent, + ): Boolean { val showOverLockscreen = keyguardStateController.isShowing && activityIntentHelper.wouldPendingShowOverLockscreen( pendingIntent, - lockscreenUserManager.currentUserId + lockscreenUserManager.currentUserId, ) if (showOverLockscreen) { try { - val options = BroadcastOptions.makeBasic() - options.isInteractive = true - options.pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED - pendingIntent.send(options.toBundle()) + if (expandable != null) { + activityStarter.startPendingIntentMaybeDismissingKeyguard( + pendingIntent, + /* intentSentUiThreadCallback = */ null, + expandable.activityTransitionController( + Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER + ), + ) + } else { + val options = BroadcastOptions.makeBasic() + options.isInteractive = true + options.pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + pendingIntent.send(options.toBundle()) + } } catch (e: PendingIntent.CanceledException) { Log.e(TAG, "pending intent of $instanceId was canceled") } @@ -166,7 +181,7 @@ constructor( fun startMediaOutputDialog( expandable: Expandable, packageName: String, - token: MediaSession.Token? = null + token: MediaSession.Token? = null, ) { mediaOutputDialogManager.createAndShowWithController( packageName, @@ -180,7 +195,7 @@ constructor( broadcastDialogController.createBroadcastDialogWithController( broadcastApp, packageName, - expandable.dialogTransitionController() + expandable.dialogTransitionController(), ) } @@ -188,10 +203,14 @@ constructor( repository.logSmartspaceCardUserEvent( eventId, MediaSmartspaceLogger.getSurface(location), - instanceId = instanceId + instanceId = instanceId, ) } + fun logMediaControlIsBound(artistName: CharSequence, songName: CharSequence) { + mediaLogger.logMediaControlIsBound(instanceId, artistName, songName) + } + private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? { return dialogTransitionController( cuj = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt index 9ee59d1875a615cfc11a6d88baa59b6ffc9f7b94..ad84a5eb74ab0fb3f435feb359edce5281aedf20 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt @@ -16,6 +16,7 @@ package com.android.systemui.media.controls.domain.resume +import android.annotation.WorkerThread import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context @@ -41,6 +42,7 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils +import com.android.systemui.util.kotlin.logD import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.util.concurrent.ConcurrentLinkedQueue @@ -86,11 +88,12 @@ constructor( @VisibleForTesting val userUnlockReceiver = object : BroadcastReceiver() { + @WorkerThread override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_USER_UNLOCKED == intent.action) { val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) if (userId == currentUserId) { - backgroundExecutor.execute { loadMediaResumptionControls() } + loadMediaResumptionControls() } } } @@ -109,7 +112,7 @@ constructor( override fun addTrack( desc: MediaDescription, component: ComponentName, - browser: ResumeMediaBrowser + browser: ResumeMediaBrowser, ) { val token = browser.token val appIntent = browser.appIntent @@ -123,7 +126,7 @@ constructor( Log.e(TAG, "Error getting package information", e) } - Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc") + logD(TAG) { "Adding resume controls for ${browser.userId}: $desc" } mediaDataManager.addResumptionControls( browser.userId, desc, @@ -131,7 +134,7 @@ constructor( token, appName.toString(), appIntent, - component.packageName + component.packageName, ) } } @@ -144,8 +147,8 @@ constructor( broadcastDispatcher.registerReceiver( userUnlockReceiver, unlockFilter, - null, - UserHandle.ALL + backgroundExecutor, + UserHandle.ALL, ) userTracker.addCallback(userTrackerCallback, mainExecutor) loadSavedComponents() @@ -163,7 +166,7 @@ constructor( mediaDataManager.setMediaResumptionEnabled(useMediaResumption) } }, - Settings.Secure.MEDIA_CONTROLS_RESUME + Settings.Secure.MEDIA_CONTROLS_RESUME, ) } @@ -197,11 +200,11 @@ constructor( } resumeComponents.add(component to lastPlayed) } - Log.d( - TAG, + + logD(TAG) { "loaded resume components for $currentUserId: " + - "${resumeComponents.toArray().contentToString()}" - ) + resumeComponents.toArray().contentToString() + } if (needsUpdate) { // Save any missing times that we had to fill in @@ -228,7 +231,7 @@ constructor( mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId) browser.findRecentMedia() } else { - Log.d(TAG, "User $currentUserId does not have component ${it.first}") + logD(TAG) { "User $currentUserId does not have component ${it.first}" } } } } @@ -240,7 +243,7 @@ constructor( data: MediaData, immediately: Boolean, receivedSmartspaceCardLatency: Int, - isSsReactivated: Boolean + isSsReactivated: Boolean, ) { if (useMediaResumption) { // If this had been started from a resume state, disconnect now that it's live @@ -281,7 +284,7 @@ constructor( mediaBrowserFactory.create( object : ResumeMediaBrowser.Callback() { override fun onConnected() { - Log.d(TAG, "Connected to $componentName") + logD(TAG) { "Connected to $componentName" } } override fun onError() { @@ -292,20 +295,20 @@ constructor( override fun addTrack( desc: MediaDescription, component: ComponentName, - browser: ResumeMediaBrowser + browser: ResumeMediaBrowser, ) { // Since this is a test, just save the component for later - Log.d( - TAG, + logD(TAG) { "Can get resumable media for ${browser.userId} from $componentName" - ) + } + mediaDataManager.setResumeAction(key, getResumeAction(componentName)) updateResumptionList(componentName) mediaBrowser = null } }, componentName, - currentUserId + currentUserId, ) mediaBrowser?.testConnection() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt index 7d20e170d8bc98125c8317b2ffac01b9f052219e..88c47ba4d24325b28acfe707af7c5ad595eb4bba 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt @@ -36,7 +36,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { bool1 = active str2 = reason }, - { "add media $str1, active: $bool1, reason: $str2" } + { "add media $str1, active: $bool1, reason: $str2" }, ) } @@ -48,7 +48,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { str1 = instanceId.toString() str2 = reason }, - { "removing media $str1, reason: $str2" } + { "removing media $str1, reason: $str2" }, ) } @@ -61,7 +61,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { bool1 = isActive str2 = reason }, - { "add recommendation $str1, active $bool1, reason: $str2" } + { "add recommendation $str1, active $bool1, reason: $str2" }, ) } @@ -74,7 +74,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { bool1 = immediately str2 = reason }, - { "removing recommendation $str1, immediate=$bool1, reason: $str2" } + { "removing recommendation $str1, immediate=$bool1, reason: $str2" }, ) } @@ -83,7 +83,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = instanceId.toString() }, - { "adding media card $str1 to carousel" } + { "adding media card $str1 to carousel" }, ) } @@ -92,7 +92,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = instanceId.toString() }, - { "removing media card $str1 from carousel" } + { "removing media card $str1 from carousel" }, ) } @@ -101,7 +101,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = key }, - { "adding recommendation card $str1 to carousel" } + { "adding recommendation card $str1 to carousel" }, ) } @@ -110,7 +110,7 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = key }, - { "removing recommendation card $str1 from carousel" } + { "removing recommendation card $str1 from carousel" }, ) } @@ -119,7 +119,24 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { TAG, LogLevel.DEBUG, { str1 = key }, - { "duplicate media notification $str1 posted" } + { "duplicate media notification $str1 posted" }, + ) + } + + fun logMediaControlIsBound( + instanceId: InstanceId, + artistName: CharSequence, + title: CharSequence, + ) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = instanceId.toString() + str2 = artistName.toString() + str3 = title.toString() + }, + { "binding media control, instance id= $str1, artist= $str2, title= $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index 6373feda9c9b3cc1a6c32049edccdbc7c1160efe..7a6de5c07b4391e7a1a5a4983ca28748f045b81e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -55,6 +55,7 @@ import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewM import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.monet.ColorScheme +import com.android.systemui.monet.Style import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.surfaceeffects.ripple.MultiRippleView @@ -66,6 +67,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +private const val TAG = "MediaControlViewBinder" + object MediaControlViewBinder { fun bind( @@ -80,16 +83,19 @@ object MediaControlViewBinder { mediaCard.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.player.collectLatest { playerViewModel -> - playerViewModel?.let { - bindMediaCard( - viewHolder, - viewController, - it, - falsingManager, - backgroundDispatcher, - mainDispatcher, - ) + viewModel.player.collectLatest { player -> + player?.let { + if (viewModel.isNewPlayer(it)) { + bindMediaCard( + viewHolder, + viewController, + it, + falsingManager, + backgroundDispatcher, + mainDispatcher, + ) + viewModel.onMediaControlIsBound(it.artistName, it.titleName) + } } } } @@ -143,7 +149,7 @@ object MediaControlViewBinder { viewHolder, viewModel.outputSwitcher, viewController, - falsingManager + falsingManager, ) bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager) bindActionButtons(viewHolder, viewModel, viewController, falsingManager) @@ -157,7 +163,7 @@ object MediaControlViewBinder { viewController, backgroundDispatcher, mainDispatcher, - isSongUpdated + isSongUpdated, ) if (viewModel.playTurbulenceNoise) { @@ -231,9 +237,6 @@ object MediaControlViewBinder { } } setDismissible(model.isDismissEnabled) - setTextPrimaryColor(model.textPrimaryColor) - setAccentPrimaryColor(model.accentPrimaryColor) - setSurfaceColor(model.surfaceColor) } } @@ -259,12 +262,12 @@ object MediaControlViewBinder { if (buttonView.id == R.id.actionPrev) { viewController.setUpPrevButtonInfo( buttonModel.isEnabled, - buttonModel.notVisibleValue + buttonModel.notVisibleValue, ) } else if (buttonView.id == R.id.actionNext) { viewController.setUpNextButtonInfo( buttonModel.isEnabled, - buttonModel.notVisibleValue + buttonModel.notVisibleValue, ) } val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler @@ -295,7 +298,7 @@ object MediaControlViewBinder { viewController.collapsedLayout, visible, buttonModel.notVisibleValue, - buttonModel.showInCollapsed + buttonModel.showInCollapsed, ) } } @@ -350,7 +353,7 @@ object MediaControlViewBinder { createTouchRippleAnimation( button, viewController.colorSchemeTransition, - multiRippleView + multiRippleView, ) ) @@ -382,12 +385,12 @@ object MediaControlViewBinder { setVisibleAndAlpha( expandedSet, R.id.media_explicit_indicator, - viewModel.isExplicitVisible + viewModel.isExplicitVisible, ) setVisibleAndAlpha( collapsedSet, R.id.media_explicit_indicator, - viewModel.isExplicitVisible + viewModel.isExplicitVisible, ) // refreshState is required here to resize the text views (and prevent ellipsis) @@ -398,7 +401,7 @@ object MediaControlViewBinder { // something is incorrectly bound, but needs to be run if other elements were // updated while the enter animation was running viewController.refreshState() - } + }, ) } @@ -420,22 +423,37 @@ object MediaControlViewBinder { val width = viewController.widthInSceneContainerPx val height = viewController.heightInSceneContainerPx withContext(backgroundDispatcher) { + val wallpaperColors = + MediaArtworkHelper.getWallpaperColor( + viewHolder.albumView.context, + backgroundDispatcher, + viewModel.backgroundCover, + TAG, + ) + val isArtworkBound = wallpaperColors != null + val scheme = + wallpaperColors?.let { ColorScheme(it, true, Style.CONTENT) } + ?: let { + if (viewModel.launcherIcon is Icon.Loaded) { + MediaArtworkHelper.getColorScheme(viewModel.launcherIcon.drawable, TAG) + } else { + null + } + } val artwork = - if (viewModel.shouldAddGradient) { + wallpaperColors?.let { addGradientToPlayerAlbum( viewHolder.albumView.context, viewModel.backgroundCover!!, - viewModel.colorScheme, + scheme!!, width, - height + height, ) - } else { - ColorDrawable(Color.TRANSPARENT) - } + } ?: ColorDrawable(Color.TRANSPARENT) withContext(mainDispatcher) { // Transition Colors to current color scheme val colorSchemeChanged = - viewController.colorSchemeTransition.updateColorScheme(viewModel.colorScheme) + viewController.colorSchemeTransition.updateColorScheme(scheme) val albumView = viewHolder.albumView // Set up width of album view constraint. @@ -446,7 +464,7 @@ object MediaControlViewBinder { if ( updateBackground || colorSchemeChanged || - (!viewController.isArtworkBound && viewModel.shouldAddGradient) + (!viewController.isArtworkBound && isArtworkBound) ) { viewController.prevArtwork?.let { // Since we throw away the last transition, this will pop if your @@ -461,12 +479,10 @@ object MediaControlViewBinder { transitionDrawable.isCrossFadeEnabled = true albumView.setImageDrawable(transitionDrawable) - transitionDrawable.startTransition( - if (viewModel.shouldAddGradient) 333 else 80 - ) + transitionDrawable.startTransition(if (isArtworkBound) 333 else 80) } ?: albumView.setImageDrawable(artwork) } - viewController.isArtworkBound = viewModel.shouldAddGradient + viewController.isArtworkBound = isArtworkBound viewController.prevArtwork = artwork if (viewModel.useGrayColorFilter) { @@ -493,7 +509,7 @@ object MediaControlViewBinder { transitionDrawable: TransitionDrawable, layer: Int, targetWidth: Int, - targetHeight: Int + targetHeight: Int, ) { val drawable = transitionDrawable.getDrawable(layer) ?: return val width = drawable.intrinsicWidth @@ -509,7 +525,7 @@ object MediaControlViewBinder { artworkIcon: android.graphics.drawable.Icon, mutableColorScheme: ColorScheme, width: Int, - height: Int + height: Int, ): LayerDrawable { val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) return MediaArtworkHelper.setUpGradientColorOnDrawable( @@ -517,7 +533,7 @@ object MediaControlViewBinder { context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable, mutableColorScheme, MEDIA_PLAYER_SCRIM_START_ALPHA, - MEDIA_PLAYER_SCRIM_END_ALPHA + MEDIA_PLAYER_SCRIM_END_ALPHA, ) } @@ -544,7 +560,7 @@ object MediaControlViewBinder { private fun createTouchRippleAnimation( button: ImageButton, colorSchemeTransition: ColorSchemeTransition, - multiRippleView: MultiRippleView + multiRippleView: MultiRippleView, ): RippleAnimation { val maxSize = (multiRippleView.width * 2).toFloat() return RippleAnimation( @@ -562,7 +578,7 @@ object MediaControlViewBinder { baseRingFadeParams = null, sparkleRingFadeParams = null, centerFillFadeParams = null, - shouldDistort = false + shouldDistort = false, ) ) } @@ -596,7 +612,7 @@ object MediaControlViewBinder { set: ConstraintSet, resId: Int, visible: Boolean, - notVisibleValue: Int + notVisibleValue: Int, ) { set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue) set.setAlpha(resId, if (visible) 1.0f else 0.0f) @@ -618,7 +634,7 @@ object MediaControlViewBinder { collapsedSet: ConstraintSet, visible: Boolean, notVisibleValue: Int, - showInCollapsed: Boolean + showInCollapsed: Boolean, ) { if (notVisibleValue == ConstraintSet.INVISIBLE) { // Since time views should appear instead of buttons. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt index 5e8a879adbb4524c3221530dad893001d5f4f67b..8a04799d3f9495c237f4bffd224a25e1811f09c7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt @@ -16,13 +16,24 @@ package com.android.systemui.media.controls.ui.binder +import android.app.WallpaperColors import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Color import android.graphics.Matrix +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon +import android.graphics.drawable.LayerDrawable +import android.os.Trace import android.util.TypedValue import android.view.View import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -30,17 +41,29 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.animation.Expandable import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS +import com.android.systemui.media.controls.ui.animation.surfaceFromScheme +import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme +import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme import com.android.systemui.media.controls.ui.controller.MediaViewController +import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel +import com.android.systemui.monet.ColorScheme +import com.android.systemui.monet.Style import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.util.animation.TransitionLayout import kotlin.math.min +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +private const val TAG = "MediaRecommendationsViewBinder" +private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f +private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f object MediaRecommendationsViewBinder { @@ -50,6 +73,8 @@ object MediaRecommendationsViewBinder { viewModel: MediaRecommendationsViewModel, mediaViewController: MediaViewController, falsingManager: FalsingManager, + backgroundDispatcher: CoroutineDispatcher, + mainDispatcher: CoroutineDispatcher, ) { mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility val cardView = viewHolder.recommendations @@ -59,7 +84,14 @@ object MediaRecommendationsViewBinder { launch { viewModel.mediaRecsCard.collectLatest { viewModel -> viewModel?.let { - bindRecsCard(viewHolder, it, mediaViewController, falsingManager) + bindRecsCard( + viewHolder, + it, + mediaViewController, + falsingManager, + backgroundDispatcher, + mainDispatcher, + ) } } } @@ -68,19 +100,19 @@ object MediaRecommendationsViewBinder { } } - fun bindRecsCard( + suspend fun bindRecsCard( viewHolder: RecommendationViewHolder, viewModel: MediaRecsCardViewModel, viewController: MediaViewController, falsingManager: FalsingManager, + backgroundDispatcher: CoroutineDispatcher, + mainDispatcher: CoroutineDispatcher, ) { // Set up media control location and its listener. viewModel.onLocationChanged(viewController.currentEndLocation) viewController.locationChangeListener = viewModel.onLocationChanged // Bind main card. - viewHolder.cardTitle.setTextColor(viewModel.cardTitleColor) - viewHolder.recommendations.backgroundTintList = ColorStateList.valueOf(viewModel.cardColor) viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(viewController.isGutsVisible) @@ -100,8 +132,17 @@ object MediaRecommendationsViewBinder { return@setOnLongClickListener true } + // Bind colors + val appIcon = viewModel.mediaRecs.first().appIcon + fetchAndUpdateColors(viewHolder, appIcon, backgroundDispatcher, mainDispatcher) // Bind all recommendations. - bindRecommendationsList(viewHolder, viewModel.mediaRecs, falsingManager) + bindRecommendationsList( + viewHolder, + viewModel.mediaRecs, + falsingManager, + backgroundDispatcher, + mainDispatcher, + ) updateRecommendationsVisibility(viewController, viewHolder.recommendations) // Set visibility of recommendations. @@ -153,26 +194,21 @@ object MediaRecommendationsViewBinder { } gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled) - gutsViewHolder.setTextPrimaryColor(gutsViewModel.textPrimaryColor) - gutsViewHolder.setAccentPrimaryColor(gutsViewModel.accentPrimaryColor) - gutsViewHolder.setSurfaceColor(gutsViewModel.surfaceColor) } - private fun bindRecommendationsList( + private suspend fun bindRecommendationsList( viewHolder: RecommendationViewHolder, mediaRecs: List, - falsingManager: FalsingManager + falsingManager: FalsingManager, + backgroundDispatcher: CoroutineDispatcher, + mainDispatcher: CoroutineDispatcher, ) { mediaRecs.forEachIndexed { index, mediaRecViewModel -> if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed val appIconView = viewHolder.mediaAppIcons[index] appIconView.clearColorFilter() - if (mediaRecViewModel.appIcon != null) { - appIconView.setImageDrawable(mediaRecViewModel.appIcon) - } else { - appIconView.setImageResource(R.drawable.ic_music_note) - } + appIconView.setImageDrawable(mediaRecViewModel.appIcon) val mediaCoverContainer = viewHolder.mediaCoverContainers[index] mediaCoverContainer.setOnClickListener { @@ -187,29 +223,24 @@ object MediaRecommendationsViewBinder { } val mediaCover = viewHolder.mediaCoverItems[index] - val width: Int = - mediaCover.context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) - val height: Int = - mediaCover.context.resources.getDimensionPixelSize( - R.dimen.qs_media_rec_album_height_expanded - ) - val coverMatrix = Matrix(mediaCover.imageMatrix) - coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height) - mediaCover.imageMatrix = coverMatrix - mediaCover.setImageDrawable(mediaRecViewModel.albumIcon) + bindRecommendationArtwork( + mediaCover.context, + viewHolder, + mediaRecViewModel, + index, + backgroundDispatcher, + mainDispatcher, + ) mediaCover.contentDescription = mediaRecViewModel.contentDescription val title = viewHolder.mediaTitles[index] title.text = mediaRecViewModel.title - title.setTextColor(ColorStateList.valueOf(mediaRecViewModel.titleColor)) val subtitle = viewHolder.mediaSubtitles[index] subtitle.text = mediaRecViewModel.subtitle - subtitle.setTextColor(ColorStateList.valueOf(mediaRecViewModel.subtitleColor)) val progressBar = viewHolder.mediaProgressBars[index] progressBar.progress = mediaRecViewModel.progress - progressBar.progressTintList = ColorStateList.valueOf(mediaRecViewModel.progressColor) if (mediaRecViewModel.progress == 0) { progressBar.visibility = View.GONE } @@ -291,11 +322,148 @@ object MediaRecommendationsViewBinder { TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, displayAvailableDpWidth.toFloat(), - res.displayMetrics + res.displayMetrics, ) .toInt() displayAvailableWidth / recCoverWidth } return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt() } + + private suspend fun bindRecommendationArtwork( + context: Context, + viewHolder: RecommendationViewHolder, + viewModel: MediaRecViewModel, + index: Int, + backgroundDispatcher: CoroutineDispatcher, + mainDispatcher: CoroutineDispatcher, + ) { + val traceCookie = viewHolder.hashCode() + val traceName = "MediaRecommendationsViewBinder#bindRecommendationArtwork" + Trace.beginAsyncSection(traceName, traceCookie) + + // Capture width & height from views in foreground for artwork scaling in background + val width = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + val height = + context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_height_expanded) + + withContext(backgroundDispatcher) { + val artwork = + getRecCoverBackground( + context, + viewModel.albumIcon, + width, + height, + backgroundDispatcher, + ) + withContext(mainDispatcher) { + val mediaCover = viewHolder.mediaCoverItems[index] + val coverMatrix = Matrix(mediaCover.imageMatrix) + coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height) + mediaCover.imageMatrix = coverMatrix + mediaCover.setImageDrawable(artwork) + } + } + } + + /** Returns the recommendation album cover of [width]x[height] size. */ + private suspend fun getRecCoverBackground( + context: Context, + icon: Icon?, + width: Int, + height: Int, + backgroundDispatcher: CoroutineDispatcher, + ): Drawable = + withContext(backgroundDispatcher) { + return@withContext MediaArtworkHelper.getWallpaperColor( + context, + backgroundDispatcher, + icon, + TAG, + ) + ?.let { wallpaperColors -> + addGradientToRecommendationAlbum( + context, + icon!!, + ColorScheme(wallpaperColors, true, Style.CONTENT), + width, + height, + ) + } ?: ColorDrawable(Color.TRANSPARENT) + } + + private fun addGradientToRecommendationAlbum( + context: Context, + artworkIcon: Icon, + mutableColorScheme: ColorScheme, + width: Int, + height: Int, + ): LayerDrawable { + // First try scaling rec card using bitmap drawable. + // If returns null, set drawable bounds. + val albumArt = + getScaledRecommendationCover(context, artworkIcon, width, height) + ?: MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) + val gradient = + AppCompatResources.getDrawable(context, R.drawable.qs_media_rec_scrim)?.mutate() + as GradientDrawable + return MediaArtworkHelper.setUpGradientColorOnDrawable( + albumArt, + gradient, + mutableColorScheme, + MEDIA_REC_SCRIM_START_ALPHA, + MEDIA_REC_SCRIM_END_ALPHA, + ) + } + + /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */ + private fun getScaledRecommendationCover( + context: Context, + artworkIcon: Icon, + width: Int, + height: Int, + ): Drawable? { + check(width > 0) { "Width must be a positive number but was $width" } + check(height > 0) { "Height must be a positive number but was $height" } + + return if ( + artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP + ) { + artworkIcon.bitmap?.let { + val bitmap = Bitmap.createScaledBitmap(it, width, height, false) + BitmapDrawable(context.resources, bitmap) + } + } else { + null + } + } + + private suspend fun fetchAndUpdateColors( + viewHolder: RecommendationViewHolder, + appIcon: Drawable, + backgroundDispatcher: CoroutineDispatcher, + mainDispatcher: CoroutineDispatcher, + ) = + withContext(backgroundDispatcher) { + val colorScheme = + ColorScheme(WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true) + withContext(mainDispatcher) { + val backgroundColor = surfaceFromScheme(colorScheme) + val textPrimaryColor = textPrimaryFromScheme(colorScheme) + val textSecondaryColor = textSecondaryFromScheme(colorScheme) + + viewHolder.cardTitle.setTextColor(textPrimaryColor) + viewHolder.recommendations.setBackgroundTintList( + ColorStateList.valueOf(backgroundColor) + ) + + viewHolder.mediaTitles.forEach { it.setTextColor(textPrimaryColor) } + viewHolder.mediaSubtitles.forEach { it.setTextColor(textSecondaryColor) } + viewHolder.mediaProgressBars.forEach { + it.progressTintList = ColorStateList.valueOf(textPrimaryColor) + } + + viewHolder.gutsViewHolder.setColors(colorScheme) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 8505a784d3024497aee5e8cd022307a0f703ff97..bb9517a14142fac2d31861578ae98bddf6d50466 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -123,7 +123,7 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) * Class that is responsible for keeping the view carousel up to date. This also handles changes in * state and applies them to the media carousel like the expansion. */ -@ExperimentalCoroutinesApi +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MediaCarouselController @Inject @@ -252,13 +252,13 @@ constructor( fun calculateAlpha( squishinessFraction: Float, startPosition: Float, - endPosition: Float + endPosition: Float, ): Float { val transformFraction = MathUtils.constrain( (squishinessFraction - startPosition) / (endPosition - startPosition), 0F, - 1F + 1F, ) return TRANSFORM_BEZIER.getInterpolation(transformFraction) } @@ -354,7 +354,7 @@ constructor( this::closeGuts, falsingManager, this::logSmartspaceImpression, - logger + logger, ) carouselLocale = context.resources.configuration.locales.get(0) isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL @@ -387,7 +387,7 @@ constructor( object : MediaHostStatesManager.Callback { override fun onHostStateChanged( @MediaLocation location: Int, - mediaHostState: MediaHostState + mediaHostState: MediaHostState, ) { updateUserVisibility() if (location == desiredLocation) { @@ -412,7 +412,7 @@ constructor( bgExecutor.execute { globalSettings.registerContentObserverSync( Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), - animationScaleObserver + animationScaleObserver, ) } } @@ -452,7 +452,7 @@ constructor( data: MediaData, immediately: Boolean, receivedSmartspaceCardLatency: Int, - isSsReactivated: Boolean + isSsReactivated: Boolean, ) { debugLogger.logMediaLoaded(key, data.active) if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) { @@ -468,7 +468,7 @@ constructor( SSPACE_CARD_REPORTED__LOCKSCREEN, SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), - rank = MediaPlayerData.getMediaPlayerIndex(key) + rank = MediaPlayerData.getMediaPlayerIndex(key), ) } if ( @@ -500,7 +500,7 @@ constructor( SSPACE_CARD_REPORTED__DREAM_OVERLAY, ), rank = index, - receivedLatencyMillis = receivedSmartspaceCardLatency + receivedLatencyMillis = receivedSmartspaceCardLatency, ) } } @@ -533,7 +533,7 @@ constructor( override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, ) { debugLogger.logRecommendationLoaded(key, data.isActive) // Log the case where the hidden media carousel with the existed inactive resume @@ -568,7 +568,7 @@ constructor( receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) - .toInt() + .toInt(), ) } } @@ -589,7 +589,7 @@ constructor( receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) - .toInt() + .toInt(), ) } if ( @@ -644,10 +644,7 @@ constructor( mediaCarouselScrollHandler.onSettingsButtonUpdated(settings) settingsButton.setOnClickListener { logger.logCarouselSettings() - activityStarter.startActivity( - settingsIntent, - /* dismissShade= */ true, - ) + activityStarter.startActivity(settingsIntent, /* dismissShade= */ true) } } @@ -739,7 +736,7 @@ constructor( val lp = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) when (commonViewModel) { is MediaCommonViewModel.MediaControl -> { @@ -771,6 +768,8 @@ constructor( commonViewModel.recsViewModel, viewController, falsingManager, + backgroundDispatcher, + mainDispatcher, ) mediaContent.addView(viewHolder.recommendations, position) controllerById[commonViewModel.key] = viewController @@ -785,7 +784,7 @@ constructor( ) { mediaCarouselScrollHandler.scrollToPlayer( mediaCarouselScrollHandler.visibleMediaIndex, - destIndex = 0 + destIndex = 0, ) } mediaCarouselScrollHandler.onPlayersChanged() @@ -799,7 +798,7 @@ constructor( mediaCarouselScrollHandler.onPlayersChanged() onAddOrUpdateVisibleToUserCard( position, - commonViewModel is MediaCommonViewModel.MediaControl + commonViewModel is MediaCommonViewModel.MediaControl, ) } @@ -856,7 +855,7 @@ constructor( mediaCarouselScrollHandler.qsExpanded, mediaCarouselScrollHandler.visibleMediaIndex, currentEndLocation, - isMediaCardUpdate + isMediaCardUpdate, ) } } @@ -893,7 +892,7 @@ constructor( secureSettings.getBoolForUser( Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true, - UserHandle.USER_CURRENT + UserHandle.USER_CURRENT, ) } } @@ -926,7 +925,7 @@ constructor( private fun reorderAllPlayers( previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?, - key: String? = null + key: String? = null, ) { mediaContent.removeAllViews() for (mediaPlayer in MediaPlayerData.players()) { @@ -960,7 +959,7 @@ constructor( TAG, "Size of players list and number of views in carousel are out of sync. " + "Players size is ${MediaPlayerData.players().size}. " + - "View count is ${mediaContent.childCount}." + "View count is ${mediaContent.childCount}.", ) } } @@ -970,7 +969,7 @@ constructor( key: String, oldKey: String?, data: MediaData, - isSsReactivated: Boolean + isSsReactivated: Boolean, ): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") { MediaPlayerData.moveIfExists(oldKey, key) @@ -992,7 +991,7 @@ constructor( val lp = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) newPlayer.mediaViewHolder?.player?.setLayoutParams(lp) newPlayer.bindPlayer(data, key) @@ -1005,7 +1004,7 @@ constructor( newPlayer, systemClock, isSsReactivated, - debugLogger + debugLogger, ) updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true) // Media data added from a recommendation card should starts playing. @@ -1025,7 +1024,7 @@ constructor( existingPlayer, systemClock, isSsReactivated, - debugLogger + debugLogger, ) val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String() // In case of recommendations hits. @@ -1051,7 +1050,7 @@ constructor( private fun addSmartspaceMediaRecommendations( key: String, data: SmartspaceMediaData, - shouldPrioritize: Boolean + shouldPrioritize: Boolean, ) = traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") { if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel") @@ -1090,7 +1089,7 @@ constructor( val lp = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) newRecs.bindRecommendation(data) @@ -1117,7 +1116,7 @@ constructor( TAG, "Size of players list and number of views in carousel are out of sync. " + "Players size is ${MediaPlayerData.players().size}. " + - "View count is ${mediaContent.childCount}." + "View count is ${mediaContent.childCount}.", ) } } @@ -1173,7 +1172,7 @@ constructor( addSmartspaceMediaRecommendations( it.targetId, it, - MediaPlayerData.shouldPrioritizeSs + MediaPlayerData.shouldPrioritizeSs, ) } } else { @@ -1185,7 +1184,7 @@ constructor( key = key, oldKey = null, data = data, - isSsReactivated = isSsReactivated + isSsReactivated = isSsReactivated, ) } if (recreateMedia) { @@ -1248,7 +1247,7 @@ constructor( @MediaLocation startLocation: Int, @MediaLocation endLocation: Int, progress: Float, - immediately: Boolean + immediately: Boolean, ) { if ( startLocation != currentStartLocation || @@ -1286,7 +1285,7 @@ constructor( squishFraction, (pageIndicator.translationY + pageIndicator.height) / mediaCarousel.measuredHeight, - 1F + 1F, ) var alpha = 1.0f if (!endIsVisible || !startIsVisible) { @@ -1354,7 +1353,7 @@ constructor( currentCarouselHeight = height mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, - currentCarouselHeight + currentCarouselHeight, ) updatePageIndicatorLocation() updatePageIndicatorAlpha() @@ -1386,13 +1385,13 @@ constructor( private fun updateViewControllerToState( viewController: MediaViewController, - noAnimation: Boolean + noAnimation: Boolean, ) { viewController.setCurrentState( startLocation = currentStartLocation, endLocation = currentEndLocation, transitionProgress = currentTransitionProgress, - applyImmediately = noAnimation + applyImmediately = noAnimation, ) } @@ -1411,7 +1410,7 @@ constructor( desiredHostState: MediaHostState?, animate: Boolean, duration: Long = 200, - startDelay: Long = 0 + startDelay: Long = 0, ) = traceSection("MediaCarouselController#onDesiredLocationChanged") { desiredHostState?.let { @@ -1435,7 +1434,7 @@ constructor( if (animate) { mediaPlayer.mediaViewController.animatePendingStateChange( duration = duration, - delay = startDelay + delay = startDelay, ) } if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) { @@ -1506,7 +1505,7 @@ constructor( mediaCarouselViewModel.onCardVisibleToUser( qsExpanded, mediaCarouselScrollHandler.visibleMediaIndex, - currentEndLocation + currentEndLocation, ) return } @@ -1524,7 +1523,7 @@ constructor( 800, // SMARTSPACE_CARD_SEEN it.mSmartspaceId, it.mUid, - intArrayOf(it.surfaceForSmartspaceLogging) + intArrayOf(it.surfaceForSmartspaceLogging), ) it.mIsImpressed = true } @@ -1559,7 +1558,7 @@ constructor( interactedSubcardCardinality: Int = 0, rank: Int = mediaCarouselScrollHandler.visibleMediaIndex, receivedLatencyMillis: Int = 0, - isSwipeToDismiss: Boolean = false + isSwipeToDismiss: Boolean = false, ) { if (MediaPlayerData.players().size <= rank) { return @@ -1600,7 +1599,7 @@ constructor( interactedSubcardCardinality, receivedLatencyMillis, null, // Media cards cannot have subcards. - null // Media cards don't have dimensions today. + null, // Media cards don't have dimensions today. ) if (DEBUG) { @@ -1613,7 +1612,7 @@ constructor( "uid: $uid " + "interactedSubcardRank: $interactedSubcardRank " + "interactedSubcardCardinality: $interactedSubcardCardinality " + - "received_latency_millis: $receivedLatencyMillis" + "received_latency_millis: $receivedLatencyMillis", ) } } @@ -1633,7 +1632,7 @@ constructor( it.mUid, intArrayOf(it.surfaceForSmartspaceLogging), rank = index, - isSwipeToDismiss = true + isSwipeToDismiss = true, ) // Reset card impressed state when swipe to dismissed it.mIsImpressed = false @@ -1692,7 +1691,7 @@ internal object MediaPlayerData { active = true, resumeAction = null, instanceId = InstanceId.fakeInstanceId(-1), - appUid = -1 + appUid = -1, ) // Whether should prioritize Smartspace card. @@ -1741,7 +1740,7 @@ internal object MediaPlayerData { player: MediaControlPanel, clock: SystemClock, isSsReactivated: Boolean, - debugLogger: MediaCarouselControllerLogger? = null + debugLogger: MediaCarouselControllerLogger? = null, ) { val removedPlayer = removeMediaPlayer(key) if (removedPlayer != null && removedPlayer != player) { @@ -1754,7 +1753,7 @@ internal object MediaPlayerData { data, key, clock.currentTimeMillis(), - isSsReactivated = isSsReactivated + isSsReactivated = isSsReactivated, ) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) @@ -1768,7 +1767,7 @@ internal object MediaPlayerData { shouldPrioritize: Boolean, clock: SystemClock, debugLogger: MediaCarouselControllerLogger? = null, - update: Boolean = false + update: Boolean = false, ) { shouldPrioritizeSs = shouldPrioritize val removedPlayer = removeMediaPlayer(key) @@ -1782,7 +1781,7 @@ internal object MediaPlayerData { EMPTY.copy(active = data.isActive, isPlaying = false), key, clock.currentTimeMillis(), - isSsReactivated = true + isSsReactivated = true, ) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) @@ -1793,7 +1792,7 @@ internal object MediaPlayerData { fun moveIfExists( oldKey: String?, newKey: String, - debugLogger: MediaCarouselControllerLogger? = null + debugLogger: MediaCarouselControllerLogger? = null, ) { if (oldKey == null || oldKey == newKey) { return diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 8bec46abd5044ffb588aad47d569899a6392b0af..70ca8249277592762c254fd43419a36e0ce92233 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -545,6 +545,7 @@ public class MediaControlPanel { /** Bind this player view based on the data given. */ public void bindPlayer(@NonNull MediaData data, String key) { + SceneContainerFlag.assertInLegacyMode(); if (mMediaViewHolder == null) { return; } @@ -638,10 +639,7 @@ public class MediaControlPanel { // to something which might impact the measurement // State refresh interferes with the translation animation, only run it if it's not running. if (!mMetadataAnimationHandler.isRunning()) { - // Don't refresh in scene framework, because it will calculate with invalid layout sizes - if (!SceneContainerFlag.isEnabled()) { - mMediaViewController.refreshState(); - } + mMediaViewController.refreshState(); } if (shouldPlayTurbulenceNoise()) { @@ -907,11 +905,6 @@ public class MediaControlPanel { // Capture width & height from views in foreground for artwork scaling in background int width = mMediaViewHolder.getAlbumView().getMeasuredWidth(); int height = mMediaViewHolder.getAlbumView().getMeasuredHeight(); - if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) { - // TODO(b/312714128): ensure we have a valid size before setting background - width = mMediaViewController.getWidthInSceneContainerPx(); - height = mMediaViewController.getHeightInSceneContainerPx(); - } final int finalWidth = width; final int finalHeight = height; diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index a9d2a541a241ec58cafcc405717bf5af7f389e10..38cea5b23f783d4a9556feef85689cb8d88bbf04 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -64,6 +64,7 @@ import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -431,6 +432,12 @@ constructor( /** Is the communal UI showing */ private var isCommunalShowing: Boolean = false + /** Is the primary bouncer showing */ + private var isPrimaryBouncerShowing: Boolean = false + + /** Is either shade or QS fully expanded */ + private var isAnyShadeFullyExpanded: Boolean = false + /** Is the communal UI showing and not dreaming */ private var onCommunalNotDreaming: Boolean = false @@ -587,6 +594,20 @@ constructor( } } + coroutineScope.launch { + shadeInteractor.isAnyFullyExpanded.collect { + isAnyShadeFullyExpanded = it + updateUserVisibility() + } + } + + coroutineScope.launch { + keyguardInteractor.primaryBouncerShowing.collect { + isPrimaryBouncerShowing = it + updateUserVisibility() + } + } + if (mediaControlsLockscreenShadeBugFix()) { coroutineScope.launch { shadeInteractor.shadeExpansion.collect { expansion -> @@ -638,6 +659,7 @@ constructor( communalShowing && isDreaming && isShadeExpanding onCommunalNotDreaming = communalShowing && !isDreaming updateDesiredLocation(forceNoAnimation = true) + updateUserVisibility() } } } @@ -1290,7 +1312,8 @@ constructor( val shadeVisible = isLockScreenVisibleToUser() || isLockScreenShadeVisibleToUser() || - isHomeScreenShadeVisibleToUser() + isHomeScreenShadeVisibleToUser() || + isGlanceableHubVisibleToUser() val mediaVisible = qsExpanded || hasActiveMediaOrRecommendation mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = shadeVisible && mediaVisible @@ -1318,6 +1341,10 @@ constructor( statusBarStateController.isExpanded } + private fun isGlanceableHubVisibleToUser(): Boolean { + return isCommunalShowing && !isPrimaryBouncerShowing && !isAnyShadeFullyExpanded + } + companion object { /** Attached in expanded quick settings */ const val LOCATION_QS = 0 diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt index c97221e7bd50ad50e58ef9cf960de8496f5bc61c..c21513b1263a5d250f1821448e601af60d77b5d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt @@ -87,26 +87,19 @@ object MediaArtworkHelper { gradient: GradientDrawable, colorScheme: ColorScheme, startAlpha: Float, - endAlpha: Float + endAlpha: Float, ): LayerDrawable { gradient.colors = intArrayOf( getColorWithAlpha(backgroundStartFromScheme(colorScheme), startAlpha), - getColorWithAlpha(backgroundEndFromScheme(colorScheme), endAlpha) + getColorWithAlpha(backgroundEndFromScheme(colorScheme), endAlpha), ) return LayerDrawable(arrayOf(albumArt, gradient)) } - /** Returns [ColorScheme] of media app given its [packageName]. */ - fun getColorScheme( - applicationContext: Context, - packageName: String, - tag: String, - style: Style = Style.TONAL_SPOT - ): ColorScheme? { + /** Returns [ColorScheme] of media app given its [icon]. */ + fun getColorScheme(icon: Drawable, tag: String, style: Style = Style.TONAL_SPOT): ColorScheme? { return try { - // Set up media source app's logo. - val icon = applicationContext.packageManager.getApplicationIcon(packageName) ColorScheme(WallpaperColors.fromDrawable(icon), true, style) } catch (e: PackageManager.NameNotFoundException) { Log.w(tag, "Fail to get media app info", e) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt index 6c7c31c41eebafeebffed5a416fdc732cac61902..314d9afe5aa6757d2298b975069c05ebc7952845 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt @@ -16,15 +16,11 @@ package com.android.systemui.media.controls.ui.viewmodel -import android.annotation.ColorInt import android.graphics.drawable.Drawable /** Models UI state for media guts menu */ data class GutsViewModel( val gutsText: CharSequence, - @ColorInt val textPrimaryColor: Int, - @ColorInt val accentPrimaryColor: Int, - @ColorInt val surfaceColor: Int, val isDismissEnabled: Boolean = true, val onDismissClicked: () -> Unit, val cancelTextBackground: Drawable?, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt index 82099e61009f6a467e85b65f38bd0aa9c11b4555..3f22d549698c1dcb3df72a0e24aa8ae619bc1ffa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt @@ -32,4 +32,16 @@ data class MediaActionViewModel( val buttonId: Int? = null, val isEnabled: Boolean, val onClicked: (Int) -> Unit, -) +) { + fun contentEquals(other: MediaActionViewModel?): Boolean { + return other?.let { + contentDescription == other.contentDescription && + isVisibleWhenScrubbing == other.isVisibleWhenScrubbing && + notVisibleValue == other.notVisibleValue && + showInCollapsed == other.showInCollapsed && + rebindId == other.rebindId && + buttonId == other.buttonId && + isEnabled == other.isEnabled + } ?: false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index 64820e0d0cedc8c0f8f482ffc6c7b2e567a64fc8..f07f2de0853700b5ef709d4aef7834f6fea4eb41 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -33,15 +33,9 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.MediaContr import com.android.systemui.media.controls.shared.model.MediaAction import com.android.systemui.media.controls.shared.model.MediaButton import com.android.systemui.media.controls.shared.model.MediaControlModel -import com.android.systemui.media.controls.ui.animation.accentPrimaryFromScheme -import com.android.systemui.media.controls.ui.animation.surfaceFromScheme -import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme -import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_CLICK_EVENT import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT import com.android.systemui.media.controls.util.MediaUiEventLogger -import com.android.systemui.monet.ColorScheme -import com.android.systemui.monet.Style import com.android.systemui.res.R import java.util.concurrent.Executor import kotlinx.coroutines.CoroutineDispatcher @@ -69,18 +63,30 @@ class MediaControlViewModel( mediaControl?.let { toViewModel(it) } } } - .distinctUntilChanged() + .distinctUntilChanged { old, new -> + (new == null && old == null) || new?.contentEquals(old) ?: false + } .flowOn(backgroundDispatcher) private var isPlaying = false private var isAnyButtonClicked = false private var location = -1 + private var playerViewModel: MediaPlayerViewModel? = null + + fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean { + val contentEquals = playerViewModel?.contentEquals(viewModel) ?: false + return (!contentEquals).also { playerViewModel = viewModel } + } + + fun onMediaControlIsBound(artistName: CharSequence, titleName: CharSequence) { + interactor.logMediaControlIsBound(artistName, titleName) + } private fun onDismissMediaData( token: Token?, uid: Int, packageName: String, - instanceId: InstanceId + instanceId: InstanceId, ) { logger.logLongPressDismiss(uid, packageName, instanceId) interactor.removeMediaControl( @@ -88,30 +94,13 @@ class MediaControlViewModel( instanceId, MEDIA_PLAYER_ANIMATION_DELAY, SMARTSPACE_CARD_DISMISS_EVENT, - location + location, ) } - private suspend fun toViewModel(model: MediaControlModel): MediaPlayerViewModel? { + private fun toViewModel(model: MediaControlModel): MediaPlayerViewModel { val mediaController = model.token?.let { MediaController(applicationContext, it) } - val wallpaperColors = - MediaArtworkHelper.getWallpaperColor( - applicationContext, - backgroundDispatcher, - model.artwork, - TAG - ) - val scheme = - wallpaperColors?.let { ColorScheme(it, true, Style.CONTENT) } - ?: MediaArtworkHelper.getColorScheme( - applicationContext, - model.packageName, - TAG, - Style.CONTENT - ) - ?: return null - - val gutsViewModel = toGutsViewModel(model, scheme) + val gutsViewModel = toGutsViewModel(model) // Set playing state val wasPlaying = isPlaying @@ -131,7 +120,7 @@ class MediaControlViewModel( R.string.controls_media_playing_item_description, model.songName, model.artistName, - model.appName + model.appName, ) } }, @@ -142,8 +131,6 @@ class MediaControlViewModel( artistName = model.artistName ?: "", titleName = model.songName ?: "", isExplicitVisible = model.showExplicit, - shouldAddGradient = wallpaperColors != null, - colorScheme = scheme, canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons), playTurbulenceNoise = isPlaying && !wasPlaying && wasButtonClicked, useSemanticActions = model.semanticActionButtons != null, @@ -157,7 +144,7 @@ class MediaControlViewModel( expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, - location + location, ) } }, @@ -177,7 +164,7 @@ class MediaControlViewModel( } } }, - onLocationChanged = { location = it } + onLocationChanged = { location = it }, ) } @@ -191,7 +178,7 @@ class MediaControlViewModel( device?.name?.let { TextUtils.equals( it, - applicationContext.getString(R.string.broadcasting_description_is_broadcasting) + applicationContext.getString(R.string.broadcasting_description_is_broadcasting), ) } ?: false val useDisabledAlpha = @@ -236,19 +223,19 @@ class MediaControlViewModel( logger.logOpenBroadcastDialog( model.uid, model.packageName, - model.instanceId + model.instanceId, ) interactor.startBroadcastDialog( expandable, device?.name.toString(), - model.packageName + model.packageName, ) } else { logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId) interactor.startMediaOutputDialog( expandable, model.packageName, - model.token + model.token, ) } } else { @@ -257,27 +244,24 @@ class MediaControlViewModel( ?: interactor.startMediaOutputDialog( expandable, model.packageName, - model.token + model.token, ) } - } + }, ) } - private fun toGutsViewModel(model: MediaControlModel, scheme: ColorScheme): GutsViewModel { + private fun toGutsViewModel(model: MediaControlModel): GutsViewModel { return GutsViewModel( gutsText = if (model.isDismissible) { applicationContext.getString( R.string.controls_media_close_session, - model.appName + model.appName, ) } else { applicationContext.getString(R.string.controls_media_active_session) }, - textPrimaryColor = textPrimaryFromScheme(scheme), - accentPrimaryColor = accentPrimaryFromScheme(scheme), - surfaceColor = surfaceFromScheme(scheme), isDismissEnabled = model.isDismissible, onDismissClicked = { onDismissMediaData(model.token, model.uid, model.packageName, model.instanceId) @@ -304,7 +288,7 @@ class MediaControlViewModel( model, mediaButton.getActionById(buttonId), buttonId, - isScrubbingTimeEnabled + isScrubbingTimeEnabled, ) } } @@ -319,7 +303,7 @@ class MediaControlViewModel( model: MediaControlModel, mediaAction: MediaAction?, buttonId: Int, - canShowScrubbingTimeViews: Boolean + canShowScrubbingTimeViews: Boolean, ): MediaActionViewModel { val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId) val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId) @@ -353,7 +337,7 @@ class MediaControlViewModel( private fun toNotifActionViewModel( model: MediaControlModel, mediaAction: MediaAction, - index: Int + index: Int, ): MediaActionViewModel { return MediaActionViewModel( icon = mediaAction.icon, @@ -375,7 +359,7 @@ class MediaControlViewModel( uid: Int, packageName: String, instanceId: InstanceId, - action: Runnable + action: Runnable, ) { logger.logTapAction(id, uid, packageName, instanceId) interactor.logSmartspaceUserEvent(SMARTSPACE_CARD_CLICK_EVENT, location) @@ -424,7 +408,7 @@ class MediaControlViewModel( R.id.actionPrev, R.id.actionNext, R.id.action0, - R.id.action1 + R.id.action1, ) const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt index 9df9bccdf522c61b590fd4dd7c1f6553dd774f97..2a47a5af790adca8006ab453cc3eee8c84ab24a9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt @@ -29,4 +29,15 @@ data class MediaOutputSwitcherViewModel( val alpha: Float, val isVisible: Boolean, val onClicked: (Expandable) -> Unit, -) +) { + fun contentEquals(other: MediaOutputSwitcherViewModel?): Boolean { + return (other?.let { + isTapEnabled == other.isTapEnabled && + deviceString == other.deviceString && + isCurrentBroadcastApp == other.isCurrentBroadcastApp && + isIntentValid == other.isIntentValid && + alpha == other.alpha && + isVisible == other.isVisible + } ?: false) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt index 96e7fc79c8eb012d8106b8d61db357fd57a2bbd1..4aae72fb375be5a8afbeb451df316f4b624e7869 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.media.controls.ui.viewmodel import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon -import com.android.systemui.monet.ColorScheme /** Models UI state for media player. */ data class MediaPlayerViewModel( @@ -30,8 +29,6 @@ data class MediaPlayerViewModel( val artistName: CharSequence, val titleName: CharSequence, val isExplicitVisible: Boolean, - val shouldAddGradient: Boolean, - val colorScheme: ColorScheme, val canShowTime: Boolean, val playTurbulenceNoise: Boolean, val useSemanticActions: Boolean, @@ -43,4 +40,29 @@ data class MediaPlayerViewModel( val onSeek: () -> Unit, val onBindSeekbar: (SeekBarViewModel) -> Unit, val onLocationChanged: (Int) -> Unit, -) +) { + fun contentEquals(other: MediaPlayerViewModel?): Boolean { + return other?.let { + other.backgroundCover == backgroundCover && + appIcon == other.appIcon && + useGrayColorFilter == other.useGrayColorFilter && + artistName == other.artistName && + titleName == other.titleName && + isExplicitVisible == other.isExplicitVisible && + canShowTime == other.canShowTime && + playTurbulenceNoise == other.playTurbulenceNoise && + useSemanticActions == other.useSemanticActions && + areActionsEqual(other.actionButtons) && + outputSwitcher.contentEquals(other.outputSwitcher) + } ?: false + } + + private fun areActionsEqual(other: List): Boolean { + actionButtons.forEachIndexed { index, mediaActionViewModel -> + if (!mediaActionViewModel.contentEquals(other[index])) { + return false + } + } + return true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt index 2f9fc9bc699a5241f8eaead4045fcab7d3bb8ba1..77add4035067b1be2583742286c826e125f16859 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt @@ -16,21 +16,18 @@ package com.android.systemui.media.controls.ui.viewmodel -import android.annotation.ColorInt import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon import com.android.systemui.animation.Expandable /** Models UI state for media recommendation item */ data class MediaRecViewModel( val contentDescription: CharSequence, val title: CharSequence = "", - @ColorInt val titleColor: Int, val subtitle: CharSequence = "", - @ColorInt val subtitleColor: Int, /** track progress [0 - 100] for the recommendation album. */ val progress: Int = 0, - @ColorInt val progressColor: Int, - val albumIcon: Drawable? = null, - val appIcon: Drawable? = null, + val albumIcon: Icon? = null, + val appIcon: Drawable, val onClicked: ((Expandable, Int) -> Unit), ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt index 1fd9c4f014ee23988d310b4e0a382ca5bd20bdb3..a7bce7772270bfd3a3571e6ceffb54c9c0fd486d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt @@ -18,17 +18,10 @@ package com.android.systemui.media.controls.ui.viewmodel import android.content.Context import android.content.Intent -import android.graphics.Bitmap -import android.graphics.Color -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.ColorDrawable +import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.Icon -import android.graphics.drawable.LayerDrawable import android.os.Process import android.util.Log -import androidx.appcompat.content.res.AppCompatResources import com.android.internal.logging.InstanceId import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton @@ -38,18 +31,11 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecom import com.android.systemui.media.controls.shared.model.MediaRecModel import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS -import com.android.systemui.media.controls.ui.animation.accentPrimaryFromScheme -import com.android.systemui.media.controls.ui.animation.surfaceFromScheme -import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme -import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme import com.android.systemui.media.controls.ui.controller.MediaViewController.Companion.GUTS_ANIMATION_DURATION -import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_CLICK_EVENT import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT import com.android.systemui.media.controls.util.MediaUiEventLogger -import com.android.systemui.monet.ColorScheme -import com.android.systemui.monet.Style import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -59,7 +45,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext /** Models UI state and handles user input for media recommendations */ @SysUISingleton @@ -92,7 +77,7 @@ constructor( uid: Int, packageName: String, dismissIntent: Intent?, - instanceId: InstanceId? + instanceId: InstanceId?, ) { logger.logLongPressDismiss(uid, packageName, instanceId) interactor.removeMediaRecommendations( @@ -100,7 +85,7 @@ constructor( dismissIntent, GUTS_DISMISS_DELAY_MS_DURATION, SMARTSPACE_CARD_DISMISS_EVENT, - location + location, ) } @@ -109,7 +94,7 @@ constructor( intent: Intent?, packageName: String, instanceId: InstanceId?, - index: Int + index: Int, ) { if (intent == null || intent.extras == null) { Log.e(TAG, "No tap action can be set up") @@ -131,7 +116,7 @@ constructor( SMARTSPACE_CARD_CLICK_EVENT, location, index, - NUM_REQUIRED_RECOMMENDATIONS + NUM_REQUIRED_RECOMMENDATIONS, ) } @@ -145,22 +130,7 @@ constructor( return null } - val scheme = - MediaArtworkHelper.getColorScheme(applicationContext, model.packageName, TAG) - ?: return null - - // Capture width & height from views in foreground for artwork scaling in background - val width = - applicationContext.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) - val height = - applicationContext.resources.getDimensionPixelSize( - R.dimen.qs_media_rec_album_height_expanded - ) - - val appIcon = applicationContext.packageManager.getApplicationIcon(model.packageName) - val textPrimaryColor = textPrimaryFromScheme(scheme) - val textSecondaryColor = textSecondaryFromScheme(scheme) - val backgroundColor = surfaceFromScheme(scheme) + val appIcon = getIconFromApp(model.packageName) ?: return null var areTitlesVisible = false var areSubtitlesVisible = false @@ -173,17 +143,9 @@ constructor( contentDescription = setUpMediaRecContentDescription(mediaRecModel, model.appName), title = mediaRecModel.title ?: "", - titleColor = textPrimaryColor, subtitle = mediaRecModel.subtitle ?: "", - subtitleColor = textSecondaryColor, progress = (progress * 100).toInt(), - progressColor = textPrimaryColor, - albumIcon = - getRecCoverBackground( - mediaRecModel.icon, - width, - height, - ), + albumIcon = mediaRecModel.icon, appIcon = appIcon, onClicked = { expandable, index -> onClicked( @@ -193,7 +155,7 @@ constructor( model.instanceId, index, ) - } + }, ) } // Subtitles should only be visible if titles are visible. @@ -204,21 +166,19 @@ constructor( if (gutsVisible) { applicationContext.getString( R.string.controls_media_close_session, - model.appName + model.appName, ) } else { applicationContext.getString(R.string.controls_media_smartspace_rec_header) } }, - cardColor = backgroundColor, - cardTitleColor = textPrimaryColor, onClicked = { expandable -> onClicked( expandable, model.dismissIntent, model.packageName, model.instanceId, - index = -1 + index = -1, ) }, onLongClicked = { @@ -227,28 +187,22 @@ constructor( mediaRecs = mediaRecs, areTitlesVisible = areTitlesVisible, areSubtitlesVisible = areSubtitlesVisible, - gutsMenu = toGutsViewModel(model, scheme), - onLocationChanged = { location = it } + gutsMenu = toGutsViewModel(model), + onLocationChanged = { location = it }, ) } - private fun toGutsViewModel( - model: MediaRecommendationsModel, - scheme: ColorScheme - ): GutsViewModel { + private fun toGutsViewModel(model: MediaRecommendationsModel): GutsViewModel { return GutsViewModel( gutsText = applicationContext.getString(R.string.controls_media_close_session, model.appName), - textPrimaryColor = textPrimaryFromScheme(scheme), - accentPrimaryColor = accentPrimaryFromScheme(scheme), - surfaceColor = surfaceFromScheme(scheme), onDismissClicked = { onMediaRecommendationsDismissed( model.key, model.uid, model.packageName, model.dismissIntent, - model.instanceId + model.instanceId, ) }, cancelTextBackground = @@ -260,56 +214,9 @@ constructor( ) } - /** Returns the recommendation album cover of [width]x[height] size. */ - private suspend fun getRecCoverBackground(icon: Icon?, width: Int, height: Int): Drawable = - withContext(backgroundDispatcher) { - return@withContext MediaArtworkHelper.getWallpaperColor( - applicationContext, - backgroundDispatcher, - icon, - TAG, - ) - ?.let { wallpaperColors -> - addGradientToRecommendationAlbum( - icon!!, - ColorScheme(wallpaperColors, true, Style.CONTENT), - width, - height - ) - } ?: ColorDrawable(Color.TRANSPARENT) - } - - private fun addGradientToRecommendationAlbum( - artworkIcon: Icon, - mutableColorScheme: ColorScheme, - width: Int, - height: Int - ): LayerDrawable { - // First try scaling rec card using bitmap drawable. - // If returns null, set drawable bounds. - val albumArt = - getScaledRecommendationCover(artworkIcon, width, height) - ?: MediaArtworkHelper.getScaledBackground( - applicationContext, - artworkIcon, - width, - height - ) - val gradient = - AppCompatResources.getDrawable(applicationContext, R.drawable.qs_media_rec_scrim) - ?.mutate() as GradientDrawable - return MediaArtworkHelper.setUpGradientColorOnDrawable( - albumArt, - gradient, - mutableColorScheme, - MEDIA_REC_SCRIM_START_ALPHA, - MEDIA_REC_SCRIM_END_ALPHA - ) - } - private fun setUpMediaRecContentDescription( mediaRec: MediaRecModel, - appName: CharSequence? + appName: CharSequence?, ): CharSequence { // Set up the accessibility label for the media item. val artistName = mediaRec.extras?.getString(KEY_SMARTSPACE_ARTIST_NAME, "") @@ -317,35 +224,23 @@ constructor( applicationContext.getString( R.string.controls_media_smartspace_rec_item_no_artist_description, mediaRec.title, - appName + appName, ) } else { applicationContext.getString( R.string.controls_media_smartspace_rec_item_description, mediaRec.title, artistName, - appName + appName, ) } } - /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */ - private fun getScaledRecommendationCover( - artworkIcon: Icon, - width: Int, - height: Int - ): Drawable? { - check(width > 0) { "Width must be a positive number but was $width" } - check(height > 0) { "Height must be a positive number but was $height" } - - return if ( - artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP - ) { - artworkIcon.bitmap?.let { - val bitmap = Bitmap.createScaledBitmap(it, width, height, false) - BitmapDrawable(applicationContext.resources, bitmap) - } - } else { + private fun getIconFromApp(packageName: String): Drawable? { + return try { + applicationContext.packageManager.getApplicationIcon(packageName) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "Cannot find icon for package $packageName", e) null } } @@ -353,8 +248,6 @@ constructor( companion object { private const val TAG = "MediaRecommendationsViewModel" private const val KEY_SMARTSPACE_ARTIST_NAME = "artist_name" - private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f - private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f /** * Delay duration is based on [GUTS_ANIMATION_DURATION], it should have 100 ms increase in * order to let the animation end. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt index 5ecbcb20a58a6cf23a5cae3ec147bb9887e6614f..f1f7dc2195d5ebc23b58f2dd95dca79139fcf5e5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt @@ -16,14 +16,11 @@ package com.android.systemui.media.controls.ui.viewmodel -import android.annotation.ColorInt import com.android.systemui.animation.Expandable /** Models UI state for media recommendations card. */ data class MediaRecsCardViewModel( val contentDescription: (Boolean) -> CharSequence, - @ColorInt val cardColor: Int, - @ColorInt val cardTitleColor: Int, val onClicked: (Expandable) -> Unit, val onLongClicked: () -> Unit, val mediaRecs: List, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index ff9495daaa22d1e88abe8536f99cc0f3098350a3..2961d05b2e51b0fca10ebfb31c3d6ed411e98464 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -57,7 +57,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { private static final float DEVICE_CONNECTED_ALPHA = 1f; protected List mMediaItemList = new CopyOnWriteArrayList<>(); - public MediaOutputAdapter(MediaOutputController controller) { + public MediaOutputAdapter(MediaSwitchingController controller) { super(controller); setHasStableIds(true); } @@ -531,8 +531,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @RequiresApi(34) private static class Api34Impl { @DoNotInline - static View.OnClickListener getClickListenerBasedOnSelectionBehavior(MediaDevice device, - MediaOutputController controller, View.OnClickListener defaultTransferListener) { + static View.OnClickListener getClickListenerBasedOnSelectionBehavior( + MediaDevice device, + MediaSwitchingController controller, + View.OnClickListener defaultTransferListener) { switch (device.getSelectionBehavior()) { case SELECTION_BEHAVIOR_NONE: return null; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 5958b0a24a5e869c9421906133a2bbe0e3d243c8..63a7e013022ab8f96d502a5b4f512b12ad4254b6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -63,7 +63,7 @@ public abstract class MediaOutputBaseAdapter extends static final int CUSTOMIZED_ITEM_GROUP = 2; static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3; - protected final MediaOutputController mController; + protected final MediaSwitchingController mController; private static final int UNMUTE_DEFAULT_VOLUME = 2; @@ -73,7 +73,7 @@ public abstract class MediaOutputBaseAdapter extends int mCurrentActivePosition; private boolean mIsInitVolumeFirstTime; - public MediaOutputBaseAdapter(MediaOutputController controller) { + public MediaOutputBaseAdapter(MediaSwitchingController controller) { mController = controller; mIsDragging = false; mCurrentActivePosition = -1; @@ -127,7 +127,7 @@ public abstract class MediaOutputBaseAdapter extends return mCurrentActivePosition; } - public MediaOutputController getController() { + public MediaSwitchingController getController() { return mController; } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 6cc4dcbaa1ea15c99bb5376591fa3c6b04a69349..6bc995f3437c0a120fea65a8ca4bd499175a10ee 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -65,11 +65,9 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -/** - * Base dialog for media output UI - */ -public abstract class MediaOutputBaseDialog extends SystemUIDialog implements - MediaOutputController.Callback, Window.Callback { +/** Base dialog for media output UI */ +public abstract class MediaOutputBaseDialog extends SystemUIDialog + implements MediaSwitchingController.Callback, Window.Callback { private static final String TAG = "MediaOutputDialog"; private static final String EMPTY_TITLE = " "; @@ -82,7 +80,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements private final RecyclerView.LayoutManager mLayoutManager; final Context mContext; - final MediaOutputController mMediaOutputController; + final MediaSwitchingController mMediaSwitchingController; final BroadcastSender mBroadcastSender; /** @@ -212,22 +210,22 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements @Override public void onLayoutCompleted(RecyclerView.State state) { super.onLayoutCompleted(state); - mMediaOutputController.setRefreshing(false); - mMediaOutputController.refreshDataSetIfNeeded(); + mMediaSwitchingController.setRefreshing(false); + mMediaSwitchingController.refreshDataSetIfNeeded(); } } public MediaOutputBaseDialog( Context context, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController, + MediaSwitchingController mediaSwitchingController, boolean includePlaybackAndAppMetadata) { super(context, R.style.Theme_SystemUI_Dialog_Media); // Save the context that is wrapped with our theme. mContext = getContext(); mBroadcastSender = broadcastSender; - mMediaOutputController = mediaOutputController; + mMediaSwitchingController = mediaSwitchingController; mLayoutManager = new LayoutManagerWrapper(mContext); mListMaxHeight = context.getResources().getDimensionPixelSize( R.dimen.media_output_dialog_list_max_height); @@ -279,9 +277,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements // Init bottom buttons mDoneButton.setOnClickListener(v -> dismiss()); mStopButton.setOnClickListener(v -> onStopButtonClick()); - mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication); + mAppButton.setOnClickListener(mMediaSwitchingController::tryToLaunchMediaApplication); mMediaMetadataSectionLayout.setOnClickListener( - mMediaOutputController::tryToLaunchMediaApplication); + mMediaSwitchingController::tryToLaunchMediaApplication); mDismissing = false; } @@ -298,10 +296,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements @Override public void start() { - mMediaOutputController.start(this); + mMediaSwitchingController.start(this); if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) { - mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor, - mBroadcastCallback); + mMediaSwitchingController.registerLeBroadcastServiceCallback( + mExecutor, mBroadcastCallback); mIsLeBroadcastCallbackRegistered = true; } } @@ -311,11 +309,11 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements // unregister broadcast callback should only depend on profile and registered flag // rather than remote device or broadcast state // otherwise it might have risks of leaking registered callback handle - if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) { - mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback); + if (mMediaSwitchingController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) { + mMediaSwitchingController.unregisterLeBroadcastServiceCallback(mBroadcastCallback); mIsLeBroadcastCallbackRegistered = false; } - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); } @VisibleForTesting @@ -326,18 +324,17 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements void refresh(boolean deviceSetChanged) { // TODO(287191450): remove binder calls in this method from the UI thread. // If the dialog is going away or is already refreshing, do nothing. - if (mDismissing || mMediaOutputController.isRefreshing()) { + if (mDismissing || mMediaSwitchingController.isRefreshing()) { return; } - mMediaOutputController.setRefreshing(true); + mMediaSwitchingController.setRefreshing(true); // Update header icon final int iconRes = getHeaderIconRes(); final IconCompat headerIcon = getHeaderIcon(); final IconCompat appSourceIcon = getAppSourceIcon(); boolean colorSetUpdated = false; mCastAppLayout.setVisibility( - mMediaOutputController.shouldShowLaunchSection() - ? View.VISIBLE : View.GONE); + mMediaSwitchingController.shouldShowLaunchSection() ? View.VISIBLE : View.GONE); if (iconRes != 0) { mHeaderIcon.setVisibility(View.VISIBLE); mHeaderIcon.setImageResource(iconRes); @@ -371,10 +368,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mAppResourceIcon.setVisibility(View.GONE); } else if (appSourceIcon != null) { Icon appIcon = appSourceIcon.toIcon(mContext); - mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent()); + mAppResourceIcon.setColorFilter(mMediaSwitchingController.getColorItemContent()); mAppResourceIcon.setImageIcon(appIcon); } else { - Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage(); + Drawable appIconDrawable = mMediaSwitchingController.getAppSourceIconFromPackage(); if (appIconDrawable != null) { mAppResourceIcon.setImageDrawable(appIconDrawable); } else { @@ -387,7 +384,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements R.dimen.media_output_dialog_header_icon_padding); mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); } - mAppButton.setText(mMediaOutputController.getAppSourceName()); + mAppButton.setText(mMediaSwitchingController.getAppSourceName()); if (!mIncludePlaybackAndAppMetadata) { mHeaderTitle.setVisibility(View.GONE); @@ -424,23 +421,26 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mAdapter.updateItems(); } } else { - mMediaOutputController.setRefreshing(false); - mMediaOutputController.refreshDataSetIfNeeded(); + mMediaSwitchingController.setRefreshing(false); + mMediaSwitchingController.refreshDataSetIfNeeded(); } } private void updateButtonBackgroundColorFilter() { - ColorFilter buttonColorFilter = new PorterDuffColorFilter( - mMediaOutputController.getColorButtonBackground(), - PorterDuff.Mode.SRC_IN); + ColorFilter buttonColorFilter = + new PorterDuffColorFilter( + mMediaSwitchingController.getColorButtonBackground(), + PorterDuff.Mode.SRC_IN); mDoneButton.getBackground().setColorFilter(buttonColorFilter); mStopButton.getBackground().setColorFilter(buttonColorFilter); - mDoneButton.setTextColor(mMediaOutputController.getColorPositiveButtonText()); + mDoneButton.setTextColor(mMediaSwitchingController.getColorPositiveButtonText()); } private void updateDialogBackgroundColor() { - getDialogView().getBackground().setTint(mMediaOutputController.getColorDialogBackground()); - mDeviceListLayout.setBackgroundColor(mMediaOutputController.getColorDialogBackground()); + getDialogView() + .getBackground() + .setTint(mMediaSwitchingController.getColorDialogBackground()); + mDeviceListLayout.setBackgroundColor(mMediaSwitchingController.getColorDialogBackground()); } private Drawable resizeDrawable(Drawable drawable, int size) { @@ -499,7 +499,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements protected void startLeBroadcast() { mStopButton.setText(R.string.media_output_broadcast_starting); mStopButton.setEnabled(false); - if (!mMediaOutputController.startBluetoothLeBroadcast()) { + if (!mMediaSwitchingController.startBluetoothLeBroadcast()) { // If the system can't execute "broadcast start", then UI shows the error. handleLeBroadcastStartFailed(); } @@ -512,9 +512,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements && sharedPref.getBoolean(PREF_IS_LE_BROADCAST_FIRST_LAUNCH, true)) { Log.d(TAG, "PREF_IS_LE_BROADCAST_FIRST_LAUNCH: true"); - mMediaOutputController.launchLeBroadcastNotifyDialog(mDialogView, + mMediaSwitchingController.launchLeBroadcastNotifyDialog( + mDialogView, mBroadcastSender, - MediaOutputController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH, + MediaSwitchingController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH, (d, w) -> { startLeBroadcast(); }); @@ -527,14 +528,13 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } protected void startLeBroadcastDialog() { - mMediaOutputController.launchMediaOutputBroadcastDialog(mDialogView, - mBroadcastSender); + mMediaSwitchingController.launchMediaOutputBroadcastDialog(mDialogView, mBroadcastSender); refresh(); } protected void stopLeBroadcast() { mStopButton.setEnabled(false); - if (!mMediaOutputController.stopBluetoothLeBroadcast()) { + if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) { // If the system can't execute "broadcast stop", then UI does refresh. mMainThreadHandler.post(() -> refresh()); } @@ -559,7 +559,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } public void onStopButtonClick() { - mMediaOutputController.releaseSession(); + mMediaSwitchingController.releaseSession(); dismiss(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index 1e317554859c756148f34aa324769e7dcf5e0301..9b5b872a00db75747e1fa8e639e37635e1727280 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -235,14 +235,17 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } }; - MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar, - BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { + MediaOutputBroadcastDialog( + Context context, + boolean aboveStatusbar, + BroadcastSender broadcastSender, + MediaSwitchingController mediaSwitchingController) { super( context, broadcastSender, - mediaOutputController, /* includePlaybackAndAppMetadata */ + mediaSwitchingController, /* includePlaybackAndAppMetadata */ true); - mAdapter = new MediaOutputAdapter(mMediaOutputController); + mAdapter = new MediaOutputAdapter(mMediaSwitchingController); // TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class // that extends MediaOutputBaseDialog if (!aboveStatusbar) { @@ -262,8 +265,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { super.start(); if (!mIsLeBroadcastAssistantCallbackRegistered) { mIsLeBroadcastAssistantCallbackRegistered = true; - mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor, - mBroadcastAssistantCallback); + mMediaSwitchingController.registerLeBroadcastAssistantServiceCallback( + mExecutor, mBroadcastAssistantCallback); } /* Add local source broadcast to connected capable devices that may be possible receivers * of stream. @@ -276,7 +279,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { super.stop(); if (mIsLeBroadcastAssistantCallbackRegistered) { mIsLeBroadcastAssistantCallbackRegistered = false; - mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback( + mMediaSwitchingController.unregisterLeBroadcastAssistantServiceCallback( mBroadcastAssistantCallback); } } @@ -288,7 +291,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override IconCompat getHeaderIcon() { - return mMediaOutputController.getHeaderIcon(); + return mMediaSwitchingController.getHeaderIcon(); } @Override @@ -299,17 +302,17 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override CharSequence getHeaderText() { - return mMediaOutputController.getHeaderTitle(); + return mMediaSwitchingController.getHeaderTitle(); } @Override CharSequence getHeaderSubtitle() { - return mMediaOutputController.getHeaderSubTitle(); + return mMediaSwitchingController.getHeaderSubTitle(); } @Override IconCompat getAppSourceIcon() { - return mMediaOutputController.getNotificationSmallIcon(); + return mMediaSwitchingController.getNotificationSmallIcon(); } @Override @@ -319,16 +322,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void onStopButtonClick() { - mMediaOutputController.stopBluetoothLeBroadcast(); + mMediaSwitchingController.stopBluetoothLeBroadcast(); dismiss(); } private String getBroadcastMetadataInfo(int metadata) { switch (metadata) { case METADATA_BROADCAST_NAME: - return mMediaOutputController.getBroadcastName(); + return mMediaSwitchingController.getBroadcastName(); case METADATA_BROADCAST_CODE: - return mMediaOutputController.getBroadcastCode(); + return mMediaSwitchingController.getBroadcastCode(); default: return ""; } @@ -342,13 +345,15 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { mBroadcastQrCodeView = getDialogView().requireViewById(R.id.qrcode_view); mBroadcastNotify = getDialogView().requireViewById(R.id.broadcast_info); - mBroadcastNotify.setOnClickListener(v -> { - mMediaOutputController.launchLeBroadcastNotifyDialog( - /* view= */ null, - /* broadcastSender= */ null, - MediaOutputController.BroadcastNotifyDialog.ACTION_BROADCAST_INFO_ICON, - /* onClickListener= */ null); - }); + mBroadcastNotify.setOnClickListener( + v -> { + mMediaSwitchingController.launchLeBroadcastNotifyDialog( + /* mediaOutputDialog= */ null, + /* broadcastSender= */ null, + MediaSwitchingController.BroadcastNotifyDialog + .ACTION_BROADCAST_INFO_ICON, + /* listener= */ null); + }); mBroadcastName = getDialogView().requireViewById(R.id.broadcast_name_summary); mBroadcastNameEdit = getDialogView().requireViewById(R.id.broadcast_name_edit); mBroadcastNameEdit.setOnClickListener(v -> { @@ -409,16 +414,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { return; } - for (BluetoothDevice sink : mMediaOutputController.getConnectedBroadcastSinkDevices()) { + for (BluetoothDevice sink : mMediaSwitchingController.getConnectedBroadcastSinkDevices()) { Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId() + ", the device: " + sink.getAnonymizedAddress()); - if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) { + if (mMediaSwitchingController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) { Log.d(TAG, "The sink device has the broadcast source now."); return; } - if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink, - broadcastMetadata, /*isGroupOp=*/ false)) { + if (!mMediaSwitchingController.addSourceIntoSinkDeviceWithBluetoothLeAssistant( + sink, broadcastMetadata, /* isGroupOp= */ false)) { Log.e(TAG, "Error: Source add failed"); } } @@ -457,11 +462,11 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } private String getLocalBroadcastMetadataQrCodeString() { - return mMediaOutputController.getLocalBroadcastMetadataQrCodeString(); + return mMediaSwitchingController.getLocalBroadcastMetadataQrCodeString(); } private BluetoothLeBroadcastMetadata getBroadcastMetadata() { - return mMediaOutputController.getBroadcastMetadata(); + return mMediaSwitchingController.getBroadcastMetadata(); } @VisibleForTesting @@ -476,8 +481,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { * stopped then used the new Broadcast code to start the Broadcast. */ mIsStopbyUpdateBroadcastCode = true; - mMediaOutputController.setBroadcastCode(updatedString); - if (!mMediaOutputController.stopBluetoothLeBroadcast()) { + mMediaSwitchingController.setBroadcastCode(updatedString); + if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) { handleLeBroadcastStopFailed(); return; } @@ -485,8 +490,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { /* If the user wants to update the Broadcast Name, we don't need to stop the Broadcast * session. Only use the new Broadcast name to update the broadcast session. */ - mMediaOutputController.setBroadcastName(updatedString); - if (!mMediaOutputController.updateBluetoothLeBroadcast()) { + mMediaSwitchingController.setBroadcastName(updatedString); + if (!mMediaSwitchingController.updateBluetoothLeBroadcast()) { handleLeBroadcastUpdateFailed(); } } @@ -496,12 +501,13 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { public boolean isBroadcastSupported() { if (!legacyLeAudioSharing()) return false; boolean isBluetoothLeDevice = false; - if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) { - isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice( - mMediaOutputController.getCurrentConnectedMediaDevice()); + if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) { + isBluetoothLeDevice = + mMediaSwitchingController.isBluetoothLeDevice( + mMediaSwitchingController.getCurrentConnectedMediaDevice()); } - return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice; + return mMediaSwitchingController.isBroadcastSupported() && isBluetoothLeDevice; } @Override @@ -515,7 +521,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void handleLeBroadcastStartFailed() { - mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode); + mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode); mRetryCount++; handleUpdateFailedUi(); @@ -538,8 +544,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void handleLeBroadcastUpdateFailed() { - //Change the value in shared preferences back to it original value - mMediaOutputController.setBroadcastName(mCurrentBroadcastName); + // Change the value in shared preferences back to it original value + mMediaSwitchingController.setBroadcastName(mCurrentBroadcastName); mRetryCount++; handleUpdateFailedUi(); @@ -550,7 +556,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { if (mIsStopbyUpdateBroadcastCode) { mIsStopbyUpdateBroadcastCode = false; mRetryCount = 0; - if (!mMediaOutputController.startBluetoothLeBroadcast()) { + if (!mMediaSwitchingController.startBluetoothLeBroadcast()) { handleLeBroadcastStartFailed(); return; } @@ -561,8 +567,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { @Override public void handleLeBroadcastStopFailed() { - //Change the value in shared preferences back to it original value - mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode); + // Change the value in shared preferences back to it original value + mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode); mRetryCount++; handleUpdateFailedUi(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt index 6ef9ea36882b359a028705c6ee7bda62f5bd5c66..2e7e66f5b384ffa331c9266132f90d68d087e31b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt @@ -29,7 +29,7 @@ constructor( private val context: Context, private val broadcastSender: BroadcastSender, private val dialogTransitionAnimator: DialogTransitionAnimator, - private val mediaOutputControllerFactory: MediaOutputController.Factory + private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory ) { var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null @@ -41,7 +41,7 @@ constructor( // TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to // disambiguate the same package running on different users. val controller = - mediaOutputControllerFactory.create( + mediaSwitchingControllerFactory.create( packageName, /* userHandle= */ null, /* token */ null, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index eb6a32023eb79213b0a2445df6a640ad3eb381a1..c9af7b322811242f754842ab7e0c986a09070543 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -46,14 +46,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController, + MediaSwitchingController mediaSwitchingController, DialogTransitionAnimator dialogTransitionAnimator, UiEventLogger uiEventLogger, boolean includePlaybackAndAppMetadata) { - super(context, broadcastSender, mediaOutputController, includePlaybackAndAppMetadata); + super(context, broadcastSender, mediaSwitchingController, includePlaybackAndAppMetadata); mDialogTransitionAnimator = dialogTransitionAnimator; mUiEventLogger = uiEventLogger; - mAdapter = new MediaOutputAdapter(mMediaOutputController); + mAdapter = new MediaOutputAdapter(mMediaSwitchingController); if (!aboveStatusbar) { getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } @@ -72,7 +72,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override IconCompat getHeaderIcon() { - return mMediaOutputController.getHeaderIcon(); + return mMediaSwitchingController.getHeaderIcon(); } @Override @@ -83,27 +83,29 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override CharSequence getHeaderText() { - return mMediaOutputController.getHeaderTitle(); + return mMediaSwitchingController.getHeaderTitle(); } @Override CharSequence getHeaderSubtitle() { - return mMediaOutputController.getHeaderSubTitle(); + return mMediaSwitchingController.getHeaderSubTitle(); } @Override IconCompat getAppSourceIcon() { - return mMediaOutputController.getNotificationSmallIcon(); + return mMediaSwitchingController.getNotificationSmallIcon(); } @Override int getStopButtonVisibility() { boolean isActiveRemoteDevice = false; - if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) { - isActiveRemoteDevice = mMediaOutputController.isActiveRemoteDevice( - mMediaOutputController.getCurrentConnectedMediaDevice()); + if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) { + isActiveRemoteDevice = + mMediaSwitchingController.isActiveRemoteDevice( + mMediaSwitchingController.getCurrentConnectedMediaDevice()); } - boolean showBroadcastButton = isBroadcastSupported() && mMediaOutputController.isPlaying(); + boolean showBroadcastButton = + isBroadcastSupported() && mMediaSwitchingController.isPlaying(); return (isActiveRemoteDevice || showBroadcastButton) ? View.VISIBLE : View.GONE; } @@ -115,13 +117,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { boolean isBroadcastEnabled = false; if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) { - if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) { - isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice( - mMediaOutputController.getCurrentConnectedMediaDevice()); + if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) { + isBluetoothLeDevice = + mMediaSwitchingController.isBluetoothLeDevice( + mMediaSwitchingController.getCurrentConnectedMediaDevice()); // if broadcast is active, broadcast should be considered as supported // there could be a valid case that broadcast is ongoing // without active LEA device connected - isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled(); + isBroadcastEnabled = mMediaSwitchingController.isBluetoothLeBroadcastEnabled(); } } else { // To decouple LE Audio Broadcast and Unicast, it always displays the button when there @@ -129,15 +132,16 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { isBluetoothLeDevice = true; } - return mMediaOutputController.isBroadcastSupported() + return mMediaSwitchingController.isBroadcastSupported() && (isBluetoothLeDevice || isBroadcastEnabled); } @Override public CharSequence getStopButtonText() { int resId = R.string.media_output_dialog_button_stop_casting; - if (isBroadcastSupported() && mMediaOutputController.isPlaying() - && !mMediaOutputController.isBluetoothLeBroadcastEnabled()) { + if (isBroadcastSupported() + && mMediaSwitchingController.isPlaying() + && !mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) { resId = R.string.media_output_broadcast; } return mContext.getText(resId); @@ -145,8 +149,8 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override public void onStopButtonClick() { - if (isBroadcastSupported() && mMediaOutputController.isPlaying()) { - if (!mMediaOutputController.isBluetoothLeBroadcastEnabled()) { + if (isBroadcastSupported() && mMediaSwitchingController.isPlaying()) { + if (!mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) { if (startLeBroadcastDialogForFirstTime()) { return; } @@ -155,7 +159,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { stopLeBroadcast(); } } else { - mMediaOutputController.releaseSession(); + mMediaSwitchingController.releaseSession(); mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations(); dismiss(); } @@ -163,8 +167,9 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override public int getBroadcastIconVisibility() { - return (isBroadcastSupported() && mMediaOutputController.isBluetoothLeBroadcastEnabled()) - ? View.VISIBLE : View.GONE; + return (isBroadcastSupported() && mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) + ? View.VISIBLE + : View.GONE; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt index 47e069102035b464464fae4bcfe57c94d37a1eb1..4e9451a838ade330c59250317a074367746e9a12 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt @@ -35,7 +35,7 @@ constructor( private val broadcastSender: BroadcastSender, private val uiEventLogger: UiEventLogger, private val dialogTransitionAnimator: DialogTransitionAnimator, - private val mediaOutputControllerFactory: MediaOutputController.Factory, + private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory, ) { companion object { const val INTERACTION_JANK_TAG = "media_output" @@ -118,7 +118,7 @@ constructor( // Dismiss the previous dialog, if any. mediaOutputDialog?.dismiss() - val controller = mediaOutputControllerFactory.create(packageName, userHandle, token) + val controller = mediaSwitchingControllerFactory.create(packageName, userHandle, token) val mediaOutputDialog = MediaOutputDialog( diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java similarity index 92% rename from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java rename to packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java index 875e505db1c6fb8aeb94c4829b6e548d0c4f5a14..f7b73534d35cdf14f2a4ee3b098d0ae6a3f7ef55 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -77,6 +77,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.InfoMediaManager; +import com.android.settingslib.media.InputRouteManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.flags.Flags; @@ -116,12 +117,13 @@ import java.util.function.Function; import java.util.stream.Collectors; /** - * Controller for media output dialog + * Controller for a dialog that allows users to switch media output and input devices, control + * volume, connect to new devices, etc. */ -public class MediaOutputController implements LocalMediaManager.DeviceCallback, - INearbyMediaDevicesUpdateCallback { +public class MediaSwitchingController + implements LocalMediaManager.DeviceCallback, INearbyMediaDevicesUpdateCallback { - private static final String TAG = "MediaOutputController"; + private static final String TAG = "MediaSwitchingController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String PAGE_CONNECTED_DEVICES_KEY = "top_level_connected_devices"; @@ -137,10 +139,12 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private final DialogTransitionAnimator mDialogTransitionAnimator; private final CommonNotifCollection mNotifCollection; protected final Object mMediaDevicesLock = new Object(); + protected final Object mInputMediaDevicesLock = new Object(); @VisibleForTesting final List mGroupMediaDevices = new CopyOnWriteArrayList<>(); final List mCachedMediaDevices = new CopyOnWriteArrayList<>(); - private final List mMediaItemList = new CopyOnWriteArrayList<>(); + private final List mOutputMediaItemList = new CopyOnWriteArrayList<>(); + private final List mInputMediaItemList = new CopyOnWriteArrayList<>(); private final AudioManager mAudioManager; private final PowerExemptionManager mPowerExemptionManager; private final KeyguardManager mKeyGuardManager; @@ -153,6 +157,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @VisibleForTesting boolean mNeedRefresh = false; private MediaController mMediaController; + @VisibleForTesting InputRouteManager mInputRouteManager; @VisibleForTesting Callback mCallback; @VisibleForTesting @@ -181,8 +186,20 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, ACTION_BROADCAST_INFO_ICON } + @VisibleForTesting + final InputRouteManager.InputDeviceCallback mInputDeviceCallback = + new InputRouteManager.InputDeviceCallback() { + @Override + public void onInputDeviceListUpdated(@NonNull List devices) { + synchronized (mInputMediaDevicesLock) { + buildInputMediaItems(devices); + mCallback.onDeviceListChanged(); + } + } + }; + @AssistedInject - public MediaOutputController( + public MediaSwitchingController( Context context, @Assisted String packageName, @Assisted @Nullable UserHandle userHandle, @@ -241,19 +258,23 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, R.dimen.media_output_dialog_default_margin_end); mItemMarginEndSelectable = (int) mContext.getResources().getDimension( R.dimen.media_output_dialog_selectable_margin_end); + + if (enableInputRouting()) { + mInputRouteManager = new InputRouteManager(mContext, audioManager); + } } @AssistedFactory public interface Factory { - /** Construct a MediaOutputController */ - MediaOutputController create( + /** Construct a MediaSwitchingController */ + MediaSwitchingController create( String packageName, UserHandle userHandle, MediaSession.Token token); } protected void start(@NonNull Callback cb) { synchronized (mMediaDevicesLock) { mCachedMediaDevices.clear(); - mMediaItemList.clear(); + mOutputMediaItemList.clear(); } mNearbyDeviceInfoMap.clear(); if (mNearbyMediaDevicesManager != null) { @@ -277,6 +298,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mCallback = cb; mLocalMediaManager.registerCallback(this); mLocalMediaManager.startScan(); + + if (enableInputRouting()) { + mInputRouteManager.registerCallback(mInputDeviceCallback); + } } boolean shouldShowLaunchSection() { @@ -300,12 +325,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mLocalMediaManager.stopScan(); synchronized (mMediaDevicesLock) { mCachedMediaDevices.clear(); - mMediaItemList.clear(); + mOutputMediaItemList.clear(); } if (mNearbyMediaDevicesManager != null) { mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this); } mNearbyDeviceInfoMap.clear(); + + if (enableInputRouting()) { + mInputRouteManager.unregisterCallback(mInputDeviceCallback); + synchronized (mInputMediaDevicesLock) { + mInputMediaItemList.clear(); + } + } } private MediaController getMediaController() { @@ -335,7 +367,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Override public void onDeviceListUpdate(List devices) { - boolean isListEmpty = mMediaItemList.isEmpty(); + boolean isListEmpty = mOutputMediaItemList.isEmpty(); if (isListEmpty || !mIsRefreshing) { buildMediaItems(devices); mCallback.onDeviceListChanged(); @@ -352,7 +384,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, public void onSelectedDeviceStateChanged(MediaDevice device, @LocalMediaManager.MediaDeviceState int state) { mCallback.onRouteChanged(); - mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList)); + mMetricLogger.logOutputItemSuccess( + device.toString(), new ArrayList<>(mOutputMediaItemList)); } @Override @@ -363,7 +396,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Override public void onRequestFailed(int reason) { mCallback.onRouteChanged(); - mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason); + mMetricLogger.logOutputItemFailure(new ArrayList<>(mOutputMediaItemList), reason); } /** @@ -382,7 +415,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } try { synchronized (mMediaDevicesLock) { - mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); + mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); } mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice()); } catch (Exception e) { @@ -638,9 +671,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private void buildMediaItems(List devices) { synchronized (mMediaDevicesLock) { - List updatedMediaItems = buildMediaItems(mMediaItemList, devices); - mMediaItemList.clear(); - mMediaItemList.addAll(updatedMediaItems); + List updatedMediaItems = buildMediaItems(mOutputMediaItemList, devices); + mOutputMediaItemList.clear(); + mOutputMediaItemList.addAll(updatedMediaItems); } } @@ -714,6 +747,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } } + private boolean enableInputRouting() { + return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl(); + } + + private void buildInputMediaItems(List devices) { + synchronized (mInputMediaDevicesLock) { + List updatedInputMediaItems = + devices.stream().map(MediaItem::createDeviceMediaItem).toList(); + mInputMediaItemList.clear(); + mInputMediaItemList.addAll(updatedInputMediaItems); + } + } + /** * Initial categorization of current devices, will not be called for updates to the devices * list. @@ -778,7 +824,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId())); } } - } boolean isCurrentConnectedDeviceRemote() { @@ -837,8 +882,31 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, }); } + private void addInputDevices(List mediaItems) { + mediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString(R.string.media_input_group_title))); + mediaItems.addAll(mInputMediaItemList); + } + + private void addOutputDevices(List mediaItems) { + mediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString(R.string.media_output_group_title))); + mediaItems.addAll(mOutputMediaItemList); + } + public List getMediaItemList() { - return mMediaItemList; + // If input routing is not enabled, only return output media items. + if (!enableInputRouting()) { + return mOutputMediaItemList; + } + + // If input routing is enabled, return both output and input media items. + List mediaItems = new ArrayList<>(); + addOutputDevices(mediaItems); + addInputDevices(mediaItems); + return mediaItems; } public MediaDevice getCurrentConnectedMediaDevice() { @@ -859,7 +927,18 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } public List getSelectedMediaDevice() { - return mLocalMediaManager.getSelectedMediaDevice(); + if (!enableInputRouting()) { + return mLocalMediaManager.getSelectedMediaDevice(); + } + + // Add selected input device if input routing is supported. + List selectedDevices = + new ArrayList<>(mLocalMediaManager.getSelectedMediaDevice()); + MediaDevice selectedInputDevice = mInputRouteManager.getSelectedInputDevice(); + if (selectedInputDevice != null) { + selectedDevices.add(selectedInputDevice); + } + return selectedDevices; } List getDeselectableMediaDevice() { @@ -921,7 +1000,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, public boolean isAnyDeviceTransferring() { synchronized (mMediaDevicesLock) { - for (MediaItem mediaItem : mMediaItemList) { + for (MediaItem mediaItem : mOutputMediaItemList) { if (mediaItem.getMediaDevice().isPresent() && mediaItem.getMediaDevice().get().getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { @@ -986,8 +1065,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) { - MediaOutputController controller = - new MediaOutputController( + MediaSwitchingController controller = + new MediaSwitchingController( mContext, mPackageName, mUserHandle, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 64402052c9846817ead677385f8b5fcf9623a6a4..544dbddeb3f0c18cb7b3a9694b382ec6c56a6200 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.mediaprojection.appselector +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.app.Activity import android.content.ComponentName import android.content.Context @@ -133,7 +134,7 @@ interface MediaProjectionAppSelectorModule { @MediaProjectionAppSelector @MediaProjectionAppSelectorScope fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob()) + CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("MediaProjectionAppSelectorScope")) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index d59658947771501ce7a33a032c41a63873905251..8351597f35defe4fcd9f21567248ccb41876f804 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -34,6 +34,7 @@ import android.annotation.RequiresPermission; import android.app.Activity; import android.app.ActivityOptions.LaunchCookie; import android.app.AlertDialog; +import android.app.KeyguardManager; import android.app.StatusBarManager; import android.app.compat.CompatChanges; import android.content.Context; @@ -53,7 +54,6 @@ import android.text.TextPaint; import android.text.TextUtils; import android.util.Log; import android.view.Window; -import android.view.WindowManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -84,6 +84,7 @@ public class MediaProjectionPermissionActivity extends Activity { private final StatusBarManager mStatusBarManager; private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; + private final KeyguardManager mKeyguardManager; private String mPackageName; private int mUid; @@ -102,11 +103,13 @@ public class MediaProjectionPermissionActivity extends Activity { FeatureFlags featureFlags, Lazy screenCaptureDevicePolicyResolver, StatusBarManager statusBarManager, + KeyguardManager keyguardManager, MediaProjectionMetricsLogger mediaProjectionMetricsLogger, ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) { mFeatureFlags = featureFlags; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; mStatusBarManager = statusBarManager; + mKeyguardManager = keyguardManager; mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate; } @@ -209,7 +212,14 @@ public class MediaProjectionPermissionActivity extends Activity { } setUpDialog(mDialog); - mDialog.show(); + + boolean shouldDismissKeyguard = + com.android.systemui.Flags.mediaProjectionDialogBehindLockscreen(); + if (shouldDismissKeyguard && mKeyguardManager.isDeviceLocked()) { + requestDeviceUnlock(); + } else { + mDialog.show(); + } if (savedInstanceState == null) { mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid); @@ -309,9 +319,6 @@ public class MediaProjectionPermissionActivity extends Activity { private void setUpDialog(AlertDialog dialog) { SystemUIDialog.registerDismissListener(dialog); SystemUIDialog.applyFlags(dialog, /* showWhenLocked= */ false); - - final Window w = dialog.getWindow(); - w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); SystemUIDialog.setDialogSize(dialog); dialog.setOnCancelListener(this::onDialogDismissedOrCancelled); @@ -319,6 +326,7 @@ public class MediaProjectionPermissionActivity extends Activity { dialog.create(); dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); + final Window w = dialog.getWindow(); w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); } @@ -335,6 +343,16 @@ public class MediaProjectionPermissionActivity extends Activity { return false; } + private void requestDeviceUnlock() { + mKeyguardManager.requestDismissKeyguard(this, + new KeyguardManager.KeyguardDismissCallback() { + @Override + public void onDismissSucceeded() { + mDialog.show(); + } + }); + } + private void grantMediaProjectionPermission( int screenShareMode, boolean hasCastingCapabilities) { try { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt similarity index 65% rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt rename to packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt index 601fbfaf1b64e2a057b2669c0f554bbeab5f5f28..a0663d72a076ef60edd3cc3cdf3858e75211834c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt +++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt @@ -14,26 +14,18 @@ * limitations under the License. */ -package com.android.systemui.keyguard.shared +package com.android.systemui.modes.shared -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken +import android.app.Flags import com.android.systemui.flags.RefactorFlagUtils -/** Helper for reading or using the compose lockscreen flag state. */ +/** Helper for reading or using the modes ui flag state. */ @Suppress("NOTHING_TO_INLINE") -object ComposeLockscreen { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - +object ModesUi { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.composeLockscreen() + get() = Flags.modesApi() && Flags.modesUi() /** * Called to ensure code is only run when the flag is enabled. This protects users from the @@ -42,12 +34,21 @@ object ComposeLockscreen { */ @JvmStatic inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, Flags.FLAG_MODES_UI) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + * Caution!! Using this check incorrectly will cause crashes in nextfood builds! + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) + inline fun assertInLegacyMode() = + RefactorFlagUtils.assertInLegacyMode(isEnabled, Flags.FLAG_MODES_UI) } diff --git a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt new file mode 100644 index 0000000000000000000000000000000000000000..032b0aca177003af24b6c00a4c9c0e51f78d636f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.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.systemui.modes.shared + +import android.app.Flags +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the modes ui icons flag state. */ +@Suppress("NOTHING_TO_INLINE") +object ModesUiIcons { + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = ModesUi.isEnabled && Flags.modesUiIcons() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, Flags.FLAG_MODES_UI_ICONS) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + * Caution!! Using this check incorrectly will cause crashes in nextfood builds! + */ + @JvmStatic + inline fun assertInNewMode() = + RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI_ICONS) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = + RefactorFlagUtils.assertInLegacyMode(isEnabled, Flags.FLAG_MODES_UI_ICONS) +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 54a59f30c8fdefb762ae45c1f6ec4d26fb931fc5..eff5fc0db761adc3af0669d4fd3a7a6e69501a0e 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -19,11 +19,15 @@ import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager import android.content.Context import android.content.pm.UserInfo +import android.hardware.input.InputManager +import android.hardware.input.KeyGestureEvent +import android.os.IBinder import android.os.UserHandle import android.view.KeyEvent import android.view.KeyEvent.KEYCODE_N import android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL import android.view.ViewConfiguration +import com.android.hardware.input.Flags.useKeyGestureEventHandler import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.dagger.qualifiers.Background @@ -47,6 +51,7 @@ constructor( private val optionalBubbles: Optional, private val userTracker: UserTracker, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val inputManager: InputManager, @Background private val backgroundExecutor: Executor, @NoteTaskEnabledKey private val isEnabled: Boolean, ) { @@ -59,6 +64,7 @@ constructor( if (!isEnabled || optionalBubbles.isEmpty) return initializeHandleSystemKey() + initializeKeyGestureEventHandler() initializeOnRoleHoldersChanged() initializeOnUserUnlocked() initializeUserTracker() @@ -69,7 +75,19 @@ constructor( * [NoteTaskController], ensure custom actions can be triggered (i.e., keyboard shortcut). */ private fun initializeHandleSystemKey() { - commandQueue.addCallback(callbacks) + if (!useKeyGestureEventHandler()) { + commandQueue.addCallback(callbacks) + } + } + + /** + * Initializes a [InputManager.KeyGestureEventHandler] which will handle shortcuts for opening + * the notes role via [NoteTaskController]. + */ + private fun initializeKeyGestureEventHandler() { + if (useKeyGestureEventHandler()) { + inputManager.registerKeyGestureEventHandler(callbacks) + } } /** @@ -110,9 +128,15 @@ constructor( KeyguardUpdateMonitorCallback(), CommandQueue.Callbacks, UserTracker.Callback, - OnRoleHoldersChangedListener { + OnRoleHoldersChangedListener, + InputManager.KeyGestureEventHandler { override fun handleSystemKey(key: KeyEvent) { + if (useKeyGestureEventHandler()) { + throw IllegalStateException( + "handleSystemKey must not be used when KeyGestureEventHandler is used" + ) + } key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask) } @@ -131,6 +155,17 @@ constructor( override fun onProfilesChanged(profiles: List) { controller.updateNoteTaskForCurrentUserAndManagedProfiles() } + + override fun handleKeyGestureEvent( + event: KeyGestureEvent, + focusedToken: IBinder?, + ): Boolean { + return this@NoteTaskInitializer.handleKeyGestureEvent(event) + } + + override fun isKeyGestureSupported(gestureType: Int): Boolean { + return this@NoteTaskInitializer.isKeyGestureSupported(gestureType) + } } /** @@ -171,6 +206,36 @@ constructor( return !isMultiPress && !isLongPress } + private fun handleKeyGestureEvent(event: KeyGestureEvent): Boolean { + // This method is on input hot path and should be kept lightweight. Shift all complex + // processing onto background executor wherever possible. + if (event.keyGestureType != KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) { + return false + } + debugLog { + "handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " + + event.keycodes.contentToString() + } + if ( + event.keycodes.contains(KEYCODE_N) && + event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON) + ) { + debugLog { "Note task triggered by keyboard shortcut" } + backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) } + return true + } + if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) { + debugLog { "Note task triggered by stylus tail button" } + backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) } + return true + } + return false + } + + private fun isKeyGestureSupported(gestureType: Int): Boolean { + return gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES + } + companion object { val MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout().toLong() val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong() diff --git a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING index 66f020f24e805fc3ed21f855ab4da835e9ef5528..75140bee3cdd555eab63e5e081ada61af0d33c71 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING +++ b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING @@ -1,28 +1,12 @@ { "presubmit": [ { - "name": "CtsTileServiceTestCases", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTileServiceTestCases" } ], "postsubmit": [ { - "name": "QuickSettingsDeviceResetTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "QuickSettingsDeviceResetTests" } ] } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index c2f1c3dcd4260cb43e8ff59fec93b2ae3ecf60ff..c174038aafe4d0892606ba97f8e72c549ef71568 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -26,7 +26,6 @@ import android.view.ViewGroup import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner -import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -38,10 +37,14 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot @@ -51,11 +54,18 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.android.compose.animation.scene.MutableSceneTransitionLayoutState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.height import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf @@ -70,11 +80,17 @@ import com.android.systemui.media.dagger.MediaModule.QS_PANEL import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.plugins.qs.QS import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings +import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings +import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey import com.android.systemui.qs.composefragment.ui.notificationScrimClip +import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings +import com.android.systemui.qs.shared.ui.ElementKeys +import com.android.systemui.qs.ui.composable.QuickSettingsShade import com.android.systemui.qs.ui.composable.QuickSettingsTheme import com.android.systemui.qs.ui.composable.ShadeBody import com.android.systemui.res.R @@ -86,11 +102,13 @@ import java.io.PrintWriter import java.util.function.Consumer import javax.inject.Inject import javax.inject.Named +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @SuppressLint("ValidFragment") @@ -158,7 +176,7 @@ constructor( override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { val context = inflater.context return ComposeView(context).apply { @@ -166,33 +184,48 @@ constructor( setContent { PlatformTheme { val visible by viewModel.qsVisible.collectAsStateWithLifecycle() - val qsState by viewModel.expansionState.collectAsStateWithLifecycle() AnimatedVisibility( visible = visible, modifier = - Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf( - notificationScrimClippingParams.isEnabled - ) { - Modifier.notificationScrimClip( - notificationScrimClippingParams.leftInset, - notificationScrimClippingParams.top, - notificationScrimClippingParams.rightInset, - notificationScrimClippingParams.bottom, - notificationScrimClippingParams.radius, - ) - } - ) { - AnimatedContent(targetState = qsState) { - when (it) { - QSFragmentComposeViewModel.QSExpansionState.QQS -> { - QuickQuickSettingsElement() - } - QSFragmentComposeViewModel.QSExpansionState.QS -> { - QuickSettingsElement() + Modifier.windowInsetsPadding(WindowInsets.navigationBars) + .thenIf(notificationScrimClippingParams.isEnabled) { + Modifier.notificationScrimClip( + notificationScrimClippingParams.leftInset, + notificationScrimClippingParams.top, + notificationScrimClippingParams.rightInset, + notificationScrimClippingParams.bottom, + notificationScrimClippingParams.radius, + ) } - else -> {} - } + .graphicsLayer { elevation = 4.dp.toPx() }, + ) { + val sceneState = remember { + MutableSceneTransitionLayoutState( + viewModel.expansionState.value.toIdleSceneKey(), + transitions = + transitions { + from(QuickQuickSettings, QuickSettings) { + quickQuickSettingsToQuickSettings() + } + }, + ) + } + + LaunchedEffect(Unit) { + synchronizeQsState( + sceneState, + viewModel.expansionState.map { it.progress }, + ) + } + + SceneTransitionLayout( + state = sceneState, + modifier = Modifier.fillMaxSize(), + ) { + scene(QuickSettings) { QuickSettingsElement() } + + scene(QuickQuickSettings) { QuickQuickSettingsElement() } } } } @@ -272,7 +305,7 @@ constructor( qsExpansionFraction: Float, panelExpansionFraction: Float, headerTranslation: Float, - squishinessFraction: Float + squishinessFraction: Float, ) { viewModel.qsExpansionValue = qsExpansionFraction viewModel.panelExpansionFractionValue = panelExpansionFraction @@ -318,12 +351,12 @@ constructor( override fun setTransitionToFullShadeProgress( isTransitioningToFullShade: Boolean, qsTransitionFraction: Float, - qsSquishinessFraction: Float + qsSquishinessFraction: Float, ) { super.setTransitionToFullShadeProgress( isTransitioningToFullShade, qsTransitionFraction, - qsSquishinessFraction + qsSquishinessFraction, ) } @@ -334,7 +367,7 @@ constructor( bottom: Int, cornerRadius: Int, visible: Boolean, - fullWidth: Boolean + fullWidth: Boolean, ) { notificationScrimClippingParams.isEnabled = visible notificationScrimClippingParams.top = top @@ -402,7 +435,7 @@ constructor( launch { setListenerJob( heightListener, - viewModel.containerViewModel.editModeViewModel.isEditing + viewModel.containerViewModel.editModeViewModel.isEditing, ) { onQsHeightChanged() } @@ -410,7 +443,7 @@ constructor( launch { setListenerJob( qsContainerController, - viewModel.containerViewModel.editModeViewModel.isEditing + viewModel.containerViewModel.editModeViewModel.isEditing, ) { setCustomizerShowing(it) } @@ -420,8 +453,9 @@ constructor( } @Composable - private fun QuickQuickSettingsElement() { + private fun SceneScope.QuickQuickSettingsElement() { val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() + val bottomPadding = dimensionResource(id = R.dimen.qqs_layout_padding_bottom) DisposableEffect(Unit) { qqsVisible.value = true @@ -441,7 +475,7 @@ constructor( ) } .onSizeChanged { size -> qqsHeight.value = size.height } - .padding(top = { qqsPadding }) + .padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() }) ) { val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle() if (qsEnabled) { @@ -449,8 +483,15 @@ constructor( viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel, modifier = Modifier.collapseExpandSemanticAction( - stringResource(id = R.string.accessibility_quick_settings_expand) - ) + stringResource( + id = R.string.accessibility_quick_settings_expand + ) + ) + .padding( + horizontal = { + QuickSettingsShade.Dimensions.Padding.roundToPx() + } + ), ) } } @@ -459,7 +500,7 @@ constructor( } @Composable - private fun QuickSettingsElement() { + private fun SceneScope.QuickSettingsElement() { val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top) Column( @@ -470,7 +511,10 @@ constructor( ) { val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle() if (qsEnabled) { - Box(modifier = Modifier.fillMaxSize().weight(1f)) { + Box( + modifier = + Modifier.element(ElementKeys.QuickSettingsContent).fillMaxSize().weight(1f) + ) { Column { Spacer( modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() } @@ -482,7 +526,9 @@ constructor( FooterActions( viewModel = viewModel.footerActionsViewModel, qsVisibilityLifecycleOwner = this@QSFragmentCompose, - modifier = Modifier.sysuiResTag("qs_footer_actions") + modifier = + Modifier.sysuiResTag("qs_footer_actions") + .element(ElementKeys.FooterActions), ) } } @@ -562,7 +608,7 @@ private fun View.setBackPressedDispatcher() { private suspend inline fun setListenerJob( listenerFlow: MutableStateFlow, dataFlow: Flow, - crossinline onCollect: suspend Listener.(Data) -> Unit + crossinline onCollect: suspend Listener.(Data) -> Unit, ) { coroutineScope { try { @@ -589,3 +635,85 @@ private val instanceProvider = return currentId++ } } + +object SceneKeys { + val QuickQuickSettings = SceneKey("QuickQuickSettingsScene") + val QuickSettings = SceneKey("QuickSettingsScene") + + fun QSFragmentComposeViewModel.QSExpansionState.toIdleSceneKey(): SceneKey { + return when { + progress < 0.5f -> QuickQuickSettings + else -> QuickSettings + } + } +} + +suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow) { + coroutineScope { + val animationScope = this + + var currentTransition: ExpansionTransition? = null + + fun snapTo(scene: SceneKey) { + state.snapToScene(scene) + currentTransition = null + } + + expansion.collectLatest { progress -> + when (progress) { + 0f -> snapTo(QuickQuickSettings) + 1f -> snapTo(QuickSettings) + else -> { + val transition = currentTransition + if (transition != null) { + transition.progress = progress + return@collectLatest + } + + val newTransition = + ExpansionTransition(progress).also { currentTransition = it } + state.startTransitionImmediately( + animationScope = animationScope, + transition = newTransition, + ) + } + } + } + } +} + +private class ExpansionTransition(currentProgress: Float) : + TransitionState.Transition.ChangeScene( + fromScene = QuickQuickSettings, + toScene = QuickSettings, + ) { + override val currentScene: SceneKey + get() { + // This should return the logical scene. If the QS STLState is only driven by + // synchronizeQSState() then it probably does not matter which one we return, this is + // only used to compute the current user actions of a STL. + return QuickQuickSettings + } + + override var progress: Float by mutableFloatStateOf(currentProgress) + + override val progressVelocity: Float + get() = 0f + + override val isInitiatedByUserInput: Boolean + get() = true + + override val isUserInputOngoing: Boolean + get() = true + + private val finishCompletable = CompletableDeferred() + + override suspend fun run() { + // This transition runs until it is interrupted by another one. + finishCompletable.await() + } + + override fun freezeAndAnimateToCurrentState() { + finishCompletable.complete(Unit) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt similarity index 57% rename from packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt rename to packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt index 0386a6ab20d6002c9f13c264d5b3dbae52215acb..1514986d16d9995f925ea6896a6aa29ef2ca406a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt @@ -14,14 +14,16 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.domain.interactor +package com.android.systemui.qs.composefragment.ui -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.pipeline.shared.TileSpec -import javax.inject.Inject +import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.qs.shared.ui.ElementKeys -/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */ -@SysUISingleton -class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor { - override fun reconcileTiles(tiles: List): List = tiles +fun TransitionBuilder.quickQuickSettingsToQuickSettings() { + + fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) } + + fractionRange(start = 0.9f) { fade(ElementKeys.FooterActions) } + + anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/GridAnchor.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/GridAnchor.kt new file mode 100644 index 0000000000000000000000000000000000000000..f0f46d33b83d34e009354f553eb47a92a34e63cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/GridAnchor.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.systemui.qs.composefragment.ui + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneScope +import com.android.systemui.qs.shared.ui.ElementKeys + +/** + * This composable is used at the start of the tiles in QQS and QS to anchor the expansion and be + * able to have relative anchor translation of elements that appear in QS. + */ +@Composable +fun SceneScope.GridAnchor(modifier: Modifier = Modifier) { + // The size of this anchor does not matter, as the tiles don't change size on expansion. + Spacer(modifier.element(ElementKeys.GridAnchor)) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index 7ab11d22ee49599eb4c29be8290f9fdbe955dd14..7300ee1053ff3f486b916d68d8c1991447e4eb97 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -147,7 +147,7 @@ constructor( .stateIn( lifecycleScope, SharingStarted.WhileSubscribed(), - disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled() + disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled(), ) private val _showCollapsedOnKeyguard = MutableStateFlow(false) @@ -213,19 +213,11 @@ constructor( } val expansionState: StateFlow = - combine( - _stackScrollerOverscrolling, - _qsExpanded, - _qsExpansion, - ) { args: Array -> + combine(_stackScrollerOverscrolling, _qsExpanded, _qsExpansion) { args: Array -> val expansion = args[2] as Float - if (expansion > 0.5f) { - QSExpansionState.QS - } else { - QSExpansionState.QQS - } + QSExpansionState(expansion.coerceIn(0f, 1f)) } - .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS) + .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState(0f)) /** * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for @@ -262,13 +254,6 @@ constructor( fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel } - sealed interface QSExpansionState { - data object QQS : QSExpansionState - - data object QS : QSExpansionState - - @JvmInline value class Expanding(val progress: Float) : QSExpansionState - - @JvmInline value class Collapsing(val progress: Float) : QSExpansionState - } + // In the future, this will have other relevant elements like squishiness. + data class QSExpansionState(@FloatRange(0.0, 1.0) val progress: Float) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 072d322d69a6e6b557db891ea8bec57fbb119f31..1fe54e46fee1471c66158b813a240f510d2ea800 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -23,17 +23,14 @@ import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositor import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl -import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor -import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor -import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType import com.android.systemui.qs.panels.shared.model.PanelsLog import com.android.systemui.qs.panels.ui.compose.GridLayout -import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout +import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel @@ -56,11 +53,6 @@ interface PanelsModule { @Binds fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository - @Binds - fun bindDefaultGridConsistencyInteractor( - impl: NoopGridConsistencyInteractor - ): GridTypeConsistencyInteractor - @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel @Binds fun bindGridSizeViewModel(impl: FixedColumnsSizeViewModelImpl): FixedColumnsSizeViewModel @@ -74,12 +66,6 @@ interface PanelsModule { @PaginatedBaseLayoutType fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout - @Binds - @PaginatedBaseLayoutType - fun bindPaginatedBaseConsistencyInteractor( - impl: NoopGridConsistencyInteractor - ): GridTypeConsistencyInteractor - @Binds @Named("Default") fun bindDefaultGridLayout(impl: PaginatedGridLayout): GridLayout companion object { @@ -117,28 +103,5 @@ interface PanelsModule { ): Set { return entries.map { it.first }.toSet() } - - @Provides - @IntoSet - fun provideGridConsistencyInteractor( - consistencyInteractor: InfiniteGridConsistencyInteractor - ): Pair { - return Pair(InfiniteGridLayoutType, consistencyInteractor) - } - - @Provides - @IntoSet - fun providePaginatedGridConsistencyInteractor( - @PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor, - ): Pair { - return Pair(PaginatedGridLayoutType, consistencyInteractor) - } - - @Provides - fun provideGridConsistencyInteractorMap( - entries: Set<@JvmSuppressWildcards Pair> - ): Map { - return entries.toMap() - } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt deleted file mode 100644 index a2e7ea6fe797f6d17cb91799273295ef6657fadb..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt +++ /dev/null @@ -1,75 +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.systemui.qs.panels.domain.interactor - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.core.LogLevel -import com.android.systemui.qs.panels.shared.model.GridLayoutType -import com.android.systemui.qs.panels.shared.model.PanelsLog -import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor -import com.android.systemui.qs.pipeline.shared.TileSpec -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -@SysUISingleton -class GridConsistencyInteractor -@Inject -constructor( - private val gridLayoutTypeInteractor: GridLayoutTypeInteractor, - private val currentTilesInteractor: CurrentTilesInteractor, - private val consistencyInteractors: - Map, - private val defaultConsistencyInteractor: GridTypeConsistencyInteractor, - @PanelsLog private val logBuffer: LogBuffer, - @Application private val applicationScope: CoroutineScope, -) { - fun start() { - applicationScope.launch { - gridLayoutTypeInteractor.layout.collectLatest { type -> - val consistencyInteractor = - consistencyInteractors[type] ?: defaultConsistencyInteractor - currentTilesInteractor.currentTiles - .map { tiles -> tiles.map { it.spec } } - .collectLatest { tiles -> - val newTiles = consistencyInteractor.reconcileTiles(tiles) - if (newTiles != tiles) { - currentTilesInteractor.setTiles(newTiles) - logChange(newTiles) - } - } - } - } - } - - private fun logChange(tiles: List) { - logBuffer.log( - LOG_BUFFER_CURRENT_TILES_CHANGE_TAG, - LogLevel.DEBUG, - { str1 = tiles.toString() }, - { "Tiles reordered: $str1" } - ) - } - - private companion object { - const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt deleted file mode 100644 index 874b3b0a4636d80394c6721b2b64ee9446fa3709..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt +++ /dev/null @@ -1,103 +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.systemui.qs.panels.domain.interactor - -import android.util.Log -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.shared.model.SizedTile -import com.android.systemui.qs.panels.shared.model.SizedTileImpl -import com.android.systemui.qs.panels.shared.model.TileRow -import com.android.systemui.qs.pipeline.shared.TileSpec -import javax.inject.Inject - -@SysUISingleton -class InfiniteGridConsistencyInteractor -@Inject -constructor( - private val iconTilesInteractor: IconTilesInteractor, - private val gridSizeInteractor: FixedColumnsSizeInteractor -) : GridTypeConsistencyInteractor { - - /** - * Tries to fill in every columns of all rows (except the last row), potentially reordering - * tiles. - */ - override fun reconcileTiles(tiles: List): List { - val newTiles: MutableList = mutableListOf() - val row = TileRow(columns = gridSizeInteractor.columns.value) - val tilesQueue: ArrayDeque> = - ArrayDeque( - tiles.map { - SizedTileImpl( - it, - if (iconTilesInteractor.isIconTile(it)) 1 else 2, - ) - } - ) - - while (tilesQueue.isNotEmpty()) { - if (row.isFull()) { - newTiles.addAll(row.tiles.map { it.tile }) - row.clear() - } - - val tile = tilesQueue.removeFirst() - - // If the tile fits in the row, add it. - if (!row.maybeAddTile(tile)) { - // If the tile does not fit the row, find an icon tile to move. - // We'll try to either add an icon tile from the queue to complete the row, or - // remove an icon tile from the current row to free up space. - - val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 } - if (iconTile != null) { - tilesQueue.remove(iconTile) - tilesQueue.addFirst(tile) - row.maybeAddTile(iconTile) - } else { - val tileToRemove: SizedTile? = row.findLastIconTile() - if (tileToRemove != null) { - row.removeTile(tileToRemove) - row.maybeAddTile(tile) - - // Moving the icon tile to the end because there's no other - // icon tiles in the queue. - tilesQueue.addLast(tileToRemove) - } else { - // If the row does not have an icon tile, add the incomplete row. - // Note: this shouldn't happen because an icon tile is guaranteed to be in a - // row that doesn't have enough space for a large tile. - val tileSpecs = row.tiles.map { it.tile } - Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs") - newTiles.addAll(tileSpecs) - row.clear() - tilesQueue.addFirst(tile) - } - } - } - } - - // Add last row that might be incomplete - newTiles.addAll(row.tiles.map { it.tile }) - - return newTiles.toList() - } - - private companion object { - const val TAG = "InfiniteGridConsistencyInteractor" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt index 9a2315be29a2e5a009d4dec53a4b4f63da4413f4..35faa97db2fee2e78f2ab7685f98ab41445260d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt @@ -17,9 +17,10 @@ package com.android.systemui.qs.panels.ui.compose import android.content.ClipData +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.draganddrop.dragAndDropSource import androidx.compose.foundation.draganddrop.dragAndDropTarget -import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.lazy.grid.LazyGridItemInfo import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.runtime.Composable @@ -104,11 +105,10 @@ fun Modifier.dragAndDropRemoveZone( @Composable fun Modifier.dragAndDropTileList( gridState: LazyGridState, - contentOffset: Offset, + contentOffset: () -> Offset, dragAndDropState: DragAndDropState, - onDrop: () -> Unit, + onDrop: (TileSpec) -> Unit, ): Modifier { - val currentContentOffset by rememberUpdatedState(contentOffset) val target = remember(dragAndDropState) { object : DragAndDropTarget { @@ -118,7 +118,7 @@ fun Modifier.dragAndDropTileList( override fun onMoved(event: DragAndDropEvent) { // Drag offset relative to the list's top left corner - val relativeDragOffset = event.dragOffsetRelativeTo(currentContentOffset) + val relativeDragOffset = event.dragOffsetRelativeTo(contentOffset()) val targetItem = gridState.layoutInfo.visibleItemsInfo.firstOrNull { item -> // Check if the drag is on this item @@ -132,7 +132,7 @@ fun Modifier.dragAndDropTileList( override fun onDrop(event: DragAndDropEvent): Boolean { return dragAndDropState.draggedCell?.let { - onDrop() + onDrop(it.tile.tileSpec) dragAndDropState.onDrop() true } ?: false @@ -158,36 +158,39 @@ private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean { return item.span != 1 && offset.x > itemCenter.x } +@OptIn(ExperimentalFoundationApi::class) @Composable fun Modifier.dragAndDropTileSource( sizedTile: SizedTile, - onTap: (TileSpec) -> Unit, - onDoubleTap: (TileSpec) -> Unit, - dragAndDropState: DragAndDropState + dragAndDropState: DragAndDropState, + onDragStart: () -> Unit, ): Modifier { - val state by rememberUpdatedState(dragAndDropState) - return dragAndDropSource { - detectTapGestures( - onTap = { onTap(sizedTile.tile.tileSpec) }, - onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) }, - onLongPress = { - state.onStarted(sizedTile) - - // The tilespec from the ClipData transferred isn't actually needed as we're moving - // a tile within the same application. We're using a custom MIME type to limit the - // drag event to QS. - startTransfer( - DragAndDropTransferData( - ClipData( - QsDragAndDrop.CLIPDATA_LABEL, - arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE), - ClipData.Item(sizedTile.tile.tileSpec.spec) + val dragState by rememberUpdatedState(dragAndDropState) + @Suppress("DEPRECATION") // b/368361871 + return dragAndDropSource( + block = { + detectDragGesturesAfterLongPress( + onDrag = { _, _ -> }, + onDragStart = { + dragState.onStarted(sizedTile) + onDragStart() + + // The tilespec from the ClipData transferred isn't actually needed as we're + // moving a tile within the same application. We're using a custom MIME type to + // limit the drag event to QS. + startTransfer( + DragAndDropTransferData( + ClipData( + QsDragAndDrop.CLIPDATA_LABEL, + arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE), + ClipData.Item(sizedTile.tile.tileSpec.spec), + ) ) ) - ) - } - ) - } + }, + ) + } + ) } private object QsDragAndDrop { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt index 4830ba7baa9be5e4fa05a2b5b053d3e0ffa58128..a4f977b08b7077cf4e4ab6691638cf6fd4bbb922 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt @@ -42,10 +42,8 @@ fun rememberEditListState( } /** Holds the temporary state of the tile list during a drag movement where we move tiles around. */ -class EditTileListState( - tiles: List>, - private val columns: Int, -) : DragAndDropState { +class EditTileListState(tiles: List>, private val columns: Int) : + DragAndDropState { private val _draggedCell = mutableStateOf?>(null) override val draggedCell get() = _draggedCell.value @@ -91,7 +89,8 @@ class EditTileListState( regenerateGrid(includeSpacers = true) _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell) } else { - // Add the tile with a temporary row which will get reassigned when regenerating spacers + // Add the tile with a temporary row which will get reassigned when + // regenerating spacers _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0)) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index fd276c2dd2205caae2b68ab502f5308bef296064..0c02b400646cfb00305549c7fcf271a7a68f3e28 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneScope import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel @@ -27,7 +28,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec /** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */ interface GridLayout { @Composable - fun TileGrid( + fun SceneScope.TileGrid( tiles: List, modifier: Modifier, editModeStart: () -> Unit, @@ -66,7 +67,7 @@ interface PaginatableGridLayout : GridLayout { */ fun splitInRows( tiles: List>, - columns: Int + columns: Int, ): List>> { val row = TileRow(columns) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 08a56bf29f661104dac10f2fe1714ed9077009f5..083f529a21da16c435d420c74d2ef5188c9d49ef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight @@ -55,7 +56,7 @@ constructor( @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout, ) : GridLayout by delegateGridLayout { @Composable - override fun TileGrid( + override fun SceneScope.TileGrid( tiles: List, modifier: Modifier, editModeStart: () -> Unit, @@ -85,16 +86,16 @@ constructor( ) { val page = pages[it] - delegateGridLayout.TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) + with(delegateGridLayout) { + TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) + } } - Box( - modifier = Modifier.height(FooterHeight).fillMaxWidth(), - ) { + Box(modifier = Modifier.height(FooterHeight).fillMaxWidth()) { PagerDots( pagerState = pagerState, activeColor = MaterialTheme.colorScheme.primary, nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, - modifier = Modifier.align(Alignment.Center) + modifier = Modifier.align(Alignment.Center), ) CompositionLocalProvider(value = LocalContentColor provides Color.White) { IconButton( @@ -103,7 +104,7 @@ constructor( ) { Icon( imageVector = Icons.Default.Edit, - contentDescription = stringResource(id = R.string.qs_edit) + contentDescription = stringResource(id = R.string.qs_edit), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index eeb55ca19bc3cc34deb6af0e7cf9e58d7b9f6dfa..8998a7f5d8154d9b2dc08a61364bd14c602169a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -16,24 +16,31 @@ package com.android.systemui.qs.panels.ui.compose -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.grid.ui.compose.VerticalSpannedGrid +import com.android.systemui.qs.composefragment.ui.GridAnchor +import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel +import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey +import com.android.systemui.res.R @Composable -fun QuickQuickSettings( +fun SceneScope.QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, ) { val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) - val tiles = sizedTiles.map { it.tile } + val tiles = sizedTiles.fastMap { it.tile } DisposableEffect(tiles) { val token = Any() @@ -41,17 +48,21 @@ fun QuickQuickSettings( onDispose { tiles.forEach { it.stopListening(token) } } } val columns by viewModel.columns.collectAsStateWithLifecycle() - - TileLazyGrid( - modifier = modifier.sysuiResTag("qqs_tile_layout"), - columns = GridCells.Fixed(columns) - ) { - items( - tiles.size, - key = { index -> sizedTiles[index].tile.spec.spec }, - span = { index -> GridItemSpan(sizedTiles[index].width) } - ) { index -> - Tile(tile = tiles[index], iconOnly = sizedTiles[index].isIcon, modifier = Modifier) + Box(modifier = modifier) { + GridAnchor() + VerticalSpannedGrid( + columns = columns, + columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal), + rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), + spans = sizedTiles.fastMap { it.width }, + modifier = Modifier.sysuiResTag("qqs_tile_layout"), + ) { spanIndex -> + val it = sizedTiles[spanIndex] + Tile( + tile = it.tile, + iconOnly = it.isIcon, + modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt deleted file mode 100644 index 93037d1d25728423133808d9d6fb074d20405c42..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ /dev/null @@ -1,860 +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. - */ - -@file:OptIn(ExperimentalFoundationApi::class) - -package com.android.systemui.qs.panels.ui.compose - -import android.graphics.drawable.Animatable -import android.service.quicksettings.Tile.STATE_ACTIVE -import android.service.quicksettings.Tile.STATE_INACTIVE -import android.text.TextUtils -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi -import androidx.compose.animation.graphics.res.animatedVectorResource -import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter -import androidx.compose.animation.graphics.vector.AnimatedImageVector -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.LocalOverscrollConfiguration -import androidx.compose.foundation.background -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.border -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Arrangement.spacedBy -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridItemScope -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Clear -import androidx.compose.material3.Icon -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.layout.positionInRoot -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.onClick -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.stateDescription -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.util.fastMap -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.Expandable -import com.android.compose.modifiers.background -import com.android.compose.modifiers.thenIf -import com.android.systemui.animation.Expandable -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.ui.compose.Icon -import com.android.systemui.common.ui.compose.load -import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.panels.shared.model.SizedTile -import com.android.systemui.qs.panels.shared.model.SizedTileImpl -import com.android.systemui.qs.panels.ui.model.GridCell -import com.android.systemui.qs.panels.ui.model.SpacerGridCell -import com.android.systemui.qs.panels.ui.model.TileGridCell -import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel -import com.android.systemui.qs.panels.ui.viewmodel.TileUiState -import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel -import com.android.systemui.qs.panels.ui.viewmodel.toUiState -import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.qs.shared.model.groupAndSort -import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.res.R -import java.util.function.Supplier -import kotlinx.coroutines.delay - -object TileType - -@Composable -fun Tile( - tile: TileViewModel, - iconOnly: Boolean, - showLabels: Boolean = false, - modifier: Modifier, -) { - val state by tile.state.collectAsStateWithLifecycle(tile.currentState) - val uiState = remember(state) { state.toUiState() } - val colors = TileDefaults.getColorForState(uiState) - - // TODO(b/361789146): Draw the shapes instead of clipping - val tileShape = TileDefaults.animateTileShape(uiState.state) - - TileContainer( - colors = colors, - showLabels = showLabels, - label = uiState.label, - iconOnly = iconOnly, - shape = tileShape, - clickEnabled = true, - onClick = tile::onClick, - onLongClick = tile::onLongClick, - modifier = modifier.height(tileHeight()), - ) { - val icon = getTileIcon(icon = uiState.icon) - if (iconOnly) { - TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center)) - } else { - val iconShape = TileDefaults.animateIconShape(uiState.state) - LargeTileContent( - label = uiState.label, - secondaryLabel = uiState.secondaryLabel, - icon = icon, - colors = colors, - iconShape = iconShape, - toggleClickSupported = state.handlesSecondaryClick, - onClick = { - if (state.handlesSecondaryClick) { - tile.onSecondaryClick() - } - }, - onLongClick = { tile.onLongClick(it) }, - ) - } - } -} - -@Composable -private fun TileContainer( - colors: TileColors, - showLabels: Boolean, - label: String, - iconOnly: Boolean, - shape: Shape, - clickEnabled: Boolean = false, - onClick: (Expandable) -> Unit = {}, - onLongClick: (Expandable) -> Unit = {}, - modifier: Modifier = Modifier, - content: @Composable BoxScope.(Expandable) -> Unit, -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = - spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin), Alignment.Top), - modifier = modifier, - ) { - val backgroundColor = - if (iconOnly) { - colors.iconBackground - } else { - colors.background - } - Expandable( - color = backgroundColor, - shape = shape, - modifier = Modifier.height(tileHeight()).clip(shape) - ) { - Box( - modifier = - Modifier.fillMaxSize() - .thenIf(clickEnabled) { - Modifier.combinedClickable( - onClick = { onClick(it) }, - onLongClick = { onLongClick(it) } - ) - } - .tilePadding(), - ) { - content(it) - } - } - - if (showLabels && iconOnly) { - Text( - label, - maxLines = 2, - color = colors.label, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - ) - } - } -} - -@Composable -private fun LargeTileContent( - label: String, - secondaryLabel: String?, - icon: Icon, - colors: TileColors, - iconShape: Shape, - toggleClickSupported: Boolean = false, - onClick: () -> Unit = {}, - onLongClick: () -> Unit = {}, -) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement() - ) { - // Icon - Box( - modifier = - Modifier.size(TileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) { - Modifier.clip(iconShape) - .background(colors.iconBackground, { 1f }) - .combinedClickable(onClick = onClick, onLongClick = onLongClick) - } - ) { - TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center)) - } - - // Labels - Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { - Text( - label, - color = colors.label, - modifier = Modifier.tileMarquee(), - ) - if (!TextUtils.isEmpty(secondaryLabel)) { - Text( - secondaryLabel ?: "", - color = colors.secondaryLabel, - modifier = Modifier.tileMarquee(), - ) - } - } - } -} - -private fun Modifier.tileMarquee(): Modifier { - return basicMarquee( - iterations = 1, - initialDelayMillis = 200, - ) -} - -@Composable -fun TileLazyGrid( - modifier: Modifier = Modifier, - state: LazyGridState = rememberLazyGridState(), - columns: GridCells, - content: LazyGridScope.() -> Unit, -) { - LazyVerticalGrid( - state = state, - columns = columns, - verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), - horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), - modifier = modifier, - content = content, - ) -} - -@Composable -fun DefaultEditTileGrid( - currentListState: EditTileListState, - otherTiles: List>, - columns: Int, - modifier: Modifier, - onAddTile: (TileSpec, Int) -> Unit, - onRemoveTile: (TileSpec) -> Unit, - onSetTiles: (List) -> Unit, - onResize: (TileSpec) -> Unit, -) { - val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { - onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) - } - val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical) - - CompositionLocalProvider(LocalOverscrollConfiguration provides null) { - Column( - verticalArrangement = - spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), - modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()) - ) { - AnimatedContent( - targetState = currentListState.dragInProgress, - modifier = Modifier.wrapContentSize() - ) { dragIsInProgress -> - EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) { - if (dragIsInProgress) { - RemoveTileTarget() - } else { - Text(text = "Hold and drag to rearrange tiles.") - } - } - } - - CurrentTilesGrid( - currentListState, - columns, - tilePadding, - onRemoveTile, - onResize, - onSetTiles, - ) - - // Hide available tiles when dragging - AnimatedVisibility( - visible = !currentListState.dragInProgress, - enter = fadeIn(), - exit = fadeOut() - ) { - Column( - verticalArrangement = - spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), - modifier = modifier.fillMaxSize() - ) { - EditGridHeader { Text(text = "Hold and drag to add tiles.") } - - AvailableTileGrid( - otherTiles, - columns, - tilePadding, - addTileToEnd, - currentListState, - ) - } - } - - // Drop zone to remove tiles dragged out of the tile grid - Spacer( - modifier = - Modifier.fillMaxWidth() - .weight(1f) - .dragAndDropRemoveZone(currentListState, onRemoveTile) - ) - } - } -} - -@Composable -private fun EditGridHeader( - modifier: Modifier = Modifier, - content: @Composable BoxScope.() -> Unit -) { - CompositionLocalProvider( - LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f) - ) { - Box( - contentAlignment = Alignment.Center, - modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight) - ) { - content() - } - } -} - -@Composable -private fun RemoveTileTarget() { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement(), - modifier = - Modifier.fillMaxHeight() - .border(1.dp, LocalContentColor.current, shape = CircleShape) - .padding(10.dp) - ) { - Icon(imageVector = Icons.Default.Clear, contentDescription = null) - Text(text = "Remove") - } -} - -@Composable -private fun CurrentTilesContainer(content: @Composable () -> Unit) { - Box( - Modifier.fillMaxWidth() - .border( - width = 1.dp, - color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f), - shape = RoundedCornerShape(48.dp), - ) - .padding(dimensionResource(R.dimen.qs_tile_margin_vertical)) - ) { - content() - } -} - -@Composable -private fun CurrentTilesGrid( - listState: EditTileListState, - columns: Int, - tilePadding: Dp, - onClick: (TileSpec) -> Unit, - onResize: (TileSpec) -> Unit, - onSetTiles: (List) -> Unit, -) { - val currentListState by rememberUpdatedState(listState) - - CurrentTilesContainer { - val tileHeight = tileHeight() - val totalRows = listState.tiles.lastOrNull()?.row ?: 0 - val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding) - val gridState = rememberLazyGridState() - var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) } - - TileLazyGrid( - state = gridState, - modifier = - Modifier.height(totalHeight) - .dragAndDropTileList(gridState, gridContentOffset, listState) { - onSetTiles(currentListState.tileSpecs()) - } - .onGloballyPositioned { coordinates -> - gridContentOffset = coordinates.positionInRoot() - } - .testTag(CURRENT_TILES_GRID_TEST_TAG), - columns = GridCells.Fixed(columns) - ) { - editTiles( - listState.tiles, - ClickAction.REMOVE, - onClick, - listState, - onResize = onResize, - indicatePosition = true, - ) - } - } -} - -@Composable -private fun AvailableTileGrid( - tiles: List>, - columns: Int, - tilePadding: Dp, - onClick: (TileSpec) -> Unit, - dragAndDropState: DragAndDropState, -) { - val availableTileHeight = tileHeight(true) - val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding) - - // Available tiles aren't visible during drag and drop, so the row isn't needed - val groupedTiles = - remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { - groupAndSort(tiles.fastMap { TileGridCell(it, 0) }) - } - val labelColors = TileDefaults.inactiveTileColors() - // Available tiles - TileLazyGrid( - modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG), - columns = GridCells.Fixed(columns) - ) { - groupedTiles.forEach { category, tiles -> - stickyHeader { - Text( - text = category.label.load() ?: "", - fontSize = 20.sp, - color = labelColors.label, - modifier = - Modifier.background(Color.Black) - .padding(start = 16.dp, bottom = 8.dp, top = 8.dp) - ) - } - editTiles( - tiles, - ClickAction.ADD, - onClick, - dragAndDropState = dragAndDropState, - showLabels = true, - ) - } - } -} - -fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp { - val rows = (nTiles + columns - 1) / columns - return gridHeight(rows, tileHeight, padding) -} - -fun gridHeight(rows: Int, tileHeight: Dp, padding: Dp): Dp { - return ((tileHeight + padding) * rows) - padding -} - -private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { - return if (this is TileGridCell && !dragAndDropState.isMoving(tile.tileSpec)) { - key - } else { - index - } -} - -fun LazyGridScope.editTiles( - cells: List, - clickAction: ClickAction, - onClick: (TileSpec) -> Unit, - dragAndDropState: DragAndDropState, - onResize: (TileSpec) -> Unit = {}, - showLabels: Boolean = false, - indicatePosition: Boolean = false, -) { - items( - count = cells.size, - key = { cells[it].key(it, dragAndDropState) }, - span = { cells[it].span }, - contentType = { TileType } - ) { index -> - when (val cell = cells[index]) { - is TileGridCell -> - if (dragAndDropState.isMoving(cell.tile.tileSpec)) { - // If the tile is being moved, replace it with a visible spacer - SpacerGridCell( - Modifier.background( - color = MaterialTheme.colorScheme.secondary, - alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA }, - shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius) - ) - .animateItem() - ) - } else { - TileGridCell( - cell = cell, - index = index, - dragAndDropState = dragAndDropState, - clickAction = clickAction, - onClick = onClick, - onResize = onResize, - showLabels = showLabels, - indicatePosition = indicatePosition - ) - } - is SpacerGridCell -> SpacerGridCell() - } - } -} - -@Composable -private fun LazyGridItemScope.TileGridCell( - cell: TileGridCell, - index: Int, - dragAndDropState: DragAndDropState, - clickAction: ClickAction, - onClick: (TileSpec) -> Unit, - onResize: (TileSpec) -> Unit = {}, - showLabels: Boolean = false, - indicatePosition: Boolean = false, -) { - val tileHeight = tileHeight(cell.isIcon && showLabels) - val onClickActionName = - when (clickAction) { - ClickAction.ADD -> stringResource(id = R.string.accessibility_qs_edit_tile_add_action) - ClickAction.REMOVE -> - stringResource(id = R.string.accessibility_qs_edit_remove_tile_action) - } - val stateDescription = - if (indicatePosition) { - stringResource(id = R.string.accessibility_qs_edit_position, index + 1) - } else { - "" - } - EditTile( - tileViewModel = cell.tile, - iconOnly = cell.isIcon, - showLabels = showLabels, - modifier = - Modifier.height(tileHeight) - .animateItem() - .semantics { - onClick(onClickActionName) { false } - this.stateDescription = stateDescription - } - .dragAndDropTileSource( - SizedTileImpl(cell.tile, cell.width), - onClick, - onResize, - dragAndDropState, - ) - ) -} - -@Composable -private fun SpacerGridCell(modifier: Modifier = Modifier) { - // By default, spacers are invisible and exist purely to catch drag movements - Box(modifier.height(tileHeight()).fillMaxWidth().tilePadding()) -} - -@Composable -fun EditTile( - tileViewModel: EditTileViewModel, - iconOnly: Boolean, - showLabels: Boolean, - modifier: Modifier = Modifier, -) { - val label = tileViewModel.label.text - val colors = TileDefaults.inactiveTileColors() - - TileContainer( - colors = colors, - showLabels = showLabels, - label = label, - iconOnly = iconOnly, - shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius), - modifier = modifier, - ) { - if (iconOnly) { - TileIcon( - icon = tileViewModel.icon, - color = colors.icon, - modifier = Modifier.align(Alignment.Center) - ) - } else { - LargeTileContent( - label = label, - secondaryLabel = tileViewModel.appName?.text, - icon = tileViewModel.icon, - colors = colors, - iconShape = RoundedCornerShape(TileDefaults.InactiveCornerRadius), - ) - } - } -} - -enum class ClickAction { - ADD, - REMOVE, -} - -@Composable -private fun getTileIcon(icon: Supplier): Icon { - val context = LocalContext.current - return icon.get()?.let { - if (it is QSTileImpl.ResourceIcon) { - Icon.Resource(it.resId, null) - } else { - Icon.Loaded(it.getDrawable(context), null) - } - } ?: Icon.Resource(R.drawable.ic_error_outline, null) -} - -@OptIn(ExperimentalAnimationGraphicsApi::class) -@Composable -private fun TileIcon( - icon: Icon, - color: Color, - animateToEnd: Boolean = false, - modifier: Modifier = Modifier, -) { - val iconModifier = modifier.size(TileDefaults.IconSize) - val context = LocalContext.current - val loadedDrawable = - remember(icon, context) { - when (icon) { - is Icon.Loaded -> icon.drawable - is Icon.Resource -> context.getDrawable(icon.res) - } - } - if (loadedDrawable !is Animatable) { - Icon( - icon = icon, - tint = color, - modifier = iconModifier, - ) - } else if (icon is Icon.Resource) { - val image = AnimatedImageVector.animatedVectorResource(id = icon.res) - val painter = - if (animateToEnd) { - rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) - } else { - var atEnd by remember(icon.res) { mutableStateOf(false) } - LaunchedEffect(key1 = icon.res) { - delay(350) - atEnd = true - } - rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) - } - Image( - painter = painter, - contentDescription = icon.contentDescription?.load(), - colorFilter = ColorFilter.tint(color = color), - modifier = iconModifier - ) - } -} - -private fun Modifier.tilePadding(): Modifier { - return padding(TileDefaults.TilePadding) -} - -private fun tileHorizontalArrangement(): Arrangement.Horizontal { - return spacedBy(space = TileDefaults.TileArrangementPadding, alignment = Alignment.Start) -} - -@Composable -fun tileHeight(iconWithLabel: Boolean = false): Dp { - return if (iconWithLabel) { - TileDefaults.IconTileWithLabelHeight - } else { - TileDefaults.TileHeight - } -} - -private data class TileColors( - val background: Color, - val iconBackground: Color, - val label: Color, - val secondaryLabel: Color, - val icon: Color, -) - -private object EditModeTileDefaults { - const val PLACEHOLDER_ALPHA = .3f - val EditGridHeaderHeight = 60.dp -} - -private object TileDefaults { - val InactiveCornerRadius = 50.dp - val ActiveIconCornerRadius = 16.dp - val ActiveTileCornerRadius = 24.dp - - val ToggleTargetSize = 56.dp - val IconSize = 24.dp - - val TilePadding = 8.dp - val TileArrangementPadding = 6.dp - - val TileHeight = 72.dp - val IconTileWithLabelHeight = 140.dp - - /** An active tile without dual target uses the active color as background */ - @Composable - fun activeTileColors(): TileColors = - TileColors( - background = MaterialTheme.colorScheme.primary, - iconBackground = MaterialTheme.colorScheme.primary, - label = MaterialTheme.colorScheme.onPrimary, - secondaryLabel = MaterialTheme.colorScheme.onPrimary, - icon = MaterialTheme.colorScheme.onPrimary, - ) - - /** An active tile with dual target only show the active color on the icon */ - @Composable - fun activeDualTargetTileColors(): TileColors = - TileColors( - background = MaterialTheme.colorScheme.surfaceVariant, - iconBackground = MaterialTheme.colorScheme.primary, - label = MaterialTheme.colorScheme.onSurfaceVariant, - secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, - icon = MaterialTheme.colorScheme.onPrimary, - ) - - @Composable - fun inactiveTileColors(): TileColors = - TileColors( - background = MaterialTheme.colorScheme.surfaceVariant, - iconBackground = MaterialTheme.colorScheme.surfaceVariant, - label = MaterialTheme.colorScheme.onSurfaceVariant, - secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, - icon = MaterialTheme.colorScheme.onSurfaceVariant, - ) - - @Composable - fun unavailableTileColors(): TileColors = - TileColors( - background = MaterialTheme.colorScheme.surface, - iconBackground = MaterialTheme.colorScheme.surface, - label = MaterialTheme.colorScheme.onSurface, - secondaryLabel = MaterialTheme.colorScheme.onSurface, - icon = MaterialTheme.colorScheme.onSurface, - ) - - @Composable - fun getColorForState(uiState: TileUiState): TileColors { - return when (uiState.state) { - STATE_ACTIVE -> { - if (uiState.handlesSecondaryClick) { - activeDualTargetTileColors() - } else { - activeTileColors() - } - } - STATE_INACTIVE -> inactiveTileColors() - else -> unavailableTileColors() - } - } - - @Composable - fun animateIconShape(state: Int): Shape { - return animateShape( - state = state, - activeCornerRadius = ActiveIconCornerRadius, - label = "QSTileCornerRadius", - ) - } - - @Composable - fun animateTileShape(state: Int): Shape { - return animateShape( - state = state, - activeCornerRadius = ActiveTileCornerRadius, - label = "QSTileIconCornerRadius", - ) - } - - @Composable - fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape { - val animatedCornerRadius by - animateDpAsState( - targetValue = - if (state == STATE_ACTIVE) { - activeCornerRadius - } else { - InactiveCornerRadius - }, - label = label - ) - return RoundedCornerShape(animatedCornerRadius) - } -} - -private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid" -private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid" diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index 8c57d41b21239cc1ec988419d322c683374bef54..1a5297b10e371c848f56667b6d29d4b0196bb8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -20,16 +20,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable -fun TileGrid( +fun SceneScope.TileGrid( viewModel: TileGridViewModel, modifier: Modifier = Modifier, - editModeStart: () -> Unit + editModeStart: () -> Unit, ) { val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) - gridLayout.TileGrid(tiles, modifier, editModeStart) + with(gridLayout) { TileGrid(tiles, modifier, editModeStart) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c2fb252d13c8a2979662664502813d2bc98cc3c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose.infinitegrid + +import android.graphics.drawable.Animatable +import android.text.TextUtils +import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.Image +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.toggleableState +import androidx.compose.ui.unit.dp +import com.android.compose.modifiers.background +import com.android.compose.modifiers.thenIf +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.common.ui.compose.load +import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel +import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState +import com.android.systemui.res.R +import kotlinx.coroutines.delay + +private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target" + +@Composable +fun LargeTileContent( + label: String, + secondaryLabel: String?, + icon: Icon, + colors: TileColors, + accessibilityUiState: AccessibilityUiState? = null, + toggleClickSupported: Boolean = false, + iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius), + onClick: () -> Unit = {}, + onLongClick: () -> Unit = {}, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = tileHorizontalArrangement(), + ) { + // Icon + val longPressLabel = longPressLabel() + Box( + modifier = + Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) { + Modifier.clip(iconShape) + .background(colors.iconBackground, { 1f }) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + onLongClickLabel = longPressLabel, + ) + .thenIf(accessibilityUiState != null) { + Modifier.semantics { + accessibilityUiState as AccessibilityUiState + contentDescription = accessibilityUiState.contentDescription + stateDescription = accessibilityUiState.stateDescription + accessibilityUiState.toggleableState?.let { + toggleableState = it + } + role = Role.Switch + } + .sysuiResTag(TEST_TAG_TOGGLE) + } + } + ) { + SmallTileContent( + icon = icon, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), + ) + } + + // Labels + LargeTileLabels( + label = label, + secondaryLabel = secondaryLabel, + colors = colors, + accessibilityUiState = accessibilityUiState, + ) + } +} + +@Composable +fun LargeTileLabels( + label: String, + secondaryLabel: String?, + colors: TileColors, + modifier: Modifier = Modifier, + accessibilityUiState: AccessibilityUiState? = null, +) { + Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { + Text(label, color = colors.label, modifier = Modifier.tileMarquee()) + if (!TextUtils.isEmpty(secondaryLabel)) { + Text( + secondaryLabel ?: "", + color = colors.secondaryLabel, + modifier = + Modifier.tileMarquee().thenIf( + accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") == + true + ) { + Modifier.clearAndSetSemantics {} + }, + ) + } + } +} + +@OptIn(ExperimentalAnimationGraphicsApi::class) +@Composable +fun SmallTileContent( + modifier: Modifier = Modifier, + icon: Icon, + color: Color, + animateToEnd: Boolean = false, +) { + val iconModifier = modifier.size(CommonTileDefaults.IconSize) + val context = LocalContext.current + val loadedDrawable = + remember(icon, context) { + when (icon) { + is Icon.Loaded -> icon.drawable + is Icon.Resource -> context.getDrawable(icon.res) + } + } + if (loadedDrawable !is Animatable) { + Icon(icon = icon, tint = color, modifier = iconModifier) + } else if (icon is Icon.Resource) { + val image = AnimatedImageVector.animatedVectorResource(id = icon.res) + val painter = + if (animateToEnd) { + rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) + } else { + var atEnd by remember(icon.res) { mutableStateOf(false) } + LaunchedEffect(key1 = icon.res) { + delay(350) + atEnd = true + } + rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) + } + Image( + painter = painter, + contentDescription = icon.contentDescription?.load(), + colorFilter = ColorFilter.tint(color = color), + modifier = iconModifier, + ) + } +} + +object CommonTileDefaults { + val IconSize = 24.dp + val ToggleTargetSize = 56.dp + val TileHeight = 72.dp + val TilePadding = 8.dp + val TileArrangementPadding = 6.dp + val InactiveCornerRadius = 50.dp + + @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt new file mode 100644 index 0000000000000000000000000000000000000000..0e76e18fab8e0363cbab3e061f966f536cbb4f5a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -0,0 +1,711 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalFoundationApi::class) + +package com.android.systemui.qs.panels.ui.compose.infinitegrid + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.animateIntAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.LocalOverscrollConfiguration +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridItemScope +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAlignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.CustomAccessibilityAction +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.customActions +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastMap +import androidx.compose.ui.zIndex +import com.android.compose.modifiers.background +import com.android.compose.modifiers.height +import com.android.systemui.common.ui.compose.load +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.DragAndDropState +import com.android.systemui.qs.panels.ui.compose.EditTileListState +import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone +import com.android.systemui.qs.panels.ui.compose.dragAndDropTileList +import com.android.systemui.qs.panels.ui.compose.dragAndDropTileSource +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileArrangementPadding +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.ToggleTargetSize +import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding +import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState +import com.android.systemui.qs.panels.ui.compose.selection.ResizingHandle +import com.android.systemui.qs.panels.ui.compose.selection.TileWidths +import com.android.systemui.qs.panels.ui.compose.selection.clearSelectionTile +import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState +import com.android.systemui.qs.panels.ui.compose.selection.selectableTile +import com.android.systemui.qs.panels.ui.model.GridCell +import com.android.systemui.qs.panels.ui.model.SpacerGridCell +import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.groupAndSort +import com.android.systemui.res.R +import kotlinx.coroutines.delay + +object TileType + +@Composable +fun DefaultEditTileGrid( + currentListState: EditTileListState, + otherTiles: List>, + columns: Int, + modifier: Modifier, + onRemoveTile: (TileSpec) -> Unit, + onSetTiles: (List) -> Unit, + onResize: (TileSpec) -> Unit, +) { + val selectionState = rememberSelectionState() + + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + Column( + verticalArrangement = + spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), + modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()), + ) { + AnimatedContent( + targetState = currentListState.dragInProgress, + modifier = Modifier.wrapContentSize(), + label = "", + ) { dragIsInProgress -> + EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) { + if (dragIsInProgress) { + RemoveTileTarget() + } else { + Text(text = "Hold and drag to rearrange tiles.") + } + } + } + + CurrentTilesGrid(currentListState, selectionState, columns, onResize, onSetTiles) + + // Hide available tiles when dragging + AnimatedVisibility( + visible = !currentListState.dragInProgress, + enter = fadeIn(), + exit = fadeOut(), + ) { + Column( + verticalArrangement = + spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), + modifier = modifier.fillMaxSize(), + ) { + EditGridHeader { Text(text = "Hold and drag to add tiles.") } + + AvailableTileGrid(otherTiles, selectionState, columns, currentListState) + } + } + + // Drop zone to remove tiles dragged out of the tile grid + Spacer( + modifier = + Modifier.fillMaxWidth() + .weight(1f) + .dragAndDropRemoveZone(currentListState, onRemoveTile) + ) + } + } +} + +@Composable +private fun EditGridHeader( + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit, +) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f) + ) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight), + ) { + content() + } + } +} + +@Composable +private fun RemoveTileTarget() { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = tileHorizontalArrangement(), + modifier = + Modifier.fillMaxHeight() + .border(1.dp, LocalContentColor.current, shape = CircleShape) + .padding(10.dp), + ) { + Icon(imageVector = Icons.Default.Clear, contentDescription = null) + Text(text = "Remove") + } +} + +@Composable +private fun CurrentTilesGrid( + listState: EditTileListState, + selectionState: MutableSelectionState, + columns: Int, + onResize: (TileSpec) -> Unit, + onSetTiles: (List) -> Unit, +) { + val currentListState by rememberUpdatedState(listState) + val tileHeight = CommonTileDefaults.TileHeight + val totalRows = listState.tiles.lastOrNull()?.row ?: 0 + val totalHeight by + animateDpAsState( + gridHeight(totalRows + 1, tileHeight, TileArrangementPadding, CurrentTilesGridPadding), + label = "QSEditCurrentTilesGridHeight", + ) + val gridState = rememberLazyGridState() + var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) } + var droppedSpec by remember { mutableStateOf(null) } + + // Select the tile that was dropped. A delay is introduced to avoid clipping issues on the + // selected border and resizing handle, as well as letting the selection animation play. + LaunchedEffect(droppedSpec) { + droppedSpec?.let { + delay(200) + selectionState.select(it) + + // Reset droppedSpec in case a tile is dropped twice in a row + droppedSpec = null + } + } + + TileLazyGrid( + state = gridState, + columns = GridCells.Fixed(columns), + contentPadding = PaddingValues(CurrentTilesGridPadding), + modifier = + Modifier.fillMaxWidth() + .height { totalHeight.roundToPx() } + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f), + shape = RoundedCornerShape(48.dp), + ) + .dragAndDropTileList(gridState, { gridContentOffset }, listState) { spec -> + onSetTiles(currentListState.tileSpecs()) + droppedSpec = spec + } + .onGloballyPositioned { coordinates -> + gridContentOffset = coordinates.positionInRoot() + } + .testTag(CURRENT_TILES_GRID_TEST_TAG), + ) { + EditTiles(listState.tiles, listState, selectionState, onResize) + } +} + +@Composable +private fun AvailableTileGrid( + tiles: List>, + selectionState: MutableSelectionState, + columns: Int, + dragAndDropState: DragAndDropState, +) { + // Available tiles aren't visible during drag and drop, so the row isn't needed + val groupedTiles = + remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { + groupAndSort(tiles.fastMap { TileGridCell(it, 0) }) + } + val labelColors = EditModeTileDefaults.editTileColors() + + // Available tiles + Column( + verticalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding), + horizontalAlignment = Alignment.Start, + modifier = + Modifier.fillMaxWidth().wrapContentHeight().testTag(AVAILABLE_TILES_GRID_TEST_TAG), + ) { + groupedTiles.forEach { (category, tiles) -> + Text( + text = category.label.load() ?: "", + fontSize = 20.sp, + color = labelColors.label, + modifier = + Modifier.fillMaxWidth() + .background(Color.Black) + .padding(start = 16.dp, bottom = 8.dp, top = 8.dp), + ) + tiles.chunked(columns).forEach { row -> + Row( + horizontalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding), + modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max), + ) { + row.forEachIndexed { index, tileGridCell -> + AvailableTileGridCell( + cell = tileGridCell, + index = index, + dragAndDropState = dragAndDropState, + selectionState = selectionState, + modifier = Modifier.weight(1f).fillMaxHeight(), + ) + } + + // Spacers for incomplete rows + repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) } + } + } + } + } +} + +fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp { + return ((tileHeight + tilePadding) * rows) - tilePadding + gridPadding * 2 +} + +private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { + return when (this) { + is TileGridCell -> { + if (dragAndDropState.isMoving(tile.tileSpec)) index else key + } + is SpacerGridCell -> index + } +} + +fun LazyGridScope.EditTiles( + cells: List, + dragAndDropState: DragAndDropState, + selectionState: MutableSelectionState, + onResize: (TileSpec) -> Unit, +) { + items( + count = cells.size, + key = { cells[it].key(it, dragAndDropState) }, + span = { cells[it].span }, + contentType = { TileType }, + ) { index -> + when (val cell = cells[index]) { + is TileGridCell -> + if (dragAndDropState.isMoving(cell.tile.tileSpec)) { + // If the tile is being moved, replace it with a visible spacer + SpacerGridCell( + Modifier.background( + color = MaterialTheme.colorScheme.secondary, + alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA }, + shape = RoundedCornerShape(InactiveCornerRadius), + ) + .animateItem() + ) + } else { + TileGridCell( + cell = cell, + index = index, + dragAndDropState = dragAndDropState, + selectionState = selectionState, + onResize = onResize, + ) + } + is SpacerGridCell -> SpacerGridCell() + } + } +} + +@Composable +private fun LazyGridItemScope.TileGridCell( + cell: TileGridCell, + index: Int, + dragAndDropState: DragAndDropState, + selectionState: MutableSelectionState, + onResize: (TileSpec) -> Unit, +) { + val selected = selectionState.isSelected(cell.tile.tileSpec) + val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1) + val selectionAlpha by + animateFloatAsState( + targetValue = if (selected) 1f else 0f, + label = "QSEditTileSelectionAlpha", + ) + + val modifier = + Modifier.animateItem() + .semantics(mergeDescendants = true) { + this.stateDescription = stateDescription + contentDescription = cell.tile.label.text + customActions = + listOf( + // TODO(b/367748260): Add final accessibility actions + CustomAccessibilityAction("Toggle size") { + onResize(cell.tile.tileSpec) + true + } + ) + } + .height(CommonTileDefaults.TileHeight) + .fillMaxWidth() + + val content = + @Composable { + EditTile( + tileViewModel = cell.tile, + iconOnly = cell.isIcon, + selectionAlpha = { selectionAlpha }, + modifier = + Modifier.fillMaxSize() + .selectableTile(cell.tile.tileSpec, selectionState) + .dragAndDropTileSource( + SizedTileImpl(cell.tile, cell.width), + dragAndDropState, + selectionState::unSelect, + ), + ) + } + + if (selected) { + SelectedTile( + tileSpec = cell.tile.tileSpec, + isIcon = cell.isIcon, + selectionAlpha = { selectionAlpha }, + selectionState = selectionState, + onResize = onResize, + modifier = modifier.zIndex(2f), // 2f to display this tile over neighbors when dragged + content = content, + ) + } else { + UnselectedTile( + selectionAlpha = { selectionAlpha }, + selectionState = selectionState, + modifier = modifier, + content = content, + ) + } +} + +@Composable +private fun SelectedTile( + tileSpec: TileSpec, + isIcon: Boolean, + selectionAlpha: () -> Float, + selectionState: MutableSelectionState, + onResize: (TileSpec) -> Unit, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + // Current base, min and max width of this tile + var tileWidths: TileWidths? by remember { mutableStateOf(null) } + + // Animated diff between the current width and the resized width of the tile. We can't use + // animateContentSize here as the tile is sometimes unbounded. + val remainingOffset by + animateIntAsState( + selectionState.resizingState?.let { tileWidths?.base?.minus(it.width) ?: 0 } ?: 0, + label = "QSEditTileWidthOffset", + ) + + val padding = with(LocalDensity.current) { TileArrangementPadding.roundToPx() } + Box( + modifier.onSizeChanged { + val min = if (isIcon) it.width else (it.width - padding) / 2 + val max = if (isIcon) (it.width * 2) + padding else it.width + tileWidths = TileWidths(it.width, min, max) + } + ) { + val handle = + @Composable { + ResizingHandle( + enabled = true, + selectionState = selectionState, + transition = selectionAlpha, + tileWidths = { tileWidths }, + ) { + onResize(tileSpec) + } + } + + Layout(contents = listOf(content, handle)) { + (contentMeasurables, handleMeasurables), + constraints -> + // Grab the width from the resizing state if a resize is in progress, otherwise fill the + // max width + val width = + selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset) + val contentPlaceable = + contentMeasurables.first().measure(constraints.copy(maxWidth = width)) + val handlePlaceable = handleMeasurables.first().measure(constraints) + + // Place the dot vertically centered on the right edge + val handleX = contentPlaceable.width - (handlePlaceable.width / 2) + val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) + + layout(constraints.maxWidth, constraints.maxHeight) { + contentPlaceable.place(0, 0) + handlePlaceable.place(handleX, handleY) + } + } + } +} + +@Composable +private fun UnselectedTile( + selectionAlpha: () -> Float, + selectionState: MutableSelectionState, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + val handle = + @Composable { + ResizingHandle( + enabled = false, + selectionState = selectionState, + transition = selectionAlpha, + ) + } + + Box(modifier) { + Layout(contents = listOf(content, handle)) { + (contentMeasurables, handleMeasurables), + constraints -> + val contentPlaceable = + contentMeasurables + .first() + .measure(constraints.copy(maxWidth = constraints.maxWidth)) + val handlePlaceable = handleMeasurables.first().measure(constraints) + + // Place the dot vertically centered on the right edge + val handleX = contentPlaceable.width - (handlePlaceable.width / 2) + val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) + + layout(constraints.maxWidth, constraints.maxHeight) { + contentPlaceable.place(0, 0) + handlePlaceable.place(handleX, handleY) + } + } + } +} + +@Composable +private fun AvailableTileGridCell( + cell: TileGridCell, + index: Int, + dragAndDropState: DragAndDropState, + selectionState: MutableSelectionState, + modifier: Modifier = Modifier, +) { + val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_tile_add_action) + val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1) + val colors = EditModeTileDefaults.editTileColors() + + // Displays the tile as an icon tile with the label underneath + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top), + modifier = modifier, + ) { + EditTileContainer( + colors = colors, + modifier = + Modifier.fillMaxWidth() + .height(CommonTileDefaults.TileHeight) + .clearSelectionTile(selectionState) + .semantics(mergeDescendants = true) { + onClick(onClickActionName) { false } + this.stateDescription = stateDescription + } + .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { + selectionState.unSelect() + }, + ) { + // Icon + SmallTileContent( + icon = cell.tile.icon, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), + ) + } + Box(Modifier.fillMaxSize()) { + Text( + cell.tile.label.text, + maxLines = 2, + color = colors.label, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + modifier = Modifier.align(Alignment.Center), + ) + } + } +} + +@Composable +private fun SpacerGridCell(modifier: Modifier = Modifier) { + // By default, spacers are invisible and exist purely to catch drag movements + Box(modifier.height(CommonTileDefaults.TileHeight).fillMaxWidth()) +} + +@Composable +fun EditTile( + tileViewModel: EditTileViewModel, + iconOnly: Boolean, + modifier: Modifier = Modifier, + colors: TileColors = EditModeTileDefaults.editTileColors(), + selectionAlpha: () -> Float = { 1f }, +) { + // Animated horizontal alignment from center (0f) to start (-1f) + val alignmentValue by + animateFloatAsState( + targetValue = if (iconOnly) 0f else -1f, + label = "QSEditTileContentAlignment", + ) + val alignment by remember { + derivedStateOf { BiasAlignment(horizontalBias = alignmentValue, verticalBias = 0f) } + } + + EditTileContainer(colors = colors, selectionAlpha = selectionAlpha, modifier = modifier) { + // Icon + Box(Modifier.size(ToggleTargetSize).align(alignment)) { + SmallTileContent( + icon = tileViewModel.icon, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), + ) + } + + // Labels, positioned after the icon + AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { + LargeTileLabels( + label = tileViewModel.label.text, + secondaryLabel = tileViewModel.appName?.text, + colors = colors, + modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), + ) + } + } +} + +@Composable +private fun EditTileContainer( + colors: TileColors, + modifier: Modifier = Modifier, + selectionAlpha: () -> Float = { 0f }, + selectionColor: Color = MaterialTheme.colorScheme.primary, + content: @Composable BoxScope.() -> Unit = {}, +) { + Box( + Modifier.wrapContentSize().drawWithContent { + drawContent() + drawRoundRect( + SolidColor(selectionColor), + cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), + style = Stroke(EditModeTileDefaults.SelectedBorderWidth.toPx()), + alpha = selectionAlpha(), + ) + } + ) { + Box( + modifier = + modifier + .drawBehind { + drawRoundRect( + SolidColor(colors.background), + cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), + ) + } + .tilePadding(), + content = content, + ) + } +} + +private object EditModeTileDefaults { + const val PLACEHOLDER_ALPHA = .3f + val EditGridHeaderHeight = 60.dp + val SelectedBorderWidth = 2.dp + val CurrentTilesGridPadding = 8.dp + + @Composable + fun editTileColors(): TileColors = + TileColors( + background = MaterialTheme.colorScheme.surfaceVariant, + iconBackground = MaterialTheme.colorScheme.surfaceVariant, + label = MaterialTheme.colorScheme.onSurfaceVariant, + secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, + icon = MaterialTheme.colorScheme.onSurfaceVariant, + ) +} + +private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid" +private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid" diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt similarity index 77% rename from packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt rename to packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index c75b601ab4a8e5aa5079938971f633795c8e53f2..8a9606545fc323607d4c9d03320468d7ea3a2bae 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -14,23 +14,29 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.ui.compose +package com.android.systemui.qs.panels.ui.compose.infinitegrid -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout +import com.android.systemui.qs.panels.ui.compose.rememberEditListState import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey +import com.android.systemui.res.R import javax.inject.Inject @SysUISingleton @@ -42,7 +48,7 @@ constructor( ) : PaginatableGridLayout { @Composable - override fun TileGrid( + override fun SceneScope.TileGrid( tiles: List, modifier: Modifier, editModeStart: () -> Unit, @@ -55,15 +61,18 @@ constructor( val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) } - TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { - items(sizedTiles.size, span = { index -> GridItemSpan(sizedTiles[index].width) }) { - index -> - Tile( - tile = sizedTiles[index].tile, - iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec), - modifier = Modifier - ) - } + VerticalSpannedGrid( + columns = columns, + columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal), + rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), + spans = sizedTiles.fastMap { it.width }, + ) { spanIndex -> + val it = sizedTiles[spanIndex] + Tile( + tile = it.tile, + iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), + modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), + ) } } @@ -96,7 +105,6 @@ constructor( otherTiles = otherTiles, columns = columns, modifier = modifier, - onAddTile = onAddTile, onRemoveTile = onRemoveTile, onSetTiles = onSetTiles, onResize = iconTilesViewModel::resize, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt new file mode 100644 index 0000000000000000000000000000000000000000..afcbed6db53ba53f30e0d27f3b3689f2b5f9b321 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -0,0 +1,333 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalFoundationApi::class) + +package com.android.systemui.qs.panels.ui.compose.infinitegrid + +import android.content.res.Resources +import android.service.quicksettings.Tile.STATE_ACTIVE +import android.service.quicksettings.Tile.STATE_INACTIVE +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.toggleableState +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.Expandable +import com.android.compose.modifiers.thenIf +import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel +import com.android.systemui.qs.panels.ui.viewmodel.TileUiState +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toUiState +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R +import java.util.function.Supplier + +private const val TEST_TAG_SMALL = "qs_tile_small" +private const val TEST_TAG_LARGE = "qs_tile_large" + +@Composable +fun TileLazyGrid( + columns: GridCells, + modifier: Modifier = Modifier, + state: LazyGridState = rememberLazyGridState(), + contentPadding: PaddingValues = PaddingValues(0.dp), + content: LazyGridScope.() -> Unit, +) { + LazyVerticalGrid( + state = state, + columns = columns, + verticalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding), + horizontalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding), + contentPadding = contentPadding, + modifier = modifier, + content = content, + ) +} + +@Composable +fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) { + val state by tile.state.collectAsStateWithLifecycle(tile.currentState) + val resources = resources() + val uiState = remember(state, resources) { state.toUiState(resources) } + val colors = TileDefaults.getColorForState(uiState) + + // TODO(b/361789146): Draw the shapes instead of clipping + val tileShape = TileDefaults.animateTileShape(uiState.state) + + TileContainer( + color = + if (iconOnly || !uiState.handlesSecondaryClick) { + colors.iconBackground + } else { + colors.background + }, + shape = tileShape, + iconOnly = iconOnly, + onClick = tile::onClick, + onLongClick = tile::onLongClick, + uiState = uiState, + modifier = modifier, + ) { expandable -> + val icon = getTileIcon(icon = uiState.icon) + if (iconOnly) { + SmallTileContent( + icon = icon, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), + ) + } else { + val iconShape = TileDefaults.animateIconShape(uiState.state) + LargeTileContent( + label = uiState.label, + secondaryLabel = uiState.secondaryLabel, + icon = icon, + colors = colors, + iconShape = iconShape, + toggleClickSupported = state.handlesSecondaryClick, + onClick = { + if (state.handlesSecondaryClick) { + tile.onSecondaryClick() + } + }, + onLongClick = { tile.onLongClick(expandable) }, + accessibilityUiState = uiState.accessibilityUiState, + ) + } + } +} + +@Composable +private fun TileContainer( + color: Color, + shape: Shape, + iconOnly: Boolean, + uiState: TileUiState, + modifier: Modifier = Modifier, + onClick: (Expandable) -> Unit = {}, + onLongClick: (Expandable) -> Unit = {}, + content: @Composable BoxScope.(Expandable) -> Unit, +) { + Expandable(color = color, shape = shape, modifier = modifier.clip(shape)) { + val longPressLabel = longPressLabel() + Box( + modifier = + Modifier.height(CommonTileDefaults.TileHeight) + .fillMaxWidth() + .combinedClickable( + onClick = { onClick(it) }, + onLongClick = { onLongClick(it) }, + onClickLabel = uiState.accessibilityUiState.clickLabel, + onLongClickLabel = longPressLabel, + ) + .semantics { + role = uiState.accessibilityUiState.accessibilityRole + if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { + uiState.accessibilityUiState.toggleableState?.let { + toggleableState = it + } + } + stateDescription = uiState.accessibilityUiState.stateDescription + } + .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) + .thenIf(iconOnly) { + Modifier.semantics { + contentDescription = uiState.accessibilityUiState.contentDescription + } + } + .tilePadding() + ) { + content(it) + } + } +} + +@Composable +private fun getTileIcon(icon: Supplier): Icon { + val context = LocalContext.current + return icon.get()?.let { + if (it is QSTileImpl.ResourceIcon) { + Icon.Resource(it.resId, null) + } else { + Icon.Loaded(it.getDrawable(context), null) + } + } ?: Icon.Resource(R.drawable.ic_error_outline, null) +} + +fun tileHorizontalArrangement(): Arrangement.Horizontal { + return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start) +} + +fun Modifier.tileMarquee(): Modifier { + return basicMarquee(iterations = 1, initialDelayMillis = 200) +} + +fun Modifier.tilePadding(): Modifier { + return padding(CommonTileDefaults.TilePadding) +} + +data class TileColors( + val background: Color, + val iconBackground: Color, + val label: Color, + val secondaryLabel: Color, + val icon: Color, +) + +private object TileDefaults { + val ActiveIconCornerRadius = 16.dp + val ActiveTileCornerRadius = 24.dp + + /** An active tile without dual target uses the active color as background */ + @Composable + fun activeTileColors(): TileColors = + TileColors( + background = MaterialTheme.colorScheme.primary, + iconBackground = MaterialTheme.colorScheme.primary, + label = MaterialTheme.colorScheme.onPrimary, + secondaryLabel = MaterialTheme.colorScheme.onPrimary, + icon = MaterialTheme.colorScheme.onPrimary, + ) + + /** An active tile with dual target only show the active color on the icon */ + @Composable + fun activeDualTargetTileColors(): TileColors = + TileColors( + background = MaterialTheme.colorScheme.surfaceVariant, + iconBackground = MaterialTheme.colorScheme.primary, + label = MaterialTheme.colorScheme.onSurfaceVariant, + secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, + icon = MaterialTheme.colorScheme.onPrimary, + ) + + @Composable + fun inactiveTileColors(): TileColors = + TileColors( + background = MaterialTheme.colorScheme.surfaceVariant, + iconBackground = MaterialTheme.colorScheme.surfaceVariant, + label = MaterialTheme.colorScheme.onSurfaceVariant, + secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, + icon = MaterialTheme.colorScheme.onSurfaceVariant, + ) + + @Composable + fun unavailableTileColors(): TileColors = + TileColors( + background = MaterialTheme.colorScheme.surface, + iconBackground = MaterialTheme.colorScheme.surface, + label = MaterialTheme.colorScheme.onSurface, + secondaryLabel = MaterialTheme.colorScheme.onSurface, + icon = MaterialTheme.colorScheme.onSurface, + ) + + @Composable + fun getColorForState(uiState: TileUiState): TileColors { + return when (uiState.state) { + STATE_ACTIVE -> { + if (uiState.handlesSecondaryClick) { + activeDualTargetTileColors() + } else { + activeTileColors() + } + } + STATE_INACTIVE -> inactiveTileColors() + else -> unavailableTileColors() + } + } + + @Composable + fun animateIconShape(state: Int): Shape { + return animateShape( + state = state, + activeCornerRadius = ActiveIconCornerRadius, + label = "QSTileCornerRadius", + ) + } + + @Composable + fun animateTileShape(state: Int): Shape { + return animateShape( + state = state, + activeCornerRadius = ActiveTileCornerRadius, + label = "QSTileIconCornerRadius", + ) + } + + @Composable + fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape { + val animatedCornerRadius by + animateDpAsState( + targetValue = + if (state == STATE_ACTIVE) { + activeCornerRadius + } else { + InactiveCornerRadius + }, + label = label, + ) + return RoundedCornerShape(animatedCornerRadius) + } +} + +/** + * A composable function that returns the [Resources]. It will be recomposed when [Configuration] + * gets updated. + */ +@Composable +@ReadOnlyComposable +private fun resources(): Resources { + LocalConfiguration.current + return LocalContext.current.resources +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt new file mode 100644 index 0000000000000000000000000000000000000000..2ea32e640984930fec184d1d168e2ea705f445e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.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.systemui.qs.panels.ui.compose.selection + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import com.android.systemui.qs.pipeline.shared.TileSpec + +/** Creates the state of the current selected tile that is remembered across compositions. */ +@Composable +fun rememberSelectionState(): MutableSelectionState { + return remember { MutableSelectionState() } +} + +/** Holds the state of the current selection. */ +class MutableSelectionState { + private var _selectedTile = mutableStateOf(null) + private var _resizingState = mutableStateOf(null) + + /** The [ResizingState] of the selected tile is currently being resized, null if not. */ + val resizingState by _resizingState + + fun isSelected(tileSpec: TileSpec): Boolean { + return _selectedTile.value?.let { it == tileSpec } ?: false + } + + fun select(tileSpec: TileSpec) { + _selectedTile.value = tileSpec + } + + fun unSelect() { + _selectedTile.value = null + onResizingDragEnd() + } + + fun onResizingDrag(offset: Float) { + _resizingState.value?.onDrag(offset) + } + + fun onResizingDragStart(tileWidths: TileWidths, onResize: () -> Unit) { + if (_selectedTile.value == null) return + + _resizingState.value = ResizingState(tileWidths, onResize) + } + + fun onResizingDragEnd() { + _resizingState.value = null + } +} + +/** + * Listens for click events to select/unselect the given [TileSpec]. Use this on current tiles as + * they can be selected. + */ +@Composable +fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelectionState): Modifier { + return pointerInput(Unit) { + detectTapGestures( + onTap = { + if (selectionState.isSelected(tileSpec)) { + selectionState.unSelect() + } else { + selectionState.select(tileSpec) + } + } + ) + } +} + +/** + * Listens for click events to unselect any tile. Use this on available tiles as they can't be + * selected. + */ +@Composable +fun Modifier.clearSelectionTile(selectionState: MutableSelectionState): Modifier { + return pointerInput(Unit) { detectTapGestures(onTap = { selectionState.unSelect() }) } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt new file mode 100644 index 0000000000000000000000000000000000000000..a084bc2ef68af59910d626cbfbb2c2d068cf6b21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose.selection + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.setValue +import com.android.systemui.qs.panels.ui.compose.selection.ResizingDefaults.RESIZING_THRESHOLD + +class ResizingState(private val widths: TileWidths, private val onResize: () -> Unit) { + // Total drag offset of this resize operation + private var totalOffset = 0f + + /** Width in pixels of the resizing tile. */ + var width by mutableIntStateOf(widths.base) + + // Whether the tile is currently over the threshold and should be a large tile + private var passedThreshold: Boolean = passedThreshold(calculateProgression(width)) + + fun onDrag(offset: Float) { + totalOffset += offset + width = (widths.base + totalOffset).toInt().coerceIn(widths.min, widths.max) + + passedThreshold(calculateProgression(width)).let { + // Resize if we went over the threshold + if (passedThreshold != it) { + passedThreshold = it + onResize() + } + } + } + + private fun passedThreshold(progression: Float): Boolean { + return progression >= RESIZING_THRESHOLD + } + + /** The progression of the resizing tile between an icon tile (0f) and a large tile (1f) */ + private fun calculateProgression(width: Int): Float { + return ((width - widths.min) / (widths.max - widths.min).toFloat()).coerceIn(0f, 1f) + } +} + +/** Holds the width of a tile as well as its min and max widths */ +data class TileWidths(val base: Int, val min: Int, val max: Int) { + init { + check(max > min) { "The max width needs to be larger than the min width." } + } +} + +private object ResizingDefaults { + const val RESIZING_THRESHOLD = .25f +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt new file mode 100644 index 0000000000000000000000000000000000000000..e3acf38632542aa08e30e2d9f781ceb1c6ef763e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.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.systemui.qs.panels.ui.compose.selection + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.material3.LocalMinimumInteractiveComponentSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp +import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingDotSize + +/** + * Dot handling resizing drag events. Use this on the selected tile to resize it + * + * @param enabled whether resizing drag events should be handled + * @param selectionState the [MutableSelectionState] on the grid + * @param transition the animated value for the dot, used for its alpha and scale + * @param tileWidths the [TileWidths] of the selected tile + * @param onResize the callback when the drag passes the resizing threshold + */ +@Composable +fun ResizingHandle( + enabled: Boolean, + selectionState: MutableSelectionState, + transition: () -> Float, + tileWidths: () -> TileWidths? = { null }, + onResize: () -> Unit = {}, +) { + if (enabled) { + // Manually creating the touch target around the resizing dot to ensure that the next tile + // does + // not receive the touch input accidentally. + val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current + Box( + Modifier.size(minTouchTargetSize).pointerInput(Unit) { + detectHorizontalDragGestures( + onHorizontalDrag = { _, offset -> selectionState.onResizingDrag(offset) }, + onDragStart = { + tileWidths()?.let { selectionState.onResizingDragStart(it, onResize) } + }, + onDragEnd = selectionState::onResizingDragEnd, + onDragCancel = selectionState::onResizingDragEnd, + ) + } + ) { + ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center)) + } + } else { + ResizingDot(transition = transition) + } +} + +@Composable +private fun ResizingDot( + transition: () -> Float, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, +) { + Canvas(modifier = modifier.size(ResizingDotSize)) { + val v = transition() + drawCircle(color = color, radius = (ResizingDotSize / 2).toPx() * v, alpha = v) + } +} + +private object SelectionDefaults { + val ResizingDotSize = 16.dp +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt index 08ee856a0ec61906944eaf98ce85805b43f4924a..b16a7075607fa65ddc32761bb126cb9e8e465e21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt @@ -24,7 +24,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.shared.model.CategoryAndName /** Represents an item from a grid associated with a row and a span */ -interface GridCell { +sealed interface GridCell { val row: Int val span: GridItemSpan } @@ -38,30 +38,26 @@ data class TileGridCell( override val tile: EditTileViewModel, override val row: Int, override val width: Int, - override val span: GridItemSpan = GridItemSpan(width) + override val span: GridItemSpan = GridItemSpan(width), ) : GridCell, SizedTile, CategoryAndName by tile { val key: String = "${tile.tileSpec.spec}-$row" constructor( sizedTile: SizedTile, - row: Int - ) : this( - tile = sizedTile.tile, - row = row, - width = sizedTile.width, - ) + row: Int, + ) : this(tile = sizedTile.tile, row = row, width = sizedTile.width) } /** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */ @Immutable data class SpacerGridCell( override val row: Int, - override val span: GridItemSpan = GridItemSpan(1) + override val span: GridItemSpan = GridItemSpan(1), ) : GridCell fun List>.toGridCells( columns: Int, - includeSpacers: Boolean = false + includeSpacers: Boolean = false, ): List { return splitInRowsSequence(this, columns) .flatMapIndexed { rowIndex, sizedTiles -> diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt index 45051fea76b6d47e4c7737609028ac2f2f04c1b7..aa420800be7b5ad59cb09288d9c79164cf5ecc13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt @@ -16,8 +16,16 @@ package com.android.systemui.qs.panels.ui.viewmodel +import android.content.res.Resources +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.widget.Switch import androidx.compose.runtime.Immutable +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.state.ToggleableState import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.SubtitleArrayMapping +import com.android.systemui.res.R import java.util.function.Supplier @Immutable @@ -27,14 +35,78 @@ data class TileUiState( val state: Int, val handlesSecondaryClick: Boolean, val icon: Supplier, + val accessibilityUiState: AccessibilityUiState, ) -fun QSTile.State.toUiState(): TileUiState { +data class AccessibilityUiState( + val contentDescription: String, + val stateDescription: String, + val accessibilityRole: Role, + val toggleableState: ToggleableState? = null, + val clickLabel: String? = null, +) + +fun QSTile.State.toUiState(resources: Resources): TileUiState { + val accessibilityRole = + if (expandedAccessibilityClassName == Switch::class.java.name && !handlesSecondaryClick) { + Role.Switch + } else { + Role.Button + } + // State handling and description + val stateDescription = StringBuilder() + val stateText = + if (accessibilityRole == Role.Switch || state == Tile.STATE_UNAVAILABLE) { + getStateText(resources) + } else { + "" + } + val secondaryLabel = getSecondaryLabel(stateText) + if (!TextUtils.isEmpty(stateText)) { + stateDescription.append(stateText) + } + if (disabledByPolicy && state != Tile.STATE_UNAVAILABLE) { + stateDescription.append(", ") + stateDescription.append(getUnavailableText(spec, resources)) + } + if ( + !TextUtils.isEmpty(this.stateDescription) && + !stateDescription.contains(this.stateDescription!!) + ) { + stateDescription.append(", ") + stateDescription.append(this.stateDescription) + } + val toggleableState = + if (accessibilityRole == Role.Switch || handlesSecondaryClick) { + ToggleableState(state == Tile.STATE_ACTIVE) + } else { + null + } return TileUiState( - label?.toString() ?: "", - secondaryLabel?.toString() ?: "", - state, - handlesSecondaryClick, - icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, + label = label?.toString() ?: "", + secondaryLabel = secondaryLabel?.toString() ?: "", + state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state, + handlesSecondaryClick = handlesSecondaryClick, + icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, + AccessibilityUiState( + contentDescription?.toString() ?: "", + stateDescription.toString(), + accessibilityRole, + toggleableState, + resources + .getString(R.string.accessibility_tile_disabled_by_policy_action_description) + .takeIf { disabledByPolicy }, + ), ) } + +private fun QSTile.State.getStateText(resources: Resources): CharSequence { + val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) + val array = resources.getStringArray(arrayResId) + return array[state] +} + +private fun getUnavailableText(spec: String?, resources: Resources): String { + val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) + return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE] +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt index 0bcb6b7e7874fafbb181d38acf2a6f44413ddcb6..9677d47b38e8761c4f7295e57a5f1b451c35c4b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt @@ -18,8 +18,6 @@ package com.android.systemui.qs.pipeline.domain.startable import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.flags.NewQsUI -import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor @@ -36,16 +34,11 @@ constructor( private val autoAddInteractor: AutoAddInteractor, private val featureFlags: QSPipelineFlagsRepository, private val restoreReconciliationInteractor: RestoreReconciliationInteractor, - private val gridConsistencyInteractor: GridConsistencyInteractor, ) : CoreStartable { override fun start() { accessibilityTilesInteractor.init(currentTilesInteractor) autoAddInteractor.init(currentTilesInteractor) restoreReconciliationInteractor.start() - - if (NewQsUI.isEnabled) { - gridConsistencyInteractor.start() - } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt new file mode 100644 index 0000000000000000000000000000000000000000..625459d1c6fa7ff5cd13eb7cb9379858bcfc0ace --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.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.systemui.qs.shared.ui + +import com.android.compose.animation.scene.ElementKey +import com.android.systemui.qs.pipeline.shared.TileSpec + +/** Element keys to be used by the compose implementation of QS for animations. */ +object ElementKeys { + val QuickSettingsContent = ElementKey("QuickSettingsContent") + val GridAnchor = ElementKey("QuickSettingsGridAnchor") + val FooterActions = ElementKey("FooterActions") + + class TileElementKey(spec: TileSpec, val position: Int) : ElementKey(spec.spec, spec.spec) + + fun TileSpec.toElementKey(positionInGrid: Int) = TileElementKey(this, positionInGrid) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 7ceb78638f6cce1ebda0a8ce6a72bf7d71c22053..7bff827dee034d061979b08d9b16d83a4ce32011 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -32,7 +32,7 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; -import android.widget.Switch; +import android.widget.Button; import androidx.annotation.VisibleForTesting; @@ -59,13 +59,13 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; -import kotlinx.coroutines.Job; - import java.util.List; import java.util.concurrent.Executor; import javax.inject.Inject; +import kotlinx.coroutines.Job; + /** Quick settings tile: Bluetooth **/ public class BluetoothTile extends QSTileImpl { @@ -147,6 +147,8 @@ public class BluetoothTile extends QSTileImpl { } } + + @Override public Intent getLongClickIntent() { return new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); @@ -221,7 +223,7 @@ public class BluetoothTile extends QSTileImpl { state.state = Tile.STATE_INACTIVE; } - state.expandedAccessibilityClassName = Switch.class.getName(); + state.expandedAccessibilityClassName = Button.class.getName(); state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index b927134842df9527e58c62ee6840dac57e245867..a4fe4e3e12431bc955f9ea0ae9dc2cb12dddb2bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -49,7 +49,7 @@ import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.RefactorFlagUtils; +import com.android.systemui.modes.shared.ModesUi; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; @@ -108,8 +108,7 @@ public class DndTile extends QSTileImpl { statusBarStateController, activityStarter, qsLogger); // If the flag is on, this shouldn't run at all since the modes tile replaces the DND tile. - RefactorFlagUtils.INSTANCE.assertInLegacyMode(android.app.Flags.modesUi(), - android.app.Flags.FLAG_MODES_UI); + ModesUi.assertInLegacyMode(); mController = zenModeController; mSharedPreferences = sharedPreferences; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index 078698c2872dead259934c55c8c1402afed92f0c..7606293454f8cacdfd3f7a4add16619caf89e80f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -55,7 +55,7 @@ constructor( qsLogger: QSLogger, private val keyguardStateController: KeyguardStateController, private val dialogTransitionAnimator: DialogTransitionAnimator, - private val fontScalingDialogDelegateProvider: Provider + private val fontScalingDialogDelegateProvider: Provider, ) : QSTileImpl( host, @@ -66,7 +66,7 @@ constructor( metricsLogger, statusBarStateController, activityStarter, - qsLogger + qsLogger, ) { private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling) @@ -86,7 +86,7 @@ constructor( expandable?.dialogTransitionController( DialogCuj( InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG + INTERACTION_JANK_TAG, ) ) controller?.let { dialogTransitionAnimator.show(dialog, controller) } @@ -102,7 +102,7 @@ constructor( /* cancelAction= */ null, /* dismissShade= */ true, /* afterKeyguardGone= */ true, - /* deferred= */ false + /* deferred= */ false, ) } } @@ -110,6 +110,7 @@ constructor( override fun handleUpdateState(state: QSTile.State?, arg: Any?) { state?.label = mContext.getString(R.string.quick_settings_font_scaling_label) state?.icon = icon + state?.contentDescription = state?.label } override fun getLongClickIntent(): Intent? { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt index 7d23fbdf2ece58649cecfbfb0b87f8c2565f57eb..cf2db6c66ce71866e1d4f3f58e0c2e217e99437a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles -import android.app.Flags import android.content.Intent import android.os.Handler import android.os.Looper @@ -30,7 +29,8 @@ import com.android.internal.logging.MetricsLogger import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.RefactorFlagUtils.isUnexpectedlyInLegacyMode +import com.android.systemui.modes.shared.ModesUi +import com.android.systemui.modes.shared.ModesUiIcons import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile @@ -77,14 +77,14 @@ constructor( metricsLogger, statusBarStateController, activityStarter, - qsLogger + qsLogger, ) { private lateinit var tileState: QSTileState private val config = qsTileConfigProvider.getConfig(TILE_SPEC) init { - /* Check if */ isUnexpectedlyInLegacyMode(Flags.modesUi(), Flags.FLAG_MODES_UI) + /* Check if */ ModesUiIcons.isUnexpectedlyInLegacyMode() lifecycle.coroutineScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { @@ -93,7 +93,7 @@ constructor( } } - override fun isAvailable(): Boolean = Flags.modesUi() + override fun isAvailable(): Boolean = ModesUi.isEnabled override fun getTileLabel(): CharSequence = tileState.label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt index d0437a7210f169d817921179bae063f50479134c..b8f4ab40bb1d77632267081c5f46a3dd6c700bd3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.base.viewmodel +import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -27,5 +28,5 @@ class QSTileCoroutineScopeFactory constructor(@Application private val applicationScope: CoroutineScope) { fun create(): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob()) + CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("QSTileScope")) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt index 246fe3883e1922f95a09e0c2747b62c0ec350be2..ae56c2aad4e9e6776f13361477d4c2cae65c859e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.qs.tiles.dialog +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj @@ -62,7 +63,7 @@ constructor( } return } else { - coroutineScope = CoroutineScope(bgDispatcher) + coroutineScope = CoroutineScope(bgDispatcher + createCoroutineTracingContext("InternetDialogScope")) dialog = dialogFactory .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt index cca947ff7e77a86300279d9539abf02a9db794cc..ac75932b8fee57560b20543424dc18df864c4024 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.location.domain.interactor +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.content.Intent import android.provider.Settings import com.android.systemui.dagger.qualifiers.Application @@ -52,7 +53,7 @@ constructor( val wasEnabled: Boolean = input.data.isEnabled if (keyguardController.isMethodSecure() && keyguardController.isShowing()) { activityStarter.postQSRunnableDismissingKeyguard { - CoroutineScope(applicationScope.coroutineContext).launch { + CoroutineScope(applicationScope.coroutineContext + createCoroutineTracingContext("LocationTileScope")).launch { locationController.setLocationEnabled(!wasEnabled) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index 483373d8fb6d87100c5dcde8948da61b98c46bbe..5d44ead7c21a1dc260baafa07b58e1f07acee4d9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -16,13 +16,14 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor -import android.app.Flags import android.content.Context import android.os.UserHandle import com.android.app.tracing.coroutines.flow.map import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.modes.shared.ModesUi +import com.android.systemui.modes.shared.ModesUiIcons import com.android.systemui.qs.tiles.ModesTile import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor @@ -47,7 +48,7 @@ constructor( override fun tileData( user: UserHandle, - triggers: Flow + triggers: Flow, ): Flow = tileData() /** @@ -64,20 +65,20 @@ constructor( suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes()) private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel { - if (usesModeIcons()) { + if (ModesUiIcons.isEnabled) { val tileIcon = getTileIcon(activeModes.mainMode) return ModesTileModel( isActivated = activeModes.isAnyActive(), icon = tileIcon.icon, iconResId = tileIcon.resId, - activeModes = activeModes.modeNames + activeModes = activeModes.modeNames, ) } else { return ModesTileModel( isActivated = activeModes.isAnyActive(), icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), iconResId = ModesTile.ICON_RES_ID, - activeModes = activeModes.modeNames + activeModes = activeModes.modeNames, ) } } @@ -97,7 +98,5 @@ constructor( } } - override fun availability(user: UserHandle): Flow = flowOf(Flags.modesUi()) - - private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() + override fun availability(user: UserHandle): Flow = flowOf(ModesUi.isEnabled) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt index b25c61cba2b74bd2c2708c6ef82db1ceb326f114..468e180a6e41684c185f501005c18fc7d715bd49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.saver.domain +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences @@ -44,7 +45,7 @@ class DataSaverDialogDelegate( setTitle(R.string.data_saver_enable_title) setMessage(R.string.data_saver_description) setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ -> - CoroutineScope(backgroundContext).launch { + CoroutineScope(backgroundContext + createCoroutineTracingContext("DataSaverDialogScope")).launch { dataSaverController.setDataSaverEnabled(true) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index e11ffccb0be3e0ad9ac16abc4f46a5cc2b033a30..286cac10fa05320651fbe14dee546968bd4ed59a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -61,6 +61,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.session.shared.SessionStorage import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.logger.SceneLogger +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.NotificationShadeWindowController @@ -192,22 +193,16 @@ constructor( // We are in a session if either Shade or QuickSettings is on the back stack .map { backStack -> backStack.asIterable().any { + // TODO(b/356596436): Include overlays in the back stack as well. it == Scenes.Shade || it == Scenes.QuickSettings } } .distinctUntilChanged(), - sceneInteractor.transitionState - .mapNotNull { state -> - // We are also in a session if either Shade or QuickSettings is the - // current scene - when (state) { - is ObservableTransitionState.Idle -> state.currentScene - is ObservableTransitionState.Transition -> state.fromContent - }.let { it == Scenes.Shade || it == Scenes.QuickSettings } - } - .distinctUntilChanged(), - ) { inBackStack, isCurrentScene -> - inBackStack || isCurrentScene + // We are also in a session if either Notifications Shade or QuickSettings Shade + // is currently shown (whether idle or animating). + shadeInteractor.isAnyExpanded, + ) { inBackStack, isShadeShown -> + inBackStack || isShadeShown } // Once a session has ended, clear the session storage. .filter { inSession -> !inSession } @@ -228,8 +223,10 @@ constructor( is ObservableTransitionState.Idle -> { if (state.currentScene != Scenes.Gone) { true to "scene is not Gone" + } else if (state.currentOverlays.isNotEmpty()) { + true to "overlay is shown" } else { - false to "scene is Gone" + false to "scene is Gone and no overlays are shown" } } is ObservableTransitionState.Transition -> { @@ -712,19 +709,21 @@ constructor( if (isDeviceLocked) { sceneInteractor.transitionState .mapNotNull { it as? ObservableTransitionState.Idle } - .map { it.currentScene } + .map { it.currentScene to it.currentOverlays } .distinctUntilChanged() - .map { sceneKey -> - when (sceneKey) { + .map { (sceneKey, currentOverlays) -> + when { // When locked, showing the lockscreen scene should be reported // as "interacting" while showing other scenes should report as // "not interacting". // // This is done here in order to match the legacy // implementation. The real reason why is lost to lore and myth. - Scenes.Lockscreen -> true - Scenes.Bouncer -> false - Scenes.Shade -> false + Overlays.NotificationsShade in currentOverlays -> false + Overlays.QuickSettingsShade in currentOverlays -> null + sceneKey == Scenes.Lockscreen -> true + sceneKey == Scenes.Bouncer -> false + sceneKey == Scenes.Shade -> false else -> null } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index 751448fe607eedd2e2bcabef1f93b7b5143e0961..7b6b0f614cc247df06ea1e7850468f200ec86cb7 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -26,7 +26,6 @@ import com.android.systemui.flags.RefactorFlagUtils import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag @@ -39,7 +38,6 @@ object SceneContainerFlag { inline val isEnabled get() = sceneContainer() && // mainAconfigFlag - ComposeLockscreen.isEnabled && KeyguardBottomAreaRefactor.isEnabled && KeyguardWmStateRefactor.isEnabled && MigrateClocksToBlueprint.isEnabled && @@ -55,7 +53,6 @@ object SceneContainerFlag { /** The set of secondary flags which must be enabled for scene container to work properly */ inline fun getSecondaryFlags(): Sequence = sequenceOf( - ComposeLockscreen.token, KeyguardBottomAreaRefactor.token, KeyguardWmStateRefactor.token, MigrateClocksToBlueprint.token, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java index f69b0cb630d390528f5d9f0827a2752b70843817..7724abd4aaacdaea95f81db44784781e2eca377a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java @@ -505,8 +505,8 @@ public class LegacyScreenshotController implements InteractiveScreenshotHandler return; } // delay starting scroll capture to make sure scrim is up before the app moves - mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, - mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner)); + mViewProxy.prepareScrollingTransition(response, newScreenshot, mScreenshotTakenInPortrait, + () -> executeBatchScrollCapture(response, owner)); } private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java deleted file mode 100644 index fe58bc9f34a92129d20150df086afaca1894e81d..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ /dev/null @@ -1,663 +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.systemui.screenshot; - -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; - -import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; -import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; -import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; -import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; -import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; -import static com.android.systemui.screenshot.LogConfig.logTag; -import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER; -import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Insets; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Process; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.ScrollCaptureResponse; -import android.view.ViewRootImpl; -import android.view.WindowManager; -import android.widget.Toast; -import android.window.WindowContext; - -import com.android.internal.logging.UiEventLogger; -import com.android.settingslib.applications.InterestingConfigChanges; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.broadcast.BroadcastSender; -import com.android.systemui.clipboardoverlay.ClipboardOverlayController; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.res.R; -import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; -import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor; -import com.android.systemui.util.Assert; - -import com.google.common.util.concurrent.ListenableFuture; - -import dagger.assisted.Assisted; -import dagger.assisted.AssistedFactory; -import dagger.assisted.AssistedInject; - -import kotlin.Unit; - -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; - -import javax.inject.Provider; - -/** - * Controls the state and flow for screenshots. - */ -public class ScreenshotController implements InteractiveScreenshotHandler { - private static final String TAG = logTag(ScreenshotController.class); - - // From WizardManagerHelper.java - private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; - - static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; - - private final WindowContext mContext; - private final FeatureFlags mFlags; - private final ScreenshotShelfViewProxy mViewProxy; - private final ScreenshotNotificationsController mNotificationsController; - private final ScreenshotSmartActions mScreenshotSmartActions; - private final UiEventLogger mUiEventLogger; - private final ImageExporter mImageExporter; - private final ImageCapture mImageCapture; - private final Executor mMainExecutor; - private final ExecutorService mBgExecutor; - private final BroadcastSender mBroadcastSender; - private final BroadcastDispatcher mBroadcastDispatcher; - private final ScreenshotActionsController mActionsController; - - @Nullable - private final ScreenshotSoundController mScreenshotSoundController; - private final ScreenshotWindow mWindow; - private final Display mDisplay; - private final ScrollCaptureExecutor mScrollCaptureExecutor; - private final ScreenshotNotificationSmartActionsProvider - mScreenshotNotificationSmartActionsProvider; - private final TimeoutHandler mScreenshotHandler; - private final UserManager mUserManager; - private final AssistContentRequester mAssistContentRequester; - private final ActionExecutor mActionExecutor; - - - private final MessageContainerController mMessageContainerController; - private final AnnouncementResolver mAnnouncementResolver; - private Bitmap mScreenBitmap; - private boolean mScreenshotTakenInPortrait; - private Animator mScreenshotAnimation; - private RequestCallback mCurrentRequestCallback; - private String mPackageName = ""; - private final BroadcastReceiver mCopyBroadcastReceiver; - - /** Tracks config changes that require re-creating UI */ - private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( - ActivityInfo.CONFIG_ORIENTATION - | ActivityInfo.CONFIG_LAYOUT_DIRECTION - | ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_UI_MODE - | ActivityInfo.CONFIG_SCREEN_LAYOUT - | ActivityInfo.CONFIG_ASSETS_PATHS); - - - @AssistedInject - ScreenshotController( - Context context, - ScreenshotWindow.Factory screenshotWindowFactory, - FeatureFlags flags, - ScreenshotShelfViewProxy.Factory viewProxyFactory, - ScreenshotSmartActions screenshotSmartActions, - ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory, - UiEventLogger uiEventLogger, - ImageExporter imageExporter, - ImageCapture imageCapture, - @Main Executor mainExecutor, - ScrollCaptureExecutor scrollCaptureExecutor, - TimeoutHandler timeoutHandler, - BroadcastSender broadcastSender, - BroadcastDispatcher broadcastDispatcher, - ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, - ScreenshotActionsController.Factory screenshotActionsControllerFactory, - ActionExecutor.Factory actionExecutorFactory, - UserManager userManager, - AssistContentRequester assistContentRequester, - MessageContainerController messageContainerController, - Provider screenshotSoundController, - AnnouncementResolver announcementResolver, - @Assisted Display display - ) { - mScreenshotSmartActions = screenshotSmartActions; - mNotificationsController = screenshotNotificationsControllerFactory.create( - display.getDisplayId()); - mUiEventLogger = uiEventLogger; - mImageExporter = imageExporter; - mImageCapture = imageCapture; - mMainExecutor = mainExecutor; - mScrollCaptureExecutor = scrollCaptureExecutor; - mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider; - mBgExecutor = Executors.newSingleThreadExecutor(); - mBroadcastSender = broadcastSender; - mBroadcastDispatcher = broadcastDispatcher; - - mScreenshotHandler = timeoutHandler; - mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); - - mDisplay = display; - mWindow = screenshotWindowFactory.create(mDisplay); - mContext = mWindow.getContext(); - mFlags = flags; - mUserManager = userManager; - mMessageContainerController = messageContainerController; - mAssistContentRequester = assistContentRequester; - mAnnouncementResolver = announcementResolver; - - mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId()); - - mScreenshotHandler.setOnTimeoutRunnable(() -> { - if (DEBUG_UI) { - Log.d(TAG, "Corner timeout hit"); - } - mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT); - }); - - mConfigChanges.applyNewConfig(context.getResources()); - reloadAssets(); - - mActionExecutor = actionExecutorFactory.create(mWindow.getWindow(), mViewProxy, - () -> { - finishDismiss(); - return Unit.INSTANCE; - }); - mActionsController = screenshotActionsControllerFactory.getController(mActionExecutor); - - - // Sound is only reproduced from the controller of the default display. - if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) { - mScreenshotSoundController = screenshotSoundController.get(); - } else { - mScreenshotSoundController = null; - } - - mCopyBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) { - mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER); - } - } - }; - mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( - ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null, - Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION); - } - - @Override - public void handleScreenshot(ScreenshotData screenshot, Consumer finisher, - RequestCallback requestCallback) { - Assert.isMainThread(); - - mCurrentRequestCallback = requestCallback; - if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN - && screenshot.getBitmap() == null) { - Rect bounds = getFullScreenRect(); - screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds)); - screenshot.setScreenBounds(bounds); - } - - if (screenshot.getBitmap() == null) { - Log.e(TAG, "handleScreenshot: Screenshot bitmap was null"); - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - if (mCurrentRequestCallback != null) { - mCurrentRequestCallback.reportError(); - } - return; - } - - mScreenBitmap = screenshot.getBitmap(); - String oldPackageName = mPackageName; - mPackageName = screenshot.getPackageNameString(); - - if (!isUserSetupComplete(Process.myUserHandle())) { - Log.w(TAG, "User setup not complete, displaying toast only"); - // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing - // and sharing shouldn't be exposed to the user. - saveScreenshotAndToast(screenshot, finisher); - return; - } - - mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), - ClipboardOverlayController.SELF_PERMISSION); - - mScreenshotTakenInPortrait = - mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; - - // Optimizations - mScreenBitmap.setHasAlpha(false); - mScreenBitmap.prepareToDraw(); - - prepareViewForNewScreenshot(screenshot, oldPackageName); - - final UUID requestId; - requestId = mActionsController.setCurrentScreenshot(screenshot); - saveScreenshotInBackground(screenshot, requestId, finisher, result -> { - if (result.uri != null) { - ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult( - result.uri, screenshot.getUserOrDefault(), result.timestamp); - mActionsController.setCompletedScreenshot(requestId, savedScreenshot); - } - }); - - if (screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent( - screenshot.getTaskId(), - assistContent -> - mActionsController.onAssistContent(requestId, assistContent)); - } else { - mActionsController.onAssistContent(requestId, null); - } - - // The window is focusable by default - mWindow.setFocusable(true); - mViewProxy.requestFocus(); - - enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); - - mWindow.attachWindow(); - - boolean showFlash; - if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { - if (screenshot.getScreenBounds() != null - && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), - screenshot.getScreenBounds())) { - showFlash = false; - } else { - showFlash = true; - screenshot.setInsets(Insets.NONE); - screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(), - screenshot.getBitmap().getHeight())); - } - } else { - showFlash = true; - } - - mViewProxy.prepareEntranceAnimation( - () -> startAnimation(screenshot.getScreenBounds(), showFlash, - () -> mMessageContainerController.onScreenshotTaken(screenshot))); - - mViewProxy.setScreenshot(screenshot); - - } - - void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) { - mWindow.whenWindowAttached(() -> { - mAnnouncementResolver.getScreenshotAnnouncement( - screenshot.getUserHandle().getIdentifier(), - announcement -> { - mViewProxy.announceForAccessibility(announcement); - }); - }); - - mViewProxy.reset(); - - if (mViewProxy.isAttachedToWindow()) { - // if we didn't already dismiss for another reason - if (!mViewProxy.isDismissing()) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, - oldPackageName); - } - if (DEBUG_WINDOW) { - Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " - + "(dismissing=" + mViewProxy.isDismissing() + ")"); - } - } - - mViewProxy.setPackageName(mPackageName); - } - - /** - * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already - * being dismissed) - */ - @Override - public void requestDismissal(ScreenshotEvent event) { - mViewProxy.requestDismissal(event); - } - - @Override - public boolean isPendingSharedTransition() { - return mActionExecutor.isPendingSharedTransition(); - } - - // Any cleanup needed when the service is being destroyed. - @Override - public void onDestroy() { - removeWindow(); - releaseMediaPlayer(); - releaseContext(); - mBgExecutor.shutdown(); - } - - /** - * Release the constructed window context. - */ - private void releaseContext() { - mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver); - mContext.release(); - } - - private void releaseMediaPlayer() { - if (mScreenshotSoundController == null) return; - mScreenshotSoundController.releaseScreenshotSoundAsync(); - } - - /** - * Update resources on configuration change. Reinflate for theme/color changes. - */ - private void reloadAssets() { - if (DEBUG_UI) { - Log.d(TAG, "reloadAssets()"); - } - - mMessageContainerController.setView(mViewProxy.getView()); - mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() { - @Override - public void onUserInteraction() { - if (DEBUG_INPUT) { - Log.d(TAG, "onUserInteraction"); - } - mScreenshotHandler.resetTimeout(); - } - - @Override - public void onDismiss() { - finishDismiss(); - } - - @Override - public void onTouchOutside() { - // TODO(159460485): Remove this when focus is handled properly in the system - mWindow.setFocusable(false); - } - }); - - if (DEBUG_WINDOW) { - Log.d(TAG, "setContentView: " + mViewProxy.getView()); - } - mWindow.setContentView(mViewProxy.getView()); - } - - private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) { - // Wait until this window is attached to request because it is - // the reference used to locate the target window (below). - mWindow.whenWindowAttached(() -> { - requestScrollCapture(requestId, owner); - mWindow.setActivityConfigCallback( - new ViewRootImpl.ActivityConfigCallback() { - @Override - public void onConfigurationChanged(Configuration overrideConfig, - int newDisplayId) { - if (mConfigChanges.applyNewConfig(mContext.getResources())) { - // Hide the scroll chip until we know it's available in this - // orientation - mActionsController.onScrollChipInvalidated(); - // Delay scroll capture eval a bit to allow the underlying activity - // to set up in the new orientation. - mScreenshotHandler.postDelayed( - () -> requestScrollCapture(requestId, owner), 150); - mViewProxy.updateInsets(mWindow.getWindowInsets()); - // Screenshot animation calculations won't be valid anymore, - // so just end - if (mScreenshotAnimation != null - && mScreenshotAnimation.isRunning()) { - mScreenshotAnimation.end(); - } - } - } - }); - }); - } - - private void requestScrollCapture(UUID requestId, UserHandle owner) { - mScrollCaptureExecutor.requestScrollCapture( - mDisplay.getDisplayId(), - mWindow.getWindowToken(), - (response) -> { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, - 0, response.getPackageName()); - mActionsController.onScrollChipReady(requestId, - () -> onScrollButtonClicked(owner, response)); - return Unit.INSTANCE; - } - ); - } - - private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) { - if (DEBUG_INPUT) { - Log.d(TAG, "scroll chip tapped"); - } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0, - response.getPackageName()); - Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplay.getDisplayId(), - getFullScreenRect()); - if (newScreenshot == null) { - Log.e(TAG, "Failed to capture current screenshot for scroll transition!"); - return; - } - // delay starting scroll capture to make sure scrim is up before the app moves - mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, - mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner)); - } - - private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) { - mScrollCaptureExecutor.executeBatchScrollCapture(response, - () -> { - final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent( - owner, mContext); - mContext.startActivity(intent); - }, - mViewProxy::restoreNonScrollingUi, - mViewProxy::startLongScreenshotTransition); - } - - @Override - public void removeWindow() { - mWindow.removeWindow(); - mViewProxy.stopInputListening(); - } - - private void playCameraSoundIfNeeded() { - if (mScreenshotSoundController == null) return; - // the controller is not-null only on the default display controller - mScreenshotSoundController.playScreenshotSoundAsync(); - } - - /** - * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on - * failure). - */ - private void saveScreenshotAndToast(ScreenshotData screenshot, Consumer finisher) { - // Play the shutter sound to notify that we've taken a screenshot - playCameraSoundIfNeeded(); - - saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> { - if (result.uri != null) { - mScreenshotHandler.post(() -> Toast.makeText(mContext, - R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show()); - } - }); - } - - /** - * Starts the animation after taking the screenshot - */ - private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) { - if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { - mScreenshotAnimation.cancel(); - } - - mScreenshotAnimation = - mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash); - if (onAnimationComplete != null) { - mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - onAnimationComplete.run(); - } - }); - } - - // Play the shutter sound to notify that we've taken a screenshot - playCameraSoundIfNeeded(); - - if (DEBUG_ANIM) { - Log.d(TAG, "starting post-screenshot animation"); - } - mScreenshotAnimation.start(); - } - - /** Reset screenshot view and then call onCompleteRunnable */ - private void finishDismiss() { - Log.d(TAG, "finishDismiss"); - mActionsController.endScreenshotSession(); - mScrollCaptureExecutor.close(); - if (mCurrentRequestCallback != null) { - mCurrentRequestCallback.onFinish(); - mCurrentRequestCallback = null; - } - mViewProxy.reset(); - removeWindow(); - mScreenshotHandler.cancelTimeout(); - } - - private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId, - Consumer finisher, Consumer onResult) { - ListenableFuture future = mImageExporter.export(mBgExecutor, - requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), - mDisplay.getDisplayId()); - future.addListener(() -> { - try { - ImageExporter.Result result = future.get(); - Log.d(TAG, "Saved screenshot: " + result); - logScreenshotResultStatus(result.uri, screenshot.getUserHandle()); - onResult.accept(result); - if (DEBUG_CALLBACK) { - Log.d(TAG, "finished background processing, Calling (Consumer) " - + "finisher.accept(\"" + result.uri + "\""); - } - finisher.accept(result.uri); - } catch (Exception e) { - Log.d(TAG, "Failed to store screenshot", e); - if (DEBUG_CALLBACK) { - Log.d(TAG, "Calling (Consumer) finisher.accept(null)"); - } - finisher.accept(null); - } - }, mMainExecutor); - } - - /** - * Logs success/failure of the screenshot saving task, and shows an error if it failed. - */ - private void logScreenshotResultStatus(Uri uri, UserHandle owner) { - if (uri == null) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName); - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_save_text); - } else { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); - if (mUserManager.isManagedProfile(owner.getIdentifier())) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, - mPackageName); - } - } - } - - private boolean isUserSetupComplete(UserHandle owner) { - return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) - .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; - } - - private Rect getFullScreenRect() { - DisplayMetrics displayMetrics = new DisplayMetrics(); - mDisplay.getRealMetrics(displayMetrics); - return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels); - } - - /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ - private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, - Rect screenBounds) { - int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; - int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom; - - if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 - || bitmap.getHeight() == 0) { - if (DEBUG_UI) { - Log.e(TAG, "Provided bitmap and insets create degenerate region: " - + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets); - } - return false; - } - - float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight; - float boundsAspect = ((float) screenBounds.width()) / screenBounds.height(); - - boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f; - if (DEBUG_UI) { - Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect - + ", bounds: " + boundsAspect); - } - return matchWithinTolerance; - } - - /** Injectable factory to create screenshot controller instances for a specific display. */ - @AssistedFactory - public interface Factory extends InteractiveScreenshotHandler.Factory { - /** - * Creates an instance of the controller for that specific display. - * - * @param display display to capture - */ - ScreenshotController create(Display display); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt new file mode 100644 index 0000000000000000000000000000000000000000..29208f89c4e1ce345b0fe70fbe7dd1d75d859928 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot + +import android.animation.Animator +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Insets +import android.graphics.Rect +import android.net.Uri +import android.os.Process +import android.os.UserHandle +import android.os.UserManager +import android.provider.Settings +import android.util.DisplayMetrics +import android.util.Log +import android.view.Display +import android.view.ScrollCaptureResponse +import android.view.ViewRootImpl.ActivityConfigCallback +import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN +import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE +import android.widget.Toast +import android.window.WindowContext +import androidx.core.animation.doOnEnd +import com.android.internal.logging.UiEventLogger +import com.android.settingslib.applications.InterestingConfigChanges +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.clipboardoverlay.ClipboardOverlayController +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.screenshot.ActionIntentCreator.createLongScreenshotIntent +import com.android.systemui.screenshot.ScreenshotShelfViewProxy.ScreenshotViewCallback +import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot +import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor +import com.android.systemui.util.Assert +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.UUID +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.function.Consumer +import javax.inject.Provider +import kotlin.math.abs + +/** Controls the state and flow for screenshots. */ +class ScreenshotController +@AssistedInject +internal constructor( + appContext: Context, + screenshotWindowFactory: ScreenshotWindow.Factory, + viewProxyFactory: ScreenshotShelfViewProxy.Factory, + screenshotNotificationsControllerFactory: ScreenshotNotificationsController.Factory, + screenshotActionsControllerFactory: ScreenshotActionsController.Factory, + actionExecutorFactory: ActionExecutor.Factory, + screenshotSoundControllerProvider: Provider, + private val uiEventLogger: UiEventLogger, + private val imageExporter: ImageExporter, + private val imageCapture: ImageCapture, + private val scrollCaptureExecutor: ScrollCaptureExecutor, + private val screenshotHandler: TimeoutHandler, + private val broadcastSender: BroadcastSender, + private val broadcastDispatcher: BroadcastDispatcher, + private val userManager: UserManager, + private val assistContentRequester: AssistContentRequester, + private val messageContainerController: MessageContainerController, + private val announcementResolver: AnnouncementResolver, + @Main private val mainExecutor: Executor, + @Assisted private val display: Display, +) : InteractiveScreenshotHandler { + private val context: WindowContext + private val viewProxy: ScreenshotShelfViewProxy + private val notificationController = + screenshotNotificationsControllerFactory.create(display.displayId) + private val bgExecutor: ExecutorService = Executors.newSingleThreadExecutor() + private val actionsController: ScreenshotActionsController + private val window: ScreenshotWindow + private val actionExecutor: ActionExecutor + private val copyBroadcastReceiver: BroadcastReceiver + + private var screenshotSoundController: ScreenshotSoundController? = null + private var screenBitmap: Bitmap? = null + private var screenshotTakenInPortrait = false + private var screenshotAnimation: Animator? = null + private var currentRequestCallback: TakeScreenshotService.RequestCallback? = null + private var packageName = "" + + /** Tracks config changes that require re-creating UI */ + private val configChanges = + InterestingConfigChanges( + ActivityInfo.CONFIG_ORIENTATION or + ActivityInfo.CONFIG_LAYOUT_DIRECTION or + ActivityInfo.CONFIG_LOCALE or + ActivityInfo.CONFIG_UI_MODE or + ActivityInfo.CONFIG_SCREEN_LAYOUT or + ActivityInfo.CONFIG_ASSETS_PATHS + ) + + init { + screenshotHandler.defaultTimeoutMillis = SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS + + window = screenshotWindowFactory.create(display) + context = window.getContext() + + viewProxy = viewProxyFactory.getProxy(context, display.displayId) + + screenshotHandler.setOnTimeoutRunnable { + if (LogConfig.DEBUG_UI) { + Log.d(TAG, "Corner timeout hit") + } + viewProxy.requestDismissal(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT) + } + + configChanges.applyNewConfig(appContext.resources) + reloadAssets() + + actionExecutor = actionExecutorFactory.create(window.window, viewProxy) { finishDismiss() } + actionsController = screenshotActionsControllerFactory.getController(actionExecutor) + + // Sound is only reproduced from the controller of the default display. + screenshotSoundController = + if (display.displayId == Display.DEFAULT_DISPLAY) { + screenshotSoundControllerProvider.get() + } else { + null + } + + copyBroadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (ClipboardOverlayController.COPY_OVERLAY_ACTION == intent.action) { + viewProxy.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) + } + } + } + broadcastDispatcher.registerReceiver( + copyBroadcastReceiver, + IntentFilter(ClipboardOverlayController.COPY_OVERLAY_ACTION), + null, + null, + Context.RECEIVER_NOT_EXPORTED, + ClipboardOverlayController.SELF_PERMISSION, + ) + } + + override fun handleScreenshot( + screenshot: ScreenshotData, + finisher: Consumer, + requestCallback: TakeScreenshotService.RequestCallback, + ) { + Assert.isMainThread() + + currentRequestCallback = requestCallback + if (screenshot.type == TAKE_SCREENSHOT_FULLSCREEN && screenshot.bitmap == null) { + val bounds = fullScreenRect + screenshot.bitmap = imageCapture.captureDisplay(display.displayId, bounds) + screenshot.screenBounds = bounds + } + + val currentBitmap = screenshot.bitmap + if (currentBitmap == null) { + Log.e(TAG, "handleScreenshot: Screenshot bitmap was null") + notificationController.notifyScreenshotError(R.string.screenshot_failed_to_capture_text) + currentRequestCallback?.reportError() + return + } + + screenBitmap = currentBitmap + val oldPackageName = packageName + packageName = screenshot.packageNameString + + if (!isUserSetupComplete(Process.myUserHandle())) { + Log.w(TAG, "User setup not complete, displaying toast only") + // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing + // and sharing shouldn't be exposed to the user. + saveScreenshotAndToast(screenshot, finisher) + return + } + + broadcastSender.sendBroadcast( + Intent(ClipboardOverlayController.SCREENSHOT_ACTION), + ClipboardOverlayController.SELF_PERMISSION, + ) + + screenshotTakenInPortrait = + context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT + + // Optimizations + currentBitmap.setHasAlpha(false) + currentBitmap.prepareToDraw() + + prepareViewForNewScreenshot(screenshot, oldPackageName) + val requestId = actionsController.setCurrentScreenshot(screenshot) + saveScreenshotInBackground(screenshot, requestId, finisher) { result -> + if (result.uri != null) { + val savedScreenshot = + ScreenshotSavedResult( + result.uri, + screenshot.getUserOrDefault(), + result.timestamp, + ) + actionsController.setCompletedScreenshot(requestId, savedScreenshot) + } + } + + if (screenshot.taskId >= 0) { + assistContentRequester.requestAssistContent(screenshot.taskId) { assistContent -> + actionsController.onAssistContent(requestId, assistContent) + } + } else { + actionsController.onAssistContent(requestId, null) + } + + // The window is focusable by default + window.setFocusable(true) + viewProxy.requestFocus() + + enqueueScrollCaptureRequest(requestId, screenshot.userHandle!!) + + window.attachWindow() + + val showFlash: Boolean + if (screenshot.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) { + if (aspectRatiosMatch(currentBitmap, screenshot.insets, screenshot.screenBounds)) { + showFlash = false + } else { + showFlash = true + screenshot.insets = Insets.NONE + screenshot.screenBounds = Rect(0, 0, currentBitmap.width, currentBitmap.height) + } + } else { + showFlash = true + } + + // screenshot.screenBounds is expected to be non-null in all cases at this point + val bounds = + screenshot.screenBounds ?: Rect(0, 0, currentBitmap.width, currentBitmap.height) + + viewProxy.prepareEntranceAnimation { + startAnimation(bounds, showFlash) { + messageContainerController.onScreenshotTaken(screenshot) + } + } + + viewProxy.screenshot = screenshot + } + + private fun prepareViewForNewScreenshot(screenshot: ScreenshotData, oldPackageName: String?) { + window.whenWindowAttached { + announcementResolver.getScreenshotAnnouncement(screenshot.userHandle!!.identifier) { + viewProxy.announceForAccessibility(it) + } + } + + viewProxy.reset() + + if (viewProxy.isAttachedToWindow) { + // if we didn't already dismiss for another reason + if (!viewProxy.isDismissing) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, oldPackageName) + } + if (LogConfig.DEBUG_WINDOW) { + Log.d( + TAG, + "saveScreenshot: screenshotView is already attached, resetting. " + + "(dismissing=${viewProxy.isDismissing})", + ) + } + } + + viewProxy.packageName = packageName + } + + /** + * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already + * being dismissed) + */ + override fun requestDismissal(event: ScreenshotEvent) { + viewProxy.requestDismissal(event) + } + + override fun isPendingSharedTransition(): Boolean { + return actionExecutor.isPendingSharedTransition + } + + // Any cleanup needed when the service is being destroyed. + override fun onDestroy() { + removeWindow() + releaseMediaPlayer() + releaseContext() + bgExecutor.shutdown() + } + + /** Release the constructed window context. */ + private fun releaseContext() { + broadcastDispatcher.unregisterReceiver(copyBroadcastReceiver) + context.release() + } + + private fun releaseMediaPlayer() { + screenshotSoundController?.releaseScreenshotSoundAsync() + } + + /** Update resources on configuration change. Reinflate for theme/color changes. */ + private fun reloadAssets() { + if (LogConfig.DEBUG_UI) { + Log.d(TAG, "reloadAssets()") + } + + messageContainerController.setView(viewProxy.view) + viewProxy.callbacks = + object : ScreenshotViewCallback { + override fun onUserInteraction() { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "onUserInteraction") + } + screenshotHandler.resetTimeout() + } + + override fun onDismiss() { + finishDismiss() + } + + override fun onTouchOutside() { + // TODO(159460485): Remove this when focus is handled properly in the system + window.setFocusable(false) + } + } + + if (LogConfig.DEBUG_WINDOW) { + Log.d(TAG, "setContentView: " + viewProxy.view) + } + window.setContentView(viewProxy.view) + } + + private fun enqueueScrollCaptureRequest(requestId: UUID, owner: UserHandle) { + // Wait until this window is attached to request because it is + // the reference used to locate the target window (below). + window.whenWindowAttached { + requestScrollCapture(requestId, owner) + window.setActivityConfigCallback( + object : ActivityConfigCallback { + override fun onConfigurationChanged( + overrideConfig: Configuration, + newDisplayId: Int, + ) { + if (configChanges.applyNewConfig(context.resources)) { + // Hide the scroll chip until we know it's available in this + // orientation + actionsController.onScrollChipInvalidated() + // Delay scroll capture eval a bit to allow the underlying activity + // to set up in the new orientation. + screenshotHandler.postDelayed( + { requestScrollCapture(requestId, owner) }, + 150, + ) + viewProxy.updateInsets(window.getWindowInsets()) + // Screenshot animation calculations won't be valid anymore, so just end + screenshotAnimation?.let { currentAnimation -> + if (currentAnimation.isRunning) { + currentAnimation.end() + } + } + } + } + } + ) + } + } + + private fun requestScrollCapture(requestId: UUID, owner: UserHandle) { + scrollCaptureExecutor.requestScrollCapture(display.displayId, window.getWindowToken()) { + response: ScrollCaptureResponse -> + uiEventLogger.log( + ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, + 0, + response.packageName, + ) + actionsController.onScrollChipReady(requestId) { + onScrollButtonClicked(owner, response) + } + } + } + + private fun onScrollButtonClicked(owner: UserHandle, response: ScrollCaptureResponse) { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "scroll chip tapped") + } + uiEventLogger.log( + ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, + 0, + response.packageName, + ) + val newScreenshot = imageCapture.captureDisplay(display.displayId, null) + if (newScreenshot == null) { + Log.e(TAG, "Failed to capture current screenshot for scroll transition!") + return + } + // delay starting scroll capture to make sure scrim is up before the app moves + viewProxy.prepareScrollingTransition(response, newScreenshot, screenshotTakenInPortrait) { + executeBatchScrollCapture(response, owner) + } + } + + private fun executeBatchScrollCapture(response: ScrollCaptureResponse, owner: UserHandle) { + scrollCaptureExecutor.executeBatchScrollCapture( + response, + { + val intent = createLongScreenshotIntent(owner, context) + context.startActivity(intent) + }, + { viewProxy.restoreNonScrollingUi() }, + { transitionDestination: Rect, onTransitionEnd: Runnable, longScreenshot: LongScreenshot + -> + viewProxy.startLongScreenshotTransition( + transitionDestination, + onTransitionEnd, + longScreenshot, + ) + }, + ) + } + + override fun removeWindow() { + window.removeWindow() + viewProxy.stopInputListening() + } + + private fun playCameraSoundIfNeeded() { + // the controller is not-null only on the default display controller + screenshotSoundController?.playScreenshotSoundAsync() + } + + /** + * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on + * failure). + */ + private fun saveScreenshotAndToast(screenshot: ScreenshotData, finisher: Consumer) { + // Play the shutter sound to notify that we've taken a screenshot + playCameraSoundIfNeeded() + + saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher) { + result: ImageExporter.Result -> + if (result.uri != null) { + screenshotHandler.post { + Toast.makeText(context, R.string.screenshot_saved_title, Toast.LENGTH_SHORT) + .show() + } + } + } + } + + /** Starts the animation after taking the screenshot */ + private fun startAnimation( + screenRect: Rect, + showFlash: Boolean, + onAnimationComplete: Runnable?, + ) { + screenshotAnimation?.let { currentAnimation -> + if (currentAnimation.isRunning) { + currentAnimation.cancel() + } + } + + screenshotAnimation = + viewProxy.createScreenshotDropInAnimation(screenRect, showFlash).apply { + doOnEnd { onAnimationComplete?.run() } + // Play the shutter sound to notify that we've taken a screenshot + playCameraSoundIfNeeded() + if (LogConfig.DEBUG_ANIM) { + Log.d(TAG, "starting post-screenshot animation") + } + start() + } + } + + /** Reset screenshot view and then call onCompleteRunnable */ + private fun finishDismiss() { + Log.d(TAG, "finishDismiss") + actionsController.endScreenshotSession() + scrollCaptureExecutor.close() + currentRequestCallback?.onFinish() + currentRequestCallback = null + viewProxy.reset() + removeWindow() + screenshotHandler.cancelTimeout() + } + + private fun saveScreenshotInBackground( + screenshot: ScreenshotData, + requestId: UUID, + finisher: Consumer, + onResult: Consumer, + ) { + val future = + imageExporter.export( + bgExecutor, + requestId, + screenshot.bitmap, + screenshot.getUserOrDefault(), + display.displayId, + ) + future.addListener( + { + try { + val result = future.get() + Log.d(TAG, "Saved screenshot: $result") + logScreenshotResultStatus(result.uri, screenshot.userHandle!!) + onResult.accept(result) + if (LogConfig.DEBUG_CALLBACK) { + Log.d(TAG, "finished bg processing, calling back with uri: ${result.uri}") + } + finisher.accept(result.uri) + } catch (e: Exception) { + Log.d(TAG, "Failed to store screenshot", e) + if (LogConfig.DEBUG_CALLBACK) { + Log.d(TAG, "calling back with uri: null") + } + finisher.accept(null) + } + }, + mainExecutor, + ) + } + + /** Logs success/failure of the screenshot saving task, and shows an error if it failed. */ + private fun logScreenshotResultStatus(uri: Uri?, owner: UserHandle) { + if (uri == null) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, packageName) + notificationController.notifyScreenshotError(R.string.screenshot_failed_to_save_text) + } else { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, packageName) + if (userManager.isManagedProfile(owner.identifier)) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, packageName) + } + } + } + + private fun isUserSetupComplete(owner: UserHandle): Boolean { + return Settings.Secure.getInt( + context.createContextAsUser(owner, 0).contentResolver, + SETTINGS_SECURE_USER_SETUP_COMPLETE, + 0, + ) == 1 + } + + private val fullScreenRect: Rect + get() { + val displayMetrics = DisplayMetrics() + display.getRealMetrics(displayMetrics) + return Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels) + } + + /** Injectable factory to create screenshot controller instances for a specific display. */ + @AssistedFactory + interface Factory : InteractiveScreenshotHandler.Factory { + /** + * Creates an instance of the controller for that specific display. + * + * @param display display to capture + */ + override fun create(display: Display): ScreenshotController + } + + companion object { + private val TAG: String = LogConfig.logTag(ScreenshotController::class.java) + + // From WizardManagerHelper.java + private const val SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete" + + const val SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS: Int = 6000 + + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ + private fun aspectRatiosMatch( + bitmap: Bitmap, + bitmapInsets: Insets, + screenBounds: Rect?, + ): Boolean { + if (screenBounds == null) { + return false + } + val insettedWidth = bitmap.width - bitmapInsets.left - bitmapInsets.right + val insettedHeight = bitmap.height - bitmapInsets.top - bitmapInsets.bottom + + if ( + insettedHeight == 0 || insettedWidth == 0 || bitmap.width == 0 || bitmap.height == 0 + ) { + if (LogConfig.DEBUG_UI) { + Log.e( + TAG, + "Provided bitmap and insets create degenerate region: " + + "${bitmap.width} x ${bitmap.height} $bitmapInsets", + ) + } + return false + } + + val insettedBitmapAspect = insettedWidth.toFloat() / insettedHeight + val boundsAspect = screenBounds.width().toFloat() / screenBounds.height() + + val matchWithinTolerance = abs((insettedBitmapAspect - boundsAspect).toDouble()) < 0.1f + if (LogConfig.DEBUG_UI) { + Log.d( + TAG, + "aspectRatiosMatch: don't match bitmap: " + + "$insettedBitmapAspect, bounds: $boundsAspect", + ) + } + return matchWithinTolerance + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 50215af30ad4c794d28023e4ee87bc700c0e6fee..c1ea3adc38d56d0f9b5806c7a4774601222471c2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt @@ -67,7 +67,7 @@ constructor( shelfViewBinder: ScreenshotShelfViewBinder, private val thumbnailObserver: ThumbnailObserver, @Assisted private val context: Context, - @Assisted private val displayId: Int + @Assisted private val displayId: Int, ) { interface ScreenshotViewCallback { @@ -117,7 +117,7 @@ constructor( animationController, LayoutInflater.from(context), onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) }, - onUserInteraction = { callbacks?.onUserInteraction() } + onUserInteraction = { callbacks?.onUserInteraction() }, ) view.updateInsets(windowManager.currentWindowMetrics.windowInsets) addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } @@ -130,7 +130,7 @@ constructor( screenshotPreview = view.screenshotPreview thumbnailObserver.setViews( view.blurredScreenshotPreview, - view.requireViewById(R.id.screenshot_preview_border) + view.requireViewById(R.id.screenshot_preview_border), ) view.addOnAttachStateChangeListener( object : View.OnAttachStateChangeListener { @@ -204,7 +204,6 @@ constructor( fun prepareScrollingTransition( response: ScrollCaptureResponse, - screenBitmap: Bitmap, // unused newScreenshot: Bitmap, screenshotTakenInPortrait: Boolean, onTransitionPrepared: Runnable, @@ -224,7 +223,7 @@ constructor( 0, 0, context.resources.displayMetrics.widthPixels, - context.resources.displayMetrics.heightPixels + context.resources.displayMetrics.heightPixels, ) ) return r @@ -239,7 +238,7 @@ constructor( animationController.runLongScreenshotTransition( transitionDestination, longScreenshot, - onTransitionEnd + onTransitionEnd, ) transitionAnimation.doOnEnd { callbacks?.onDismiss() } transitionAnimation.start() @@ -295,7 +294,7 @@ constructor( .findOnBackInvokedDispatcher() ?.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, - onBackInvokedCallback + onBackInvokedCallback, ) } diff --git a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java index b9f9b929d962b5e1875ec0133b8b5af283f26ee1..05f19ef2f04379f4f4fdb8767dcc85e5f82426a4 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java +++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java @@ -16,6 +16,7 @@ package com.android.systemui.settings; +import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; import android.hardware.display.DisplayManager; @@ -66,7 +67,7 @@ public abstract class MultiUserUtilsModule { @Background CoroutineDispatcher backgroundDispatcher, @Background Handler handler ) { - int startingUser = userManager.getBootUser().getIdentifier(); + int startingUser = ActivityManager.getCurrentUser(); UserTrackerImpl tracker = new UserTrackerImpl(context, featureFlagsProvider, userManager, iActivityManager, dumpManager, appScope, backgroundDispatcher, handler); tracker.initialize(startingUser); diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 1a997a7640552f883e32bec9e76b91093f6da19c..e1631ccdcb06414e8029b42249bafe9485052cb2 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -16,11 +16,10 @@ package com.android.systemui.settings -import com.android.systemui.util.annotations.WeaklyReferencedCallback - import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle +import com.android.systemui.util.annotations.WeaklyReferencedCallback import java.util.concurrent.Executor /** @@ -31,19 +30,13 @@ import java.util.concurrent.Executor */ interface UserTracker : UserContentResolverProvider, UserContextProvider { - /** - * Current user's id. - */ + /** Current user's id. */ val userId: Int - /** - * [UserHandle] for current user - */ + /** [UserHandle] for current user */ val userHandle: UserHandle - /** - * [UserInfo] for current user - */ + /** [UserInfo] for current user */ val userInfo: UserInfo /** @@ -56,39 +49,33 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { */ val userProfiles: List - /** - * Add a [Callback] to be notified of chances, on a particular [Executor] - */ + /** Is the system in the process of switching users? */ + val isUserSwitching: Boolean + + /** Add a [Callback] to be notified of chances, on a particular [Executor] */ fun addCallback(callback: Callback, executor: Executor) - /** - * Remove a [Callback] previously added. - */ + /** Remove a [Callback] previously added. */ fun removeCallback(callback: Callback) - /** - * Callback for notifying of changes. - */ + /** Callback for notifying of changes. */ @WeaklyReferencedCallback interface Callback { - /** - * Notifies that the current user will be changed. - */ + /** Notifies that the current user will be changed. */ fun onBeforeUserSwitching(newUser: Int) {} /** - * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be - * called automatically after the completion of this method. + * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be called + * automatically after the completion of this method. */ fun onUserChanging(newUser: Int, userContext: Context) {} /** - * Notifies that the current user is being changed. - * Override this method to run things while the screen is frozen for the user switch. - * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the - * screen further. Please be aware that code executed in this callback will lengthen the - * user switch duration. When overriding this method, resultCallback#run() MUST be called - * once the execution is complete. + * Notifies that the current user is being changed. Override this method to run things while + * the screen is frozen for the user switch. Please use {@link #onUserChanged} if the task + * doesn't need to push the unfreezing of the screen further. Please be aware that code + * executed in this callback will lengthen the user switch duration. When overriding this + * method, resultCallback#run() MUST be called once the execution is complete. */ fun onUserChanging(newUser: Int, userContext: Context, resultCallback: Runnable) { onUserChanging(newUser, userContext) @@ -96,15 +83,13 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { } /** - * Notifies that the current user has changed. - * Override this method to run things after the screen is unfrozen for the user switch. - * Please see {@link #onUserChanging} if you need to hide jank. + * Notifies that the current user has changed. Override this method to run things after the + * screen is unfrozen for the user switch. Please see {@link #onUserChanging} if you need to + * hide jank. */ fun onUserChanged(newUser: Int, userContext: Context) {} - /** - * Notifies that the current user's profiles have changed. - */ + /** Notifies that the current user's profiles have changed. */ fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 553d1f51a198b74887edcdc16603378adb1f8e70..1863e12187cd91d3eee5af2e9232cec755c3df4f 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -106,6 +106,9 @@ internal constructor( override var userInfo: UserInfo by SynchronizedDelegate(UserInfo(context.userId, "", 0)) protected set + override var isUserSwitching = false + protected set + /** * Returns a [List] of all profiles associated with the current user. * @@ -197,6 +200,7 @@ internal constructor( } override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { + isUserSwitching = true if (isBackgroundUserSwitchEnabled) { userSwitchingJob?.cancel() userSwitchingJob = @@ -210,6 +214,7 @@ internal constructor( } override fun onUserSwitchComplete(newUserId: Int) { + isUserSwitching = false if (isBackgroundUserSwitchEnabled) { afterUserSwitchingJob?.cancel() afterUserSwitchingJob = @@ -221,7 +226,7 @@ internal constructor( } } }, - TAG + TAG, ) } @@ -349,7 +354,7 @@ internal constructor( private data class DataItem( val callback: WeakReference, - val executor: Executor + val executor: Executor, ) { fun sameOrEmpty(other: UserTracker.Callback): Boolean { return callback.get()?.equals(other) ?: true diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 7fa9926ea920edddd8b7736fabcd4c98d40cf177..2e67277d832734d667d105a90e10c61239716fe2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -108,7 +108,7 @@ constructor( fun dispatchTouchEvent( ev: MotionEvent?, - disallowInterceptConsumer: Consumer? + disallowInterceptConsumer: Consumer?, ): Boolean { disallowInterceptConsumer?.apply { consumers.add(this) } @@ -252,9 +252,7 @@ constructor( * * @throws RuntimeException if the view is already initialized */ - fun initView( - context: Context, - ): View { + fun initView(context: Context): View { return initView( ComposeView(context).apply { repeatWhenAttached { @@ -310,40 +308,26 @@ constructor( communalContainerView = containerView - val topEdgeSwipeRegionWidth = - containerView.resources.getDimensionPixelSize( - R.dimen.communal_top_edge_swipe_region_height - ) - val bottomEdgeSwipeRegionWidth = - containerView.resources.getDimensionPixelSize( - R.dimen.communal_bottom_edge_swipe_region_height - ) + if (!Flags.hubmodeFullscreenVerticalSwipeFix()) { + val topEdgeSwipeRegionWidth = + containerView.resources.getDimensionPixelSize( + R.dimen.communal_top_edge_swipe_region_height + ) + val bottomEdgeSwipeRegionWidth = + containerView.resources.getDimensionPixelSize( + R.dimen.communal_bottom_edge_swipe_region_height + ) - // BouncerSwipeTouchHandler has a larger gesture area than we want, set an exclusion area so - // the gesture area doesn't overlap with widgets. - // TODO(b/323035776): adjust gesture area for portrait mode - containerView.repeatWhenAttached { - // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not - // occluded. - lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) { - val ltr = containerView.layoutDirection == View.LAYOUT_DIRECTION_LTR - - val backGestureInset = - Rect( - 0, - 0, - if (ltr) 0 else containerView.right, - containerView.bottom, - ) - - containerView.systemGestureExclusionRects = - if (Flags.hubmodeFullscreenVerticalSwipeFix()) { - listOf( - // Disable back gestures on the left side of the screen, to avoid - // conflicting with scene transitions. - backGestureInset - ) - } else { + // BouncerSwipeTouchHandler has a larger gesture area than we want, set an exclusion + // area so + // the gesture area doesn't overlap with widgets. + // TODO(b/323035776): adjust gesture area for portrait mode + containerView.repeatWhenAttached { + // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and + // not + // occluded. + lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) { + containerView.systemGestureExclusionRects = listOf( // Only allow swipe up to bouncer and swipe down to shade in the very // top/bottom to avoid conflicting with widgets in the hub grid. @@ -351,15 +335,13 @@ constructor( 0, topEdgeSwipeRegionWidth, containerView.right, - containerView.bottom - bottomEdgeSwipeRegionWidth - ), - // Disable back gestures on the left side of the screen, to avoid - // conflicting with scene transitions. - backGestureInset + containerView.bottom - bottomEdgeSwipeRegionWidth, + ) ) + + logger.d({ "Insets updated: $str1" }) { + str1 = containerView.systemGestureExclusionRects.toString() } - logger.d({ "Insets updated: $str1" }) { - str1 = containerView.systemGestureExclusionRects.toString() } } } @@ -372,7 +354,7 @@ constructor( containerView, anyOf( keyguardInteractor.primaryBouncerShowing, - keyguardInteractor.alternateBouncerShowing + keyguardInteractor.alternateBouncerShowing, ), { anyBouncerShowing = it @@ -380,12 +362,12 @@ constructor( logger.d({ "New value for anyBouncerShowing: $bool1" }) { bool1 = it } } updateTouchHandlingState() - } + }, ) collectFlow( containerView, keyguardTransitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN), - { onLockscreen = it } + { onLockscreen = it }, ) collectFlow( containerView, @@ -393,7 +375,7 @@ constructor( { hubShowing = it updateTouchHandlingState() - } + }, ) collectFlow( containerView, @@ -404,12 +386,12 @@ constructor( communalInteractor.editActivityShowing, keyguardTransitionInteractor.isInTransition( Edge.create(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB) - ) + ), ), { inEditModeTransition = it updateTouchHandlingState() - } + }, ) collectFlow( containerView, @@ -417,7 +399,7 @@ constructor( shadeInteractor.isAnyFullyExpanded, shadeInteractor.isUserInteracting, shadeInteractor.isShadeFullyCollapsed, - ::Triple + ::Triple, ), { (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed) -> shadeConsumingTouches = isUserInteracting @@ -441,7 +423,7 @@ constructor( } } updateTouchHandlingState() - } + }, ) collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it }) @@ -628,7 +610,7 @@ constructor( powerManager.userActivity( SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, - 0 + 0, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 42499f0434576ca1d7d9d9e7b8dab800bb1c432d..0c05dbde611784014e0cb1bff904c6b1ccefd87f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -137,7 +137,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; -import com.android.systemui.keyguard.shared.ComposeLockscreen; import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -186,6 +185,7 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; @@ -207,7 +207,6 @@ import com.android.systemui.statusbar.phone.BounceInterpolator; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -2221,6 +2220,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @VisibleForTesting void onFlingEnd(boolean cancelled) { mIsFlinging = false; + mExpectingSynthesizedDown = false; // No overshoot when the animation ends setOverExpansionInternal(0, false /* isFromGesture */); setAnimator(null); @@ -2353,7 +2353,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return; } if (mExpectingSynthesizedDown) { - mExpectingSynthesizedDown = false; // Window never will receive touch events that typically trigger haptic on open. maybeVibrateOnOpening(false /* openingWithTouch */); fling(velocity > 1f ? 1000f * velocity : 0 /* expand */); @@ -2511,11 +2510,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return 0; } - if (ComposeLockscreen.isEnabled()) { - return (int) mKeyguardInteractor.getNotificationContainerBounds() - .getValue().getTop(); - } - if (!mKeyguardBypassController.getBypassEnabled()) { if (MigrateClocksToBlueprint.isEnabled() && !mSplitShadeEnabled) { return (int) mKeyguardInteractor.getNotificationContainerBounds() @@ -4000,6 +3994,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } mExpandedFraction = Math.min(1f, maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + if (mExpandedFraction > 0f && mExpectingSynthesizedDown) { + mExpectingSynthesizedDown = false; + } mShadeRepository.setLegacyShadeExpansion(mExpandedFraction); mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction); mExpansionDragDownAmountPx = h; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 3f3ad13f9b121b885b4f384b60a29f2409641837..4f47536f6b32092f5ff769cd16926d058921e939 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; import android.os.Build; @@ -987,6 +988,19 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void onConfigChanged(Configuration newConfig) { + // If the shade window is not visible, the bounds will not update until it becomes visible. + // Touches that should invoke shade expansion but are not within those incorrect bounds + // (because the shape of the shade window remains portrait after flipping to landscape) will + // be dropped, causing the shade expansion to fail silently. Since the shade doesn't open, + // it doesn't become visible, and the bounds will never update. Therefore, we must detect + // the incorrect bounds here and force the update so that touches are routed correctly. + if (SceneContainerFlag.isEnabled() && mWindowRootView.getVisibility() == View.INVISIBLE) { + Rect bounds = newConfig.windowConfiguration.getBounds(); + if (mWindowRootView.getWidth() != bounds.width()) { + mLogger.logConfigChangeWidthAdjust(mWindowRootView.getWidth(), bounds.width()); + updateRootViewBounds(bounds); + } + } final boolean newScreenRotationAllowed = mKeyguardStateController .isKeyguardScreenRotationAllowed(); @@ -996,6 +1010,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } + private void updateRootViewBounds(Rect bounds) { + int originalMlpWidth = mLp.width; + int originalMlpHeight = mLp.height; + mLp.width = bounds.width(); + mLp.height = bounds.height(); + mWindowManager.updateViewLayout(mWindowRootView, mLp); + mLp.width = originalMlpWidth; + mLp.height = originalMlpHeight; + } + /** * When keyguard will be dismissed but didn't start animation yet. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 1c223db33fe73831eb95eb3dc63f1572fc6639db..5473af3b3fb5a498e05ad4f781ef55d072249b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -30,6 +30,8 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import androidx.core.view.ViewKt; + import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardUnfoldTransition; @@ -38,6 +40,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; +import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags; import com.android.systemui.bouncer.ui.binder.BouncerViewBinder; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; @@ -51,6 +54,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.Edge; +import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.res.R; @@ -111,6 +115,7 @@ public class NotificationShadeWindowViewController implements Dumpable { private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; private final AlternateBouncerInteractor mAlternateBouncerInteractor; private final QuickSettingsController mQuickSettingsController; + private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final GlanceableHubContainerController mGlanceableHubContainerController; private GestureDetector mPulsingWakeupGestureHandler; @@ -140,6 +145,7 @@ public class NotificationShadeWindowViewController implements Dumpable { private final PanelExpansionInteractor mPanelExpansionInteractor; private final ShadeExpansionStateManager mShadeExpansionStateManager; + private ViewGroup mBouncerParentView; /** * If {@code true}, an external touch sent in {@link #handleExternalTouch(MotionEvent)} has been * intercepted and all future touch events for the gesture should be processed by this view. @@ -217,6 +223,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mPulsingGestureListener = pulsingGestureListener; mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener; mNotificationInsetsController = notificationInsetsController; + mKeyguardTransitionInteractor = keyguardTransitionInteractor; mGlanceableHubContainerController = glanceableHubContainerController; mFeatureFlagsClassic = featureFlagsClassic; mSysUIKeyEventHandler = sysUIKeyEventHandler; @@ -227,7 +234,7 @@ public class NotificationShadeWindowViewController implements Dumpable { // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView); - bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container)); + bindBouncer(bouncerViewBinder); collectFlow(mView, keyguardTransitionInteractor.transition( Edge.create(LOCKSCREEN, DREAMING)), @@ -256,6 +263,36 @@ public class NotificationShadeWindowViewController implements Dumpable { dumpManager.registerDumpable(this); } + private void bindBouncer(BouncerViewBinder bouncerViewBinder) { + mBouncerParentView = mView.findViewById(R.id.keyguard_bouncer_container); + bouncerViewBinder.bind(mBouncerParentView); + if (ComposeBouncerFlags.INSTANCE.isOnlyComposeBouncerEnabled()) { + collectFlow(mView, mKeyguardTransitionInteractor.transition( + new Edge.StateToState(KeyguardState.PRIMARY_BOUNCER, null)), + this::onTransitionAwayFromBouncer); + collectFlow(mView, mKeyguardTransitionInteractor.transition( + new Edge.StateToState(null, KeyguardState.PRIMARY_BOUNCER)), + this::onTransitionToBouncer); + collectFlow(mView, mPrimaryBouncerInteractor.isShowing(), + (showing) -> ViewKt.setVisible(mBouncerParentView, showing)); + } + } + + private void onTransitionToBouncer(TransitionStep transitionStep) { + if (transitionStep.getTransitionState() == TransitionState.STARTED) { + if (mView.indexOfChild(mBouncerParentView) != -1) { + mView.removeView(mBouncerParentView); + } + mView.addView(mBouncerParentView); + } + } + + private void onTransitionAwayFromBouncer(TransitionStep transitionStep) { + if (transitionStep.getTransitionState() == TransitionState.FINISHED) { + mView.removeView(mBouncerParentView); + } + } + /** * @return Location where to place the KeyguardMessageArea */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt index e7a397b0fa09b9444088f8cbfdcf7b5a8748ad3e..1693e62c89fba00dbcb931166ef3ed676836c297 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt @@ -31,18 +31,13 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { fun logNewState(state: Any) { - buffer.log( - TAG, - DEBUG, - { str1 = state.toString() }, - { "Applying new state: $str1" } - ) + buffer.log(TAG, DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" }) } private inline fun log( logLevel: LogLevel, initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String + noinline printer: LogMessage.() -> String, ) { buffer.log(TAG, logLevel, initializer, printer) } @@ -52,7 +47,8 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = visible }, - { "Updating visibility, should be visible : $bool1" }) + { "Updating visibility, should be visible : $bool1" }, + ) } fun logIsExpanded( @@ -65,7 +61,7 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: headsUpNotificationShowing: Boolean, scrimsVisibilityNotTransparent: Boolean, backgroundBlurRadius: Boolean, - launchingActivityFromNotification: Boolean + launchingActivityFromNotification: Boolean, ) { buffer.log( TAG, @@ -82,11 +78,13 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: long2 = if (backgroundBlurRadius) 1 else 0 double1 = if (launchingActivityFromNotification) 1.0 else 0.0 }, - { "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " + + { + "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " + "isKeyguardShowingAndNotOccluded $bool2, panelVisible $bool3, " + "keyguardFadingAway $bool4, bouncerShowing $int1," + "headsUpNotificationShowing $int2, scrimsVisibilityNotTransparent $long1," + - "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"} + "backgroundBlurRadius $long2, launchingActivityFromNotification $double1" + }, ) } @@ -95,7 +93,7 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = visible }, - { "Updating shade, should be visible and focusable: $bool1" } + { "Updating shade, should be visible and focusable: $bool1" }, ) } @@ -104,7 +102,19 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: TAG, DEBUG, { bool1 = focusable }, - { "Updating shade, should be focusable : $bool1" } + { "Updating shade, should be focusable : $bool1" }, + ) + } + + fun logConfigChangeWidthAdjust(originalWidth: Int, newWidth: Int) { + buffer.log( + TAG, + DEBUG, + { + int1 = originalWidth + int2 = newWidth + }, + { "Config changed. SceneWindowRootView width updating from $int1 to $int2." }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 87f360eb9712630c9c842f8d671c3dd83268335a..ad3afd4d1756d74a7733f6f76b57bc8b81d75ca3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -61,6 +61,7 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIcon.Shape; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Flags; +import com.android.systemui.modes.shared.ModesUiIcons; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.NotificationContentDescription; import com.android.systemui.statusbar.notification.NotificationDozeHelper; @@ -215,7 +216,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi // We scale notification icons (on the left) plus icons on the right that explicitly // want FIXED_SPACE. boolean useNonSystemIconScaling = isNotification() - || (usesModeIcons() && mIcon != null && mIcon.shape == Shape.FIXED_SPACE); + || (ModesUiIcons.isEnabled() && mIcon != null && mIcon.shape == Shape.FIXED_SPACE); if (useNonSystemIconScaling) { updateIconScaleForNonSystemIcons(); @@ -415,7 +416,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi if (!levelEquals) { setImageLevel(icon.iconLevel); } - if (usesModeIcons() && icon.shape == Shape.FIXED_SPACE) { + if (ModesUiIcons.isEnabled() && icon.shape == Shape.FIXED_SPACE) { setScaleType(ScaleType.FIT_CENTER); } if (!visibilityEquals) { @@ -506,7 +507,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @Nullable private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) { - if (usesModeIcons() && statusBarIcon.preloadedIcon != null) { + if (ModesUiIcons.isEnabled() && statusBarIcon.preloadedIcon != null) { Drawable.ConstantState cached = statusBarIcon.preloadedIcon.getConstantState(); if (cached != null) { return cached.newDrawable(mContext.getResources()).mutate(); @@ -1041,9 +1042,4 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi public boolean showsConversation() { return mShowsConversation; } - - private static boolean usesModeIcons() { - return android.app.Flags.modesApi() && android.app.Flags.modesUi() - && android.app.Flags.modesUiIcons(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING index 718c1c0f683b2a6ea27f9ab7270d6846a9b72ebf..323268440c4a3d3b509a1b437a55e87447eee2ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING +++ b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "CtsNotificationTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsNotificationTestCases_notification" } ], "postsubmit": [ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index 4056e7b89c2c4004a5dfdf298b8e5dc65786d477..975b92e632b8668a04297888c951713b7685c0f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -16,11 +16,12 @@ package com.android.systemui.statusbar.core import android.app.Fragment -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.fragments.FragmentHostManager +import com.android.systemui.res.R +import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener +import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions -import com.android.systemui.statusbar.phone.PhoneStatusBarView import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent @@ -33,50 +34,16 @@ import javax.inject.Provider * Responsible for creating the status bar window and initializing the root components of that * window (see [CollapsedStatusBarFragment]) */ -@SysUISingleton -class StatusBarInitializer @Inject constructor( - private val windowController: StatusBarWindowController, - private val collapsedStatusBarFragmentProvider: Provider, - private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>, -) { +interface StatusBarInitializer { - var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null + var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? /** * Creates the status bar window and root views, and initializes the component. * * TODO(b/277764509): Initialize the status bar via [CoreStartable#start]. */ - fun initializeStatusBar() { - windowController.fragmentHostManager.addTagListener( - CollapsedStatusBarFragment.TAG, - object : FragmentHostManager.FragmentListener { - override fun onFragmentViewCreated(tag: String, fragment: Fragment) { - val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment) - .statusBarFragmentComponent ?: throw IllegalStateException() - statusBarViewUpdatedListener?.onStatusBarViewUpdated( - statusBarFragmentComponent.phoneStatusBarView, - statusBarFragmentComponent.phoneStatusBarViewController, - statusBarFragmentComponent.phoneStatusBarTransitions - ) - creationListeners.forEach { listener -> - listener.onStatusBarViewInitialized(statusBarFragmentComponent) - } - } - - override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) { - // nop - } - } - ).fragmentManager - .beginTransaction() - .replace( - R.id.status_bar_container, - collapsedStatusBarFragmentProvider.get(), - CollapsedStatusBarFragment.TAG - ) - .commit() - } + fun initializeStatusBar() interface OnStatusBarViewInitializedListener { @@ -84,16 +51,61 @@ class StatusBarInitializer @Inject constructor( * The status bar view has been initialized. * * @param component Dagger component that is created when the status bar view is created. - * Can be used to retrieve dependencies from that scope, including the status bar root view. + * Can be used to retrieve dependencies from that scope, including the status bar root + * view. */ fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) } interface OnStatusBarViewUpdatedListener { fun onStatusBarViewUpdated( - statusBarView: PhoneStatusBarView, statusBarViewController: PhoneStatusBarViewController, - statusBarTransitions: PhoneStatusBarTransitions + statusBarTransitions: PhoneStatusBarTransitions, ) } } + +@SysUISingleton +class StatusBarInitializerImpl +@Inject +constructor( + private val windowController: StatusBarWindowController, + private val collapsedStatusBarFragmentProvider: Provider, + private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>, +) : StatusBarInitializer { + + override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null + + override fun initializeStatusBar() { + windowController.fragmentHostManager + .addTagListener( + CollapsedStatusBarFragment.TAG, + object : FragmentHostManager.FragmentListener { + override fun onFragmentViewCreated(tag: String, fragment: Fragment) { + val statusBarFragmentComponent = + (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent + ?: throw IllegalStateException() + statusBarViewUpdatedListener?.onStatusBarViewUpdated( + statusBarFragmentComponent.phoneStatusBarViewController, + statusBarFragmentComponent.phoneStatusBarTransitions, + ) + creationListeners.forEach { listener -> + listener.onStatusBarViewInitialized(statusBarFragmentComponent) + } + } + + override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) { + // nop + } + }, + ) + .fragmentManager + .beginTransaction() + .replace( + R.id.status_bar_container, + collapsedStatusBarFragmentProvider.get(), + CollapsedStatusBarFragment.TAG, + ) + .commit() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt similarity index 86% rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt rename to packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt index 4c0c4617658a328f1fee3ac720a5269bdf2a2f45..214151383dc654dce70072ced725a619328835ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/shared/StatusBarRonChips.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.shared +package com.android.systemui.statusbar.core import com.android.systemui.Flags import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils -/** Helper for reading or using the status bar ron chips flag state. */ -@Suppress("NOTHING_TO_INLINE") -object StatusBarRonChips { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS +/** Helper for reading and using the status bar simple fragment flag state */ +object StatusBarSimpleFragment { + /** Aconfig flag for removing the fragment */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +32,7 @@ object StatusBarRonChips { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.statusBarRonChips() + get() = Flags.statusBarSimpleFragment() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 406a66449f823e9a8749fae66d3a734314c147f1..55943a527a8c6cc635883a5afa3aafa07efe802a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -16,16 +16,22 @@ package com.android.systemui.statusbar.dagger +import android.content.Context +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory +import com.android.systemui.statusbar.core.StatusBarInitializer +import com.android.systemui.statusbar.core.StatusBarInitializerImpl import com.android.systemui.statusbar.data.StatusBarDataLayerModule import com.android.systemui.statusbar.phone.LightBarController import com.android.systemui.statusbar.phone.StatusBarSignalPolicy import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl +import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -57,7 +63,20 @@ abstract class StatusBarModule { @ClassKey(StatusBarSignalPolicy::class) abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable + @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer + companion object { + + @Provides + @SysUISingleton + fun statusBarWindowController( + context: Context?, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?, + factory: StatusBarWindowControllerImpl.Factory, + ): StatusBarWindowController { + return factory.create(context, viewCaptureAwareWindowManager) + } + @Provides @SysUISingleton @OngoingCallLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt index c0302bc348b65f4c52365bd0eadabf78d5c72aac..9af4b8c18c86e6e0c1b476d4d67d4ab33af9906d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt @@ -25,6 +25,7 @@ import dagger.Module import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow /** * Repository used for tracking the state of notification remote input (e.g. when the user presses @@ -33,14 +34,21 @@ import kotlinx.coroutines.flow.Flow interface RemoteInputRepository { /** Whether remote input is currently active for any notification. */ val isRemoteInputActive: Flow + + /** + * The bottom bound of the currently focused remote input notification row, or null if there + * isn't one. + */ + val remoteInputRowBottomBound: Flow + + fun setRemoteInputRowBottomBound(bottom: Float?) } @SysUISingleton class RemoteInputRepositoryImpl @Inject -constructor( - private val notificationRemoteInputManager: NotificationRemoteInputManager, -) : RemoteInputRepository { +constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) : + RemoteInputRepository { override val isRemoteInputActive: Flow = conflatedCallbackFlow { trySend(false) // initial value is false val callback = @@ -52,6 +60,12 @@ constructor( notificationRemoteInputManager.addControllerCallback(callback) awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) } } + + override val remoteInputRowBottomBound = MutableStateFlow(null) + + override fun setRemoteInputRowBottomBound(bottom: Float?) { + remoteInputRowBottomBound.value = bottom + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt index 2bb476523cb8ac28847003c0082aea81a4c58f27..ce25cf5895c1cf712d455228388faab488d81378 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt @@ -17,8 +17,12 @@ package com.android.systemui.statusbar.disableflags.data.model import android.app.StatusBarManager.DISABLE2_NONE import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS +import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS +import android.app.StatusBarManager.DISABLE_CLOCK import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger @@ -27,12 +31,14 @@ import com.android.systemui.statusbar.disableflags.DisableFlagsLogger * Model for the disable flags that come from [IStatusBar]. * * For clients of the disable flags: do *not* refer to the disable integers directly. Instead, - * re-use or define a helper method that internally processes the flags. (We want to hide the - * bitwise logic here so no one else has to worry about it.) + * re-use or define a helper method or property that internally processes the flags. (We want to + * hide the bitwise logic here so no one else has to worry about it.) */ data class DisableFlagsModel( private val disable1: Int = DISABLE_NONE, private val disable2: Int = DISABLE2_NONE, + /** True if we should animate any view visibility changes and false otherwise. */ + val animate: Boolean = false, ) { /** Returns true if notification alerts are allowed based on the flags. */ fun areNotificationAlertsEnabled(): Boolean { @@ -49,6 +55,13 @@ data class DisableFlagsModel( return (disable2 and DISABLE2_QUICK_SETTINGS) == 0 } + val isClockEnabled = (disable1 and DISABLE_CLOCK) == 0 + + val areNotificationIconsEnabled = (disable1 and DISABLE_NOTIFICATION_ICONS) == 0 + + val isSystemInfoEnabled = + (disable1 and DISABLE_SYSTEM_INFO) == 0 && (disable2 and DISABLE2_SYSTEM_ICONS) == 0 + /** Logs the change to the provided buffer. */ fun logChange(buffer: LogBuffer, disableFlagsLogger: DisableFlagsLogger) { buffer.log( @@ -60,9 +73,9 @@ data class DisableFlagsModel( }, { disableFlagsLogger.getDisableFlagsString( - new = DisableFlagsLogger.DisableState(int1, int2), + new = DisableFlagsLogger.DisableState(int1, int2) ) - } + }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt index 13b74b493905183cf288dd857fb425c7edde0ccf..9004e5d126635c651d0de254068f94bd9e02a1d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt @@ -72,6 +72,7 @@ constructor( // [QuickSettingsInteractor]-type class. However, that's out of // scope for the CentralSurfaces removal project. remoteInputQuickSettingsDisabler.adjustDisableFlags(state2), + animate, ) ) } @@ -82,5 +83,5 @@ constructor( .distinctUntilChanged() .onEach { it.logChange(logBuffer, disableFlagsLogger) } // Use Eagerly because we always need to know about disable flags - .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel()) + .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel(animate = false)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt index 68f727b046c0bffb410ce75a186528e4cc8d9aa4..b83b0cc8d2c9fb2404c5157e79afbee02aaa4408 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt @@ -20,13 +20,24 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.data.repository.RemoteInputRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull /** * Interactor used for business logic pertaining to the notification remote input (e.g. when the * user presses "reply" on a notification and the keyboard opens). */ @SysUISingleton -class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) { +class RemoteInputInteractor +@Inject +constructor(private val remoteInputRepository: RemoteInputRepository) { /** Is remote input currently active for a notification? */ val isRemoteInputActive: Flow = remoteInputRepository.isRemoteInputActive + + /** The bottom bound of the currently focused remote input notification row. */ + val remoteInputRowBottomBound: Flow = + remoteInputRepository.remoteInputRowBottomBound.mapNotNull { it } + + fun setRemoteInputRowBottomBound(bottom: Float?) { + remoteInputRepository.setRemoteInputRowBottomBound(bottom) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java index 02a29e29035d2f9d293c7852fc934b124b8fb30a..9b96931348d67b247ad2ecbfdeeb4a8221b84e14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java @@ -103,7 +103,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements private boolean mTrackingHeadsUp; private final HashSet mSwipedOutKeys = new HashSet<>(); private final HashSet mEntriesToRemoveAfterExpand = new HashSet<>(); - private final ArraySet mEntriesToRemoveWhenReorderingAllowed + @VisibleForTesting + public final ArraySet mEntriesToRemoveWhenReorderingAllowed = new ArraySet<>(); private boolean mIsExpanded; private int mStatusBarState; @@ -417,7 +418,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isHeadsUpEntry(entry.getKey())) { // Maybe the heads-up was removed already - removeEntry(entry.getKey(), "mOnReorderingAllowedListener"); + removeEntry(entry.getKey(), "allowReorder"); } } mEntriesToRemoveWhenReorderingAllowed.clear(); @@ -617,11 +618,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements super.setEntry(entry, removeRunnable); if (NotificationThrottleHun.isEnabled()) { - if (!mVisualStabilityProvider.isReorderingAllowed() - // We don't want to allow reordering while pulsing, but headsup need to - // time out anyway - && !entry.showingPulsing()) { - mEntriesToRemoveWhenReorderingAllowed.add(entry); + mEntriesToRemoveWhenReorderingAllowed.add(entry); + if (!mVisualStabilityProvider.isReorderingAllowed()) { entry.setSeenInShade(true); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index cb3e26b9f8ea2fc52249be32669c25d057dc5810..5003a6af5c4c3f394e712e2dafbefb3cdafcb999 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -21,6 +21,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; +import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE; import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.animation.Animator; @@ -83,6 +84,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarIconView; @@ -118,6 +120,7 @@ import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; +import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; @@ -830,6 +833,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.setRemoteInputController(r); } + /** + * Return the cumulative y-value that the actions container expands via its scale animator when + * remote input is activated. + */ + public float getRemoteInputActionsContainerExpandedOffset() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + RemoteInputView expandedRemoteInput = mPrivateLayout.getExpandedRemoteInput(); + if (expandedRemoteInput == null) return 0f; + View actionsContainerLayout = expandedRemoteInput.getActionsContainerLayout(); + if (actionsContainerLayout == null) return 0f; + + return actionsContainerLayout.getHeight() * (1 - FOCUS_ANIMATION_MIN_SCALE) * 0.5f; + } + public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 7543f3b48e48326c64a959cdf4c0adfdba1957fc..e7c67f93eb78a2c4dd546b1f46ab9811c5254736 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -99,6 +99,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.FakeShadowView; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -120,7 +121,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape; import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; @@ -740,6 +740,15 @@ public class NotificationStackScrollLayout updateFooter(); } + void sendRemoteInputRowBottomBound(Float bottom) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + if (bottom != null) { + bottom += getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin); + } + mScrollViewFields.sendRemoteInputRowBottomBound(bottom); + } + /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */ public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { FooterViewRefactor.assertInLegacyMode(); @@ -1273,6 +1282,11 @@ public class NotificationStackScrollLayout mScrollViewFields.setCurrentGestureInGutsConsumer(consumer); } + @Override + public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer consumer) { + mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer); + } + @Override public void setHeadsUpHeightConsumer(@Nullable Consumer consumer) { mScrollViewFields.setHeadsUpHeightConsumer(consumer); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e5f63c1cb480e4996f789ee1168af3ba7da932f1..dad6894a43ce34b3427e56bd2a8dd5224402571f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -98,6 +98,9 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; @@ -129,9 +132,6 @@ import com.android.systemui.statusbar.notification.row.NotificationSnooze; import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; -import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -1605,6 +1605,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { return new RemoteInputController.Delegate() { public void setRemoteInputActive(NotificationEntry entry, boolean remoteInputActive) { + if (SceneContainerFlag.isEnabled()) { + sendRemoteInputRowBottomBound(entry, remoteInputActive); + } mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); if (!FooterViewRefactor.isEnabled()) { @@ -1620,6 +1623,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.requestDisallowLongPress(); mView.requestDisallowDismiss(); } + + private void sendRemoteInputRowBottomBound(NotificationEntry entry, + boolean remoteInputActive) { + ExpandableNotificationRow row = entry.getRow(); + float top = row.getTranslationY(); + int height = row.getActualHeight(); + float bottom = top + height + row.getRemoteInputActionsContainerExpandedOffset(); + mView.sendRemoteInputRowBottomBound(remoteInputActive ? bottom : null); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index aa3953987c1042c38fe9dc05188d2b1a862401b9..c08ed6120832bf59db538cb0b8780cc5e517c6ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -57,6 +57,13 @@ class ScrollViewFields { * guts off of this gesture, we can notify the placeholder through here. */ var currentGestureInGutsConsumer: Consumer? = null + + /** + * When a notification begins remote input, its bottom Y bound is sent to the placeholder + * through here in order to adjust to accommodate the IME. + */ + var remoteInputRowBottomBoundConsumer: Consumer? = null + /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder @@ -75,6 +82,10 @@ class ScrollViewFields { fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) = currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts) + /** send [bottomY] to the [remoteInputRowBottomBoundConsumer], if present. */ + fun sendRemoteInputRowBottomBound(bottomY: Float?) = + remoteInputRowBottomBoundConsumer?.accept(bottomY) + /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 235b4da3f0293cb015d27101e22890a00dd3b5ad..41c02934efa627dd2b0f1bafb188f2b5971cc6e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -74,6 +74,9 @@ interface NotificationScrollView { /** Set a consumer for current gesture in guts events */ fun setCurrentGestureInGutsConsumer(consumer: Consumer?) + /** Set a consumer for current remote input notification row bottom bound events */ + fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer?) + /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer?) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 6d5553fec6b421387a110040aecc79496ac1f7be..2e37dead87870d2c0243a8a028a527561869d31e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -108,10 +108,14 @@ constructor( view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer) + view.setRemoteInputRowBottomBoundConsumer( + viewModel.remoteInputRowBottomBoundConsumer + ) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) view.setCurrentGestureInGutsConsumer(null) + view.setRemoteInputRowBottomBoundConsumer(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 8d7007b2fba49eff5a1506be4c8914b6afc4107f..5b2e02d446cf398a8c090164cdc7ce9899cce21b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape @@ -56,6 +57,7 @@ constructor( dumpManager: DumpManager, stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, + private val remoteInputInteractor: RemoteInputInteractor, private val sceneInteractor: SceneInteractor, // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released - // while the flag is off, creating this object too early results in a crash @@ -240,6 +242,10 @@ constructor( val currentGestureInGutsConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureInGuts + /** Receives the bottom bound of the currently focused remote input notification row. */ + val remoteInputRowBottomBoundConsumer: (Float?) -> Unit = + remoteInputInteractor::setRemoteInputRowBottomBound + /** Whether the notification stack is scrollable or not. */ val isScrollable: Flow = combine(sceneInteractor.currentScene, sceneInteractor.currentOverlays) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 69c1bf3b61b7bcda68439c191a92043fb7365e8e..c8e83581e831122608e76e87bbe0a6d1d8bcd112 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds @@ -49,6 +50,7 @@ constructor( private val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, private val headsUpNotificationInteractor: HeadsUpNotificationInteractor, + remoteInputInteractor: RemoteInputInteractor, featureFlags: FeatureFlagsClassic, dumpManager: DumpManager, ) : @@ -132,6 +134,12 @@ constructor( val isCurrentGestureOverscroll: Flow = interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll") + /** Whether remote input is currently active for any notification. */ + val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive + + /** The bottom bound of the currently focused remote input notification row. */ + val remoteInputRowBottomBound = remoteInputInteractor.remoteInputRowBottomBound + /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { interactor.setScrolledToTop(scrolledToTop) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 50e92498b114ac5d21fb7db27136e9bc9c4e1efa..59533b343a579506032d2d282826cdaeab860ceb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -372,7 +372,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Point mCurrentDisplaySize = new Point(); - protected PhoneStatusBarView mStatusBarView; private PhoneStatusBarViewController mPhoneStatusBarViewController; private PhoneStatusBarTransitions mStatusBarTransitions; private final AuthRippleController mAuthRippleController; @@ -1191,8 +1190,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up CollapsedStatusBarFragment and PhoneStatusBarView mStatusBarInitializer.setStatusBarViewUpdatedListener( - (statusBarView, statusBarViewController, statusBarTransitions) -> { - mStatusBarView = statusBarView; + (statusBarViewController, statusBarTransitions) -> { mPhoneStatusBarViewController = statusBarViewController; mStatusBarTransitions = statusBarTransitions; getNotificationShadeWindowViewController() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index ba39c3bb412419ee30d9cb00a9474aca6eaed1c1..8c0353813ec63d31c6fa1abd17560d3bfb6493d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -51,6 +51,7 @@ import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor; +import com.android.systemui.modes.shared.ModesUiIcons; import com.android.systemui.privacy.PrivacyItem; import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.privacy.PrivacyType; @@ -360,7 +361,7 @@ public class PhoneStatusBarPolicy mBluetooth.addCallback(this); mProvisionedController.addCallback(this); mCurrentUserSetup = mProvisionedController.isCurrentUserSetup(); - if (usesModeIcons()) { + if (ModesUiIcons.isEnabled()) { // Note that we're not fully replacing ZenModeController with ZenModeInteractor, so // we listen for the extra event here but still add the ZMC callback. mJavaAdapter.alwaysCollectFlow(mZenModeInteractor.getMainActiveMode(), @@ -397,8 +398,7 @@ public class PhoneStatusBarPolicy } private void onMainActiveModeChanged(@Nullable ZenModeInfo mainActiveMode) { - if (!usesModeIcons()) { - Log.wtf(TAG, "onMainActiveModeChanged shouldn't run if MODES_UI_ICONS is disabled"); + if (ModesUiIcons.isUnexpectedlyInLegacyMode()) { return; } @@ -458,14 +458,14 @@ public class PhoneStatusBarPolicy private void updateVolumeZen() { int zen = mZenController.getZen(); - if (!usesModeIcons()) { + if (!ModesUiIcons.isEnabled()) { updateZenIcon(zen); } updateRingerAndAlarmIcons(zen); } private void updateZenIcon(int zen) { - if (usesModeIcons()) { + if (ModesUiIcons.isEnabled()) { Log.wtf(TAG, "updateZenIcon shouldn't be called if MODES_UI_ICONS is enabled"); return; } @@ -942,9 +942,4 @@ public class PhoneStatusBarPolicy mIconController.setIconVisibility(mSlotConnectedDisplay, visible); } - - private static boolean usesModeIcons() { - return android.app.Flags.modesApi() && android.app.Flags.modesUi() - && android.app.Flags.modesUiIcons(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 659cee3023a14c03643985362e0d637c62b63545..a8b4728bc98270168226cc5e15359580baf30c74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -55,8 +55,9 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.chips.shared.StatusBarRonChips; -import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState; +import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips; +import com.android.systemui.statusbar.core.StatusBarSimpleFragment; +import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; @@ -366,8 +367,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mPrimaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_primary); mSecondaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_secondary); - showEndSideContent(false); - showClock(false); + if (!StatusBarSimpleFragment.isEnabled()) { + showEndSideContent(false); + showClock(false); + } initOperatorName(); initNotificationIconArea(); mSystemEventAnimator = getSystemEventAnimator(); @@ -455,7 +458,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue super.onPause(); mCommandQueue.removeCallback(this); mStatusBarStateController.removeCallback(this); - mOngoingCallController.removeCallback(mOngoingCallListener); + if (!StatusBarSimpleFragment.isEnabled()) { + mOngoingCallController.removeCallback(mOngoingCallListener); + } mAnimationScheduler.removeCallback(this); mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver); } @@ -490,7 +495,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mNotificationIconAreaInner = notificationIcons; mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons); - updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false); + if (!StatusBarSimpleFragment.isEnabled()) { + updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false); + } Trace.endSection(); } @@ -509,11 +516,17 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue new StatusBarVisibilityChangeListener() { @Override public void onStatusBarVisibilityMaybeChanged() { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } updateStatusBarVisibilities(/* animate= */ true); } @Override public void onTransitionFromLockscreenToDreamStarted() { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } mTransitionFromLockscreenToDreamStarted = true; } @@ -522,6 +535,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue boolean hasPrimaryOngoingActivity, boolean hasSecondaryOngoingActivity, boolean shouldAnimate) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } mHasPrimaryOngoingActivity = hasPrimaryOngoingActivity; mHasSecondaryOngoingActivity = hasSecondaryOngoingActivity; updateStatusBarVisibilities(shouldAnimate); @@ -530,6 +546,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onIsHomeStatusBarAllowedBySceneChanged( boolean isHomeStatusBarAllowedByScene) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene; updateStatusBarVisibilities(/* animate= */ true); } @@ -537,17 +556,22 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void disable(int displayId, int state1, int state2, boolean animate) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } if (displayId != getContext().getDisplayId()) { return; } mCollapsedStatusBarFragmentLogger - .logDisableFlagChange(new DisableState(state1, state2)); + .logDisableFlagChange(new DisableFlagsLogger.DisableState(state1, state2)); mLastSystemVisibility = StatusBarVisibilityModel.createModelFromFlags(state1, state2); updateStatusBarVisibilities(animate); } private void updateStatusBarVisibilities(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarVisibilityModel previousModel = mLastModifiedVisibility; StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility); mCollapsedStatusBarFragmentLogger.logVisibilityModel(newModel); @@ -587,6 +611,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private StatusBarVisibilityModel calculateInternalModel( StatusBarVisibilityModel externalModel) { + StatusBarSimpleFragment.assertInLegacyMode(); + // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead. boolean headsUpVisible = mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); @@ -639,6 +665,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * mLastModifiedVisibility. */ private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility; boolean disableNotifications = !visibilityModel.getShowNotificationIcons(); boolean hasOngoingActivity = visibilityModel.getShowPrimaryOngoingActivityChip(); @@ -674,6 +702,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private boolean shouldHideStatusBar() { + StatusBarSimpleFragment.assertInLegacyMode(); + if (!mShadeExpansionStateManager.isClosed() && mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()) { return true; @@ -728,6 +758,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideEndSideContent(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER); } else { @@ -737,6 +768,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void showEndSideContent(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER); return; @@ -753,15 +785,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideClock(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(mClockView, clockHiddenMode(), animate); } private void showClock(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mClockView, animate); } /** Hides the primary ongoing activity chip. */ private void hidePrimaryOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(mPrimaryOngoingActivityChip, View.GONE, animate); } @@ -773,15 +808,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * activities. See b/332662551. */ private void showPrimaryOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mPrimaryOngoingActivityChip, animate); } private void hideSecondaryOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(mSecondaryOngoingActivityChip, View.GONE, animate); } private void showSecondaryOngoingActivityChip(boolean animate) { StatusBarRonChips.assertInNewMode(); + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mSecondaryOngoingActivityChip, animate); } @@ -790,6 +828,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * don't set the clock GONE otherwise it'll mess up the animation. */ private int clockHiddenMode() { + StatusBarSimpleFragment.assertInLegacyMode(); if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing() && !mStatusBarStateController.isDozing()) { return View.INVISIBLE; @@ -798,20 +837,24 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } public void hideNotificationIconArea(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHide(mNotificationIconAreaInner, animate); } public void showNotificationIconArea(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mNotificationIconAreaInner, animate); } public void hideOperatorName(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (mOperatorNameViewController != null) { animateHide(mOperatorNameViewController.getView(), animate); } } public void showOperatorName(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (mOperatorNameViewController != null) { animateShow(mOperatorNameViewController.getView(), animate); } @@ -821,6 +864,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Animate a view to INVISIBLE or GONE */ private void animateHiddenState(final View v, int state, boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); v.animate().cancel(); if (!animate || !mAnimationsEnabled) { v.setAlpha(0f); @@ -840,6 +884,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Hides a view. */ private void animateHide(final View v, boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(v, View.INVISIBLE, animate); } @@ -847,6 +892,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable. */ private void animateShow(View v, boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); v.animate().cancel(); v.setVisibility(View.VISIBLE); if (!animate || !mAnimationsEnabled) { @@ -883,13 +929,17 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mOperatorNameViewController.init(); // This view should not be visible on lock-screen if (mKeyguardStateController.isShowing()) { - hideOperatorName(false); + if (!StatusBarSimpleFragment.isEnabled()) { + hideOperatorName(false); + } } } } private void initOngoingCallChip() { - mOngoingCallController.addCallback(mOngoingCallListener); + if (!StatusBarSimpleFragment.isEnabled()) { + mOngoingCallController.addCallback(mOngoingCallListener); + } // TODO(b/364653005): Do we also need to set the secondary activity chip? mOngoingCallController.setChipView(mPrimaryOngoingActivityChip); } @@ -899,6 +949,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onDozingChanged(boolean isDozing) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } updateStatusBarVisibilities(/* animate= */ false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt index c8836e4235dca55af43b9957efba737946e11341..eaf15a8cbe17513902238b6c89686b6590b20f3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt @@ -20,6 +20,7 @@ import android.view.View import androidx.core.animation.Interpolator import androidx.core.animation.ValueAnimator import com.android.app.animation.InterpolatorsAndroidX +import com.android.systemui.statusbar.core.StatusBarSimpleFragment /** * A controller that keeps track of multiple sources applying alpha value changes to a view. It will @@ -48,7 +49,7 @@ constructor(private val view: View, private val initialAlpha: Float = 1f) { sourceId: Int, duration: Long, interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN, - startDelay: Long = 0 + startDelay: Long = 0, ) { animators[sourceId]?.cancel() val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha) @@ -74,8 +75,10 @@ constructor(private val view: View, private val initialAlpha: Float = 1f) { private fun applyAlphaToView() { val minAlpha = getMinAlpha() - view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE - view.alpha = minAlpha + if (!StatusBarSimpleFragment.isEnabled) { + view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE + view.alpha = minAlpha + } } private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index c0e36b2ab42a40d462d83a7da71ab1d14af07c1d..f026b99af49c4c55417b24f0875b8561e373b632 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -27,7 +27,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarLocation; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; @@ -111,14 +110,6 @@ public interface StatusBarFragmentModule { return view.findViewById(R.id.clock); } - /** */ - @Provides - @StatusBarFragmentScope - static StatusBarUserSwitcherContainer provideStatusBarUserSwitcherContainer( - @RootView PhoneStatusBarView view) { - return view.findViewById(R.id.user_switcher_container); - } - /** */ @Provides @StatusBarFragmentScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java index 91ead614ffa43424ebfd0f30152238f20d29c318..fd16c6090cb16183247d2a1a37b198a41cf9845f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java @@ -20,7 +20,6 @@ import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_BIND import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW; -import static com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl.usesModeIcons; import android.annotation.Nullable; import android.content.Context; @@ -31,6 +30,7 @@ import android.widget.LinearLayout; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIcon.Shape; import com.android.systemui.demomode.DemoModeCommandReceiver; +import com.android.systemui.modes.shared.ModesUiIcons; import com.android.systemui.statusbar.BaseStatusBarFrameLayout; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusIconDisplayable; @@ -233,7 +233,7 @@ public class IconManager implements DemoModeCommandReceiver { } protected LinearLayout.LayoutParams onCreateLayoutParams(Shape shape) { - int width = usesModeIcons() && shape == StatusBarIcon.Shape.FIXED_SPACE + int width = ModesUiIcons.isEnabled() && shape == StatusBarIcon.Shape.FIXED_SPACE ? mIconSize : ViewGroup.LayoutParams.WRAP_CONTENT; @@ -259,7 +259,7 @@ public class IconManager implements DemoModeCommandReceiver { /** Called once an icon has been set. */ public void onSetIcon(int viewIndex, StatusBarIcon icon) { StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex); - if (usesModeIcons()) { + if (ModesUiIcons.isEnabled()) { ViewGroup.LayoutParams current = view.getLayoutParams(); ViewGroup.LayoutParams desired = onCreateLayoutParams(icon.shape); if (desired.width != current.width || desired.height != current.height) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java index 9b6d32bd179dc85a1dd4b93837a1b91980f22508..e66e8138102e951990e697db3e7820c60e83958b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java @@ -42,6 +42,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.modes.shared.ModesUiIcons; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.phone.StatusBarIconHolder; @@ -242,10 +243,7 @@ public class StatusBarIconControllerImpl implements Tunable, public void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId, @Nullable Drawable preloadedIcon, CharSequence contentDescription, StatusBarIcon.Shape shape) { - if (!usesModeIcons()) { - Log.wtf("TAG", - "StatusBarIconController.setResourceIcon() should not be called without " - + "MODES_UI & MODES_UI_ICONS!"); + if (ModesUiIcons.isUnexpectedlyInLegacyMode()) { // Fall back to old implementation, although it will not load the icon if it's from a // different package. setIcon(slot, iconResId, contentDescription); @@ -580,9 +578,4 @@ public class StatusBarIconControllerImpl implements Tunable, return slot + EXTERNAL_SLOT_SUFFIX; } } - - static boolean usesModeIcons() { - return android.app.Flags.modesApi() && android.app.Flags.modesUi() - && android.app.Flags.modesUiIcons(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index deae576662e303e346760334ef20f1fe6fa4a46d..bad6f80c3735246b8dc1f938fb299612936d6247 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import com.android.app.tracing.coroutines.createCoroutineTracingContext import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -29,6 +30,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.cancel @@ -114,7 +116,7 @@ constructor( private fun createViewModel(subId: Int): Pair { // Create a child scope so we can cancel it - val vmScope = scope.createChildScope() + val vmScope = scope.createChildScope(createCoroutineTracingContext("MobileIconViewModel")) val vm = MobileIconViewModel( subId, @@ -128,8 +130,8 @@ constructor( return Pair(vm, vmScope) } - private fun CoroutineScope.createChildScope() = - CoroutineScope(coroutineContext + Job(coroutineContext[Job])) + private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) = + CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext) private fun invalidateCaches(subIds: List) { reuseCache.keys diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..9164da721e3af82154d8dd5b1f47fb8f67ed87e7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.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.systemui.statusbar.pipeline.shared.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.pipeline.shared.domain.model.StatusBarDisableFlagsVisibilityModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * Interactor for the home screen status bar (aka + * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment]). + */ +@SysUISingleton +class CollapsedStatusBarInteractor +@Inject +constructor(disableFlagsRepository: DisableFlagsRepository) { + /** + * The visibilities of various status bar child views, based only on the information we received + * from disable flags. + */ + val visibilityViaDisableFlags: Flow = + disableFlagsRepository.disableFlags.map { + StatusBarDisableFlagsVisibilityModel( + isClockAllowed = it.isClockEnabled, + areNotificationIconsAllowed = it.areNotificationIconsEnabled, + isSystemInfoAllowed = it.isSystemInfoEnabled, + animate = it.animate, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..69e9746ee24f0d46a978d9cf58644ed1fa445a3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.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.systemui.statusbar.pipeline.shared.domain.model + +/** + * Represents the visibilities of various status bar child views, based only on the information we + * received from disable flags. + */ +data class StatusBarDisableFlagsVisibilityModel( + /** True if the clock is allowed to be shown. */ + val isClockAllowed: Boolean, + /** True if the notification icons are allowed to be shown. */ + val areNotificationIconsAllowed: Boolean, + /** True if the system information (wifi, mobile, etc.) is allowed to be shown. */ + val isSystemInfoAllowed: Boolean, + /** True if we should animate any view visibility changes and false otherwise. */ + val animate: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index 49eabba5c2b0387d00a284fbbc8b578acdc8c5fb..4cb66c19a0bbf07b2d3ba6a6e6d0160e5be4f4f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.app.animation.Interpolators import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached @@ -28,7 +29,9 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.core.StatusBarSimpleFragment import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel import javax.inject.Inject import kotlinx.coroutines.launch @@ -134,6 +137,29 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } } + + if (StatusBarSimpleFragment.isEnabled) { + val clockView = view.requireViewById(R.id.clock) + launch { viewModel.isClockVisible.collect { clockView.adjustVisibility(it) } } + + val notificationIconsArea = view.requireViewById(R.id.notificationIcons) + launch { + viewModel.isNotificationIconContainerVisible.collect { + notificationIconsArea.adjustVisibility(it) + } + } + + val systemInfoView = + view.requireViewById(R.id.status_bar_end_side_content) + // TODO(b/364360986): Also handle operator name view. + launch { + viewModel.isSystemInfoVisible.collect { + systemInfoView.adjustVisibility(it) + // TODO(b/364360986): The system info view has a custom alpha controller + // in CollapsedStatusBarFragment. + } + } + } } } } @@ -167,6 +193,54 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa ) .start() } + + private fun View.adjustVisibility(model: CollapsedStatusBarViewModel.VisibilityModel) { + if (model.visibility == View.VISIBLE) { + this.show(model.shouldAnimateChange) + } else { + this.hide(model.visibility, model.shouldAnimateChange) + } + } + + // See CollapsedStatusBarFragment#hide. + private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) { + val v = this + v.animate().cancel() + if (!shouldAnimateChange) { + v.alpha = 0f + v.visibility = state + return + } + + v.animate() + .alpha(0f) + .setDuration(CollapsedStatusBarFragment.FADE_OUT_DURATION.toLong()) + .setStartDelay(0) + .setInterpolator(Interpolators.ALPHA_OUT) + .withEndAction { v.visibility = state } + } + + // See CollapsedStatusBarFragment#show. + private fun View.show(shouldAnimateChange: Boolean) { + val v = this + v.animate().cancel() + v.visibility = View.VISIBLE + if (!shouldAnimateChange) { + v.alpha = 1f + return + } + v.animate() + .alpha(1f) + .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION.toLong()) + .setInterpolator(Interpolators.ALPHA_IN) + .setStartDelay(CollapsedStatusBarFragment.FADE_IN_DELAY.toLong()) + // We need to clean up any pending end action from animateHide if we call both hide and + // show in the same frame before the animation actually gets started. + // cancel() doesn't really remove the end action. + .withEndAction(null) + + // TODO(b/364360986): Synchronize the motion with the Keyguard fading if necessary. + } } /** Listener for various events that may affect the status bar's visibility. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index 9cce2b8fb72b4cf18f16e85967e54acfccfcdba2..692e0e4f55f8e86873a649d982290741bcdc2478 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -16,23 +16,29 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import android.view.View import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor +import com.android.systemui.statusbar.pipeline.shared.domain.interactor.CollapsedStatusBarInteractor +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel.VisibilityModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -80,9 +86,18 @@ interface CollapsedStatusBarViewModel { /** * True if the current scene can show the home status bar (aka this status bar), and false if * the current scene should never show the home status bar. + * + * TODO(b/364360986): Once the isVisible flows are fully enabled, we shouldn't + * need this flow anymore. */ val isHomeStatusBarAllowedByScene: StateFlow + val isClockVisible: Flow + val isNotificationIconContainerVisible: Flow + val isSystemInfoVisible: Flow + + // TODO(b/364360986): Add isOngoingActivityChipVisible: Flow + /** * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where * status bar and navigation icons dim. In this mode, a notification dot appears where the @@ -93,17 +108,26 @@ interface CollapsedStatusBarViewModel { * [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE]. */ fun areNotificationsLightsOut(displayId: Int): Flow + + /** Models the current visibility for a specific child view of status bar. */ + data class VisibilityModel( + @View.Visibility val visibility: Int, + /** True if a visibility change should be animated. */ + val shouldAnimateChange: Boolean, + ) } @SysUISingleton class CollapsedStatusBarViewModelImpl @Inject constructor( + collapsedStatusBarInteractor: CollapsedStatusBarInteractor, private val lightsOutInteractor: LightsOutInteractor, private val notificationsInteractor: ActiveNotificationsInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractor: SceneInteractor, sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor, + shadeInteractor: ShadeInteractor, ongoingActivityChipsViewModel: OngoingActivityChipsViewModel, @Application coroutineScope: CoroutineScope, ) : CollapsedStatusBarViewModel { @@ -148,4 +172,59 @@ constructor( } .distinctUntilChanged() } + + /** + * True if the current SysUI state can show the home status bar (aka this status bar), and false + * if we shouldn't be showing any part of the home status bar. + */ + private val isHomeScreenStatusBarAllowedLegacy: Flow = + combine( + keyguardTransitionInteractor.currentKeyguardState, + shadeInteractor.isAnyFullyExpanded, + ) { currentKeyguardState, isShadeExpanded -> + (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) && !isShadeExpanded + // TODO(b/364360986): Add edge cases, like secure camera launch. + } + + private val isHomeScreenStatusBarAllowed: Flow = + if (SceneContainerFlag.isEnabled) { + isHomeStatusBarAllowedByScene + } else { + isHomeScreenStatusBarAllowedLegacy + } + + override val isClockVisible: Flow = + combine( + isHomeScreenStatusBarAllowed, + collapsedStatusBarInteractor.visibilityViaDisableFlags, + ) { isStatusBarAllowed, visibilityViaDisableFlags -> + val showClock = isStatusBarAllowed && visibilityViaDisableFlags.isClockAllowed + // TODO(b/364360986): Take CollapsedStatusBarFragment.clockHiddenMode into account. + VisibilityModel(showClock.toVisibilityInt(), visibilityViaDisableFlags.animate) + } + override val isNotificationIconContainerVisible: Flow = + combine( + isHomeScreenStatusBarAllowed, + collapsedStatusBarInteractor.visibilityViaDisableFlags, + ) { isStatusBarAllowed, visibilityViaDisableFlags -> + val showNotificationIconContainer = + isStatusBarAllowed && visibilityViaDisableFlags.areNotificationIconsAllowed + VisibilityModel( + showNotificationIconContainer.toVisibilityInt(), + visibilityViaDisableFlags.animate + ) + } + override val isSystemInfoVisible: Flow = + combine( + isHomeScreenStatusBarAllowed, + collapsedStatusBarInteractor.visibilityViaDisableFlags, + ) { isStatusBarAllowed, visibilityViaDisableFlags -> + val showSystemInfo = isStatusBarAllowed && visibilityViaDisableFlags.isSystemInfoAllowed + VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate) + } + + @View.Visibility + private fun Boolean.toVisibilityInt(): Int { + return if (this) View.VISIBLE else View.GONE + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 5ba5c0685941ddc94104630d2af388b2a4906b5d..caf09a3b638eef32bb12805efe3a143307353e38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -46,6 +46,8 @@ constructor( private val tag = "AvalancheController" private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG) + var baseEntryMapStr : () -> String = { "baseEntryMapStr not initialized" } + var enableAtRuntime = true set(value) { if (!value) { @@ -116,32 +118,43 @@ constructor( val key = getKey(entry) if (runnable == null) { - headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Runnable NULL, stop") + headsUpManagerLogger.logAvalancheUpdate( + caller, isEnabled, key, + "Runnable NULL, stop. ${getStateStr()}" + ) return } if (!isEnabled) { - headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, - "NOT ENABLED, run runnable") + headsUpManagerLogger.logAvalancheUpdate( + caller, isEnabled, key, + "NOT ENABLED, run runnable. ${getStateStr()}" + ) runnable.run() return } if (entry == null) { - headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Entry NULL, stop") + headsUpManagerLogger.logAvalancheUpdate( + caller, isEnabled, key, + "Entry NULL, stop. ${getStateStr()}" + ) return } if (debug) { debugRunnableLabelMap[runnable] = caller } - var outcome = "" + var stateAfter = "" if (isShowing(entry)) { - outcome = "update showing" runnable.run() + stateAfter = "update showing" + } else if (entry in nextMap) { - outcome = "update next" nextMap[entry]?.add(runnable) + stateAfter = "update next" + } else if (headsUpEntryShowing == null) { - outcome = "show now" showNow(entry, arrayListOf(runnable)) + stateAfter = "show now" + } else { // Clean up invalid state when entry is in list but not map and vice versa if (entry in nextMap) nextMap.remove(entry) @@ -162,8 +175,8 @@ constructor( ) } } - outcome += getStateStr() - headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome) + stateAfter += getStateStr() + headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled = true, key, stateAfter) } @VisibleForTesting @@ -181,32 +194,40 @@ constructor( val key = getKey(entry) if (runnable == null) { - headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, "Runnable NULL, stop") + headsUpManagerLogger.logAvalancheDelete( + caller, isEnabled, key, + "Runnable NULL, stop. ${getStateStr()}" + ) return } if (!isEnabled) { - headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, - "NOT ENABLED, run runnable") runnable.run() + headsUpManagerLogger.logAvalancheDelete( + caller, isEnabled = false, key, + "NOT ENABLED, run runnable. ${getStateStr()}" + ) return } if (entry == null) { - headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, - "Entry NULL, run runnable") runnable.run() + headsUpManagerLogger.logAvalancheDelete( + caller, isEnabled = true, key, + "Entry NULL, run runnable. ${getStateStr()}" + ) return } - var outcome = "" + val stateAfter: String if (entry in nextMap) { - outcome = "remove from next" if (entry in nextMap) nextMap.remove(entry) if (entry in nextList) nextList.remove(entry) uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED) + stateAfter = "remove from next. ${getStateStr()}" + } else if (entry in debugDropSet) { - outcome = "remove from dropset" debugDropSet.remove(entry) + stateAfter = "remove from dropset. ${getStateStr()}" + } else if (isShowing(entry)) { - outcome = "remove showing" previousHunKey = getKey(headsUpEntryShowing) // Show the next HUN before removing this one, so that we don't tell listeners // onHeadsUpPinnedModeChanged, which causes @@ -214,11 +235,13 @@ constructor( // HUN is animating out, resulting in a flicker. showNext() runnable.run() + stateAfter = "remove showing. ${getStateStr()}" + } else { - outcome = "run runnable for untracked shown" runnable.run() + stateAfter = "run runnable for untracked shown HUN. ${getStateStr()}" } - headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome) + headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), stateAfter) } /** @@ -400,12 +423,14 @@ constructor( } private fun getStateStr(): String { - return "\navalanche state:" + + return "\nAvalancheController:" + "\n\tshowing: [${getKey(headsUpEntryShowing)}]" + "\n\tprevious: [$previousHunKey]" + "\n\tnext list: $nextListStr" + "\n\tnext map: $nextMapStr" + - "\n\tdropped: $dropSetStr" + "\n\tdropped: $dropSetStr" + + "\nBHUM.mHeadsUpEntryMap: " + + baseEntryMapStr() } private val dropSetStr: String diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index f37393ac6729a23e20b26aeff33378cf3afab2d8..30524a5f26d54080ee46ad386ee7630b73c86bda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -116,6 +116,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { mAccessibilityMgr = accessibilityManagerWrapper; mUiEventLogger = uiEventLogger; mAvalancheController = avalancheController; + mAvalancheController.setBaseEntryMapStr(this::getEntryMapStr); Resources resources = context.getResources(); mMinimumDisplayTime = NotificationThrottleHun.isEnabled() ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time); @@ -589,6 +590,18 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { dumpInternal(pw, args); } + private String getEntryMapStr() { + if (mHeadsUpEntryMap.isEmpty()) { + return "EMPTY"; + } + StringBuilder entryMapStr = new StringBuilder(); + for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) { + entryMapStr.append("\n\t").append( + entry.mEntry == null ? "null" : entry.mEntry.getKey()); + } + return entryMapStr.toString(); + } + protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) { pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); @@ -992,7 +1005,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * Clear any pending removal runnables. */ public void cancelAutoRemovalCallbacks(@Nullable String reason) { - mLogger.logAutoRemoveCancelRequest(this.mEntry, reason); Runnable runnable = () -> { final boolean removed = cancelAutoRemovalCallbackInternal(); @@ -1001,6 +1013,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } }; if (mEntry != null && isHeadsUpEntry(mEntry.getKey())) { + mLogger.logAutoRemoveCancelRequest(this.mEntry, reason); mAvalancheController.update(this, runnable, reason + " cancelAutoRemovalCallbacks"); } else { // Just removed diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index 600270c7189a4f2ff6c84494779e2596e0c08ab7..41112cb5f8e7d16657aa508d746b88616c1023a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -52,7 +52,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { caller: String, isEnabled: Boolean, notifEntryKey: String, - outcome: String + stateAfter: String ) { buffer.log( TAG, @@ -60,7 +60,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { { str1 = caller str2 = notifEntryKey - str3 = outcome + str3 = stateAfter bool1 = isEnabled }, { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" } @@ -71,7 +71,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { caller: String, isEnabled: Boolean, notifEntryKey: String, - outcome: String + stateAfter: String ) { buffer.log( TAG, @@ -79,7 +79,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { { str1 = caller str2 = notifEntryKey - str3 = outcome + str3 = stateAfter bool1 = isEnabled }, { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" } @@ -136,7 +136,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { str1 = entry.logKey str2 = reason ?: "unknown" }, - { "request: cancel auto remove of $str1 reason: $str2" } + { "$str2 => request: cancelAutoRemovalCallbacks: $str1" } ) } @@ -148,7 +148,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { str1 = entry.logKey str2 = reason ?: "unknown" }, - { "cancel auto remove of $str1 reason: $str2" } + { "$str2 => cancel auto remove: $str1" } ) } @@ -161,7 +161,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { str2 = reason bool1 = isWaiting }, - { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" } + { "request: $str2 => removeEntry: $str1 isWaiting: $isWaiting" } ) } @@ -174,7 +174,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { str2 = reason bool1 = isWaiting }, - { "$str2 => remove entry $str1 isWaiting: $isWaiting" } + { "$str2 => removeEntry: $str1 isWaiting: $isWaiting" } ) } @@ -216,12 +216,12 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { str1 = logKey(key) str2 = reason }, - { "remove notification $str1 when headsUpEntry is null, reason: $str2" } + { "remove notif $str1 when headsUpEntry is null, reason: $str2" } ) } fun logNotificationActuallyRemoved(entry: NotificationEntry) { - buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "removed: $str1 " }) } fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) { @@ -233,7 +233,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { bool1 = alert bool2 = hasEntry }, - { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" } + { "request: update notif $str1 alert: $bool1 hasEntry: $bool2" } ) } @@ -246,7 +246,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { bool1 = alert bool2 = hasEntry }, - { "update notification $str1 alert: $bool1 hasEntry: $bool2" } + { "update notif $str1 alert: $bool1 hasEntry: $bool2" } ) } @@ -281,7 +281,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { bool1 = isPinned str2 = reason }, - { "$str2 => set entry pinned $str1 pinned: $bool1" } + { "$str2 => setEntryPinned[$bool1]: $str1" } ) } @@ -290,7 +290,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { TAG, INFO, { bool1 = hasPinnedNotification }, - { "has pinned notification changed to $bool1" } + { "hasPinnedNotification[$bool1]" } ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index cf9f9f4a2a81ea68a1be3144b98cedead293df45..3cb7090ea6d47c25aeb74a7661f421f181ac0845 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -21,6 +21,7 @@ import android.os.UserManager.DISALLOW_CONFIG_LOCATION import android.os.UserManager.DISALLOW_MICROPHONE_TOGGLE import android.os.UserManager.DISALLOW_SHARE_LOCATION import com.android.systemui.Flags +import com.android.systemui.modes.shared.ModesUi import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.TileCategory @@ -142,7 +143,7 @@ interface PolicyModule { dndTile: Provider, modesTile: Provider, ): QSTileImpl<*> { - return if (android.app.Flags.modesUi()) modesTile.get() else dndTile.get() + return if (ModesUi.isEnabled) modesTile.get() else dndTile.get() } /** Inject flashlight config */ @@ -169,7 +170,7 @@ interface PolicyModule { factory: QSTileViewModelFactory.Static, mapper: FlashlightMapper, stateInteractor: FlashlightTileDataInteractor, - userActionInteractor: FlashlightTileUserActionInteractor + userActionInteractor: FlashlightTileUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(FLASHLIGHT_TILE_SPEC), @@ -206,7 +207,7 @@ interface PolicyModule { factory: QSTileViewModelFactory.Static, mapper: LocationTileMapper, stateInteractor: LocationTileDataInteractor, - userActionInteractor: LocationTileUserActionInteractor + userActionInteractor: LocationTileUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(LOCATION_TILE_SPEC), @@ -239,7 +240,7 @@ interface PolicyModule { factory: QSTileViewModelFactory.Static, mapper: AlarmTileMapper, stateInteractor: AlarmTileDataInteractor, - userActionInteractor: AlarmTileUserActionInteractor + userActionInteractor: AlarmTileUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(ALARM_TILE_SPEC), @@ -272,7 +273,7 @@ interface PolicyModule { factory: QSTileViewModelFactory.Static, mapper: UiModeNightTileMapper, stateInteractor: UiModeNightTileDataInteractor, - userActionInteractor: UiModeNightTileUserActionInteractor + userActionInteractor: UiModeNightTileUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(UIMODENIGHT_TILE_SPEC), @@ -306,7 +307,7 @@ interface PolicyModule { factory: QSTileViewModelFactory.Static, mapper: WorkModeTileMapper, stateInteractor: WorkModeTileDataInteractor, - userActionInteractor: WorkModeTileUserActionInteractor + userActionInteractor: WorkModeTileUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(WORK_MODE_TILE_SPEC), @@ -406,7 +407,7 @@ interface PolicyModule { @IntoMap @StringKey(DND_TILE_SPEC) fun provideDndOrModesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = - if (android.app.Flags.modesUi()) { + if (ModesUi.isEnabled) { QSTileConfig( tileSpec = TileSpec.create(DND_TILE_SPEC), uiConfig = @@ -438,9 +439,9 @@ interface PolicyModule { factory: QSTileViewModelFactory.Static, mapper: ModesTileMapper, stateInteractor: ModesTileDataInteractor, - userActionInteractor: ModesTileUserActionInteractor + userActionInteractor: ModesTileUserActionInteractor, ): QSTileViewModel = - if (android.app.Flags.modesUi() && Flags.qsNewTilesFuture()) + if (ModesUi.isEnabled && Flags.qsNewTilesFuture()) factory.create( TileSpec.create(DND_TILE_SPEC), userActionInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 31776cf5ad1bf4409c6e3125d3737166ebb0925b..16d5f8d305932da87640ddc1f282645d222b8f9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -106,7 +106,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50; private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33; private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83; - private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f; + public static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f; private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120; private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt index dbeaa59cd219ce28b5ec7270a6c43ec1693268f9..ba45942177a28874c29f4d35336e292675f9b908 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -27,7 +27,10 @@ import com.android.settingslib.notification.modes.ZenIcon import com.android.settingslib.notification.modes.ZenIconLoader import com.android.settingslib.notification.modes.ZenMode import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.modes.shared.ModesUi import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository +import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository +import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo import java.time.Duration @@ -51,7 +54,17 @@ constructor( private val notificationSettingsRepository: NotificationSettingsRepository, @Background private val bgDispatcher: CoroutineDispatcher, private val iconLoader: ZenIconLoader, + private val deviceProvisioningRepository: DeviceProvisioningRepository, + private val userSetupRepository: UserSetupRepository, ) { + val isZenAvailable: Flow = + combine( + deviceProvisioningRepository.isDeviceProvisioned, + userSetupRepository.isUserSetUp, + ) { isDeviceProvisioned, isUserSetUp -> + isDeviceProvisioned && isUserSetUp + } + val isZenModeEnabled: Flow = zenModeRepository.globalZenMode .map { @@ -80,6 +93,18 @@ constructor( val modes: Flow> = zenModeRepository.modes + /** + * Returns the special "manual DND" mode. + * + * This is only meant as a temporary solution for "legacy" UI pieces that handle DND + * specifically; any new or migrated features should use modes more generally, through [modes] + * or [activeModes]. + */ + val dndMode: Flow by lazy { + ModesUi.assertInNewMode() + zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } } + } + /** Flow returning the currently active mode(s), if any. */ val activeModes: Flow = modes @@ -113,10 +138,11 @@ constructor( Log.e( TAG, "Interactor cannot handle showing the zen duration prompt. " + - "Please use EnableZenModeDialog when this setting is active." + "Please use EnableZenModeDialog when this setting is active.", ) null } + ZEN_DURATION_FOREVER -> null else -> Duration.ofMinutes(zenDuration.toLong()) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt index af93880bad51e9e6d66723954cdd59f113dd55a8..27bc6d36c1e6e4058e8b1e4c889b623eab78e334 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt @@ -59,32 +59,26 @@ fun ModeTile(viewModel: ModeTileViewModel) { ) CompositionLocalProvider(LocalContentColor provides contentColor) { - Surface( - color = tileColor, - shape = RoundedCornerShape(16.dp), - ) { + Surface(color = tileColor, shape = RoundedCornerShape(16.dp)) { Row( modifier = Modifier.combinedClickable( onClick = viewModel.onClick, onLongClick = viewModel.onLongClick, - onLongClickLabel = viewModel.onLongClickLabel + onLongClickLabel = viewModel.onLongClickLabel, ) - .padding(20.dp) + .padding(16.dp) .semantics { stateDescription = viewModel.stateDescription }, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = - Arrangement.spacedBy( - space = 10.dp, - alignment = Alignment.Start, - ), + Arrangement.spacedBy(space = 8.dp, alignment = Alignment.Start), ) { Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp)) Column { Text( viewModel.text, fontWeight = FontWeight.W500, - modifier = Modifier.tileMarquee().testTag("name") + modifier = Modifier.tileMarquee().testTag("name"), ) Text( viewModel.subtext, @@ -94,7 +88,7 @@ fun ModeTile(viewModel: ModeTileViewModel) { .testTag(if (viewModel.enabled) "stateOn" else "stateOff") .clearAndSetSemantics { contentDescription = viewModel.subtextDescription - } + }, ) } } @@ -103,8 +97,5 @@ fun ModeTile(viewModel: ModeTileViewModel) { } private fun Modifier.tileMarquee(): Modifier { - return this.basicMarquee( - iterations = 1, - initialDelayMillis = 200, - ) + return this.basicMarquee(iterations = 1) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt index 73d361f69eace145359796a041101266db8f6314..5953ea5989299bfc37a82517ffe66a95054ff99f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy.ui.dialog.composable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.runtime.Composable @@ -27,23 +26,20 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.Flags import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel @Composable fun ModeTileGrid(viewModel: ModesDialogViewModel) { val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList()) - // TODO(b/346519570): Handle what happens when we have more than a few modes. LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.padding(8.dp).fillMaxWidth().heightIn(max = 300.dp), + columns = GridCells.Fixed(if (Flags.modesDialogSingleRows()) 1 else 2), + modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - items( - tiles.size, - key = { index -> tiles[index].id }, - ) { index -> + items(tiles.size, key = { index -> tiles[index].id }) { index -> ModeTile(viewModel = tiles[index]) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt new file mode 100644 index 0000000000000000000000000000000000000000..421e5c45bbfe2a1aa2ead0b39719047888364ab4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.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.systemui.statusbar.window + +import android.view.View +import android.view.ViewGroup +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.fragments.FragmentHostManager +import java.util.Optional + +/** Encapsulates all logic for the status bar window state management. */ +interface StatusBarWindowController { + val statusBarHeight: Int + + /** Rereads the status bar height and reapplies the current state if the height is different. */ + fun refreshStatusBarHeight() + + /** Adds the status bar view to the window manager. */ + fun attach() + + /** Adds the given view to the status bar window view. */ + fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) + + /** Returns the status bar window's background view. */ + val backgroundView: View + + /** Returns a fragment host manager for the status bar window view. */ + val fragmentHostManager: FragmentHostManager + + /** + * Provides an updated animation controller if we're animating a view in the status bar. + * + * This is needed because we have to make sure that the status bar window matches the full + * screen during the animation and that we are expanding the view below the other status bar + * text. + * + * @param rootView the root view of the animation + * @param animationController the default animation controller to use + * @return If the animation is on a view in the status bar, returns an Optional containing an + * updated animation controller that handles status-bar-related animation details. Returns an + * empty optional if the animation is *not* on a view in the status bar. + */ + fun wrapAnimationControllerIfInStatusBar( + rootView: View, + animationController: ActivityTransitionAnimator.Controller, + ): Optional + + /** Set force status bar visible. */ + fun setForceStatusBarVisible(forceStatusBarVisible: Boolean) + + /** + * Sets whether an ongoing process requires the status bar to be forced visible. + * + * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing + * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to + * false but this method is set to true, then the status bar **will** be visible. + * + * TODO(b/195839150): We should likely merge this method and {@link + * this#setForceStatusBarVisible} together and use some sort of ranking system instead. + */ + fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java similarity index 81% rename from packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java rename to packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java index c30a6b7d0f177f521f6a83b94e05643dd908d520..1ee7cf3490f44d8aa5e6c279d7a6b8557c2f9ab0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java @@ -28,7 +28,6 @@ import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN; import android.content.Context; -import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -46,28 +45,30 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; +import androidx.annotation.NonNull; + import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DelegateTransitionAnimatorController; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; +import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener; -import java.util.Optional; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; -import javax.inject.Inject; +import java.util.Optional; /** * Encapsulates all logic for the status bar window state management. */ -@SysUISingleton -public class StatusBarWindowController { +public class StatusBarWindowControllerImpl implements StatusBarWindowController { private static final String TAG = "StatusBarWindowController"; private static final boolean DEBUG = false; @@ -88,21 +89,20 @@ public class StatusBarWindowController { private final WindowManager.LayoutParams mLpChanged; private final Binder mInsetsSourceOwner = new Binder(); - @Inject - public StatusBarWindowController( - Context context, - @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, + @AssistedInject + public StatusBarWindowControllerImpl( + @Assisted Context context, + @InternalWindowViewInflater StatusBarWindowViewInflater statusBarWindowViewInflater, + @Assisted ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, FragmentService fragmentService, - @Main Resources resources, Optional unfoldTransitionProgressProvider) { mContext = context; mWindowManager = viewCaptureAwareWindowManager; mIWindowManager = iWindowManager; mContentInsetsProvider = contentInsetsProvider; - mStatusBarWindowView = statusBarWindowView; + mStatusBarWindowView = statusBarWindowViewInflater.inflate(context); mFragmentService = fragmentService; mLaunchAnimationContainer = mStatusBarWindowView.findViewById( R.id.status_bar_launch_animation_container); @@ -117,14 +117,12 @@ public class StatusBarWindowController { /* attachedViewProvider=*/ () -> mStatusBarWindowView))); } + @Override public int getStatusBarHeight() { return mBarHeight; } - /** - * Rereads the status bar height and reapplys the current state if the height - * is different. - */ + @Override public void refreshStatusBarHeight() { Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight"); try { @@ -141,9 +139,7 @@ public class StatusBarWindowController { } } - /** - * Adds the status bar view to the window manager. - */ + @Override public void attach() { // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is @@ -161,54 +157,47 @@ public class StatusBarWindowController { apply(mCurrentState); } - /** Adds the given view to the status bar window view. */ - public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) { + @Override + public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) { mStatusBarWindowView.addView(view, layoutParams); } - /** Returns the status bar window's background view. */ + @NonNull + @Override public View getBackgroundView() { return mStatusBarWindowView.findViewById(R.id.status_bar_container); } - /** Returns a fragment host manager for the status bar window view. */ + @NonNull + @Override public FragmentHostManager getFragmentHostManager() { return mFragmentService.getFragmentHostManager(mStatusBarWindowView); } - /** - * Provides an updated animation controller if we're animating a view in the status bar. - * - * This is needed because we have to make sure that the status bar window matches the full - * screen during the animation and that we are expanding the view below the other status bar - * text. - * - * @param rootView the root view of the animation - * @param animationController the default animation controller to use - * @return If the animation is on a view in the status bar, returns an Optional containing an - * updated animation controller that handles status-bar-related animation details. Returns an - * empty optional if the animation is *not* on a view in the status bar. - */ + @NonNull + @Override public Optional wrapAnimationControllerIfInStatusBar( - View rootView, ActivityTransitionAnimator.Controller animationController) { + @NonNull View rootView, + @NonNull ActivityTransitionAnimator.Controller animationController) { if (rootView != mStatusBarWindowView) { return Optional.empty(); } animationController.setTransitionContainer(mLaunchAnimationContainer); - return Optional.of(new DelegateTransitionAnimatorController(animationController) { - @Override - public void onTransitionAnimationStart(boolean isExpandingFullyAbove) { - getDelegate().onTransitionAnimationStart(isExpandingFullyAbove); - setLaunchAnimationRunning(true); - } - - @Override - public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) { - getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove); - setLaunchAnimationRunning(false); - } - }); + return Optional.of( + new DelegateTransitionAnimatorController(animationController) { + @Override + public void onTransitionAnimationStart(boolean isExpandingFullyAbove) { + getDelegate().onTransitionAnimationStart(isExpandingFullyAbove); + setLaunchAnimationRunning(true); + } + + @Override + public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) { + getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove); + setLaunchAnimationRunning(false); + } + }); } private WindowManager.LayoutParams getBarLayoutParams(int rotation) { @@ -275,22 +264,13 @@ public class StatusBarWindowController { } } - /** Set force status bar visible. */ + @Override public void setForceStatusBarVisible(boolean forceStatusBarVisible) { mCurrentState.mForceStatusBarVisible = forceStatusBarVisible; apply(mCurrentState); } - /** - * Sets whether an ongoing process requires the status bar to be forced visible. - * - * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing - * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to - * false but this method is set to true, then the status bar **will** be visible. - * - * TODO(b/195839150): We should likely merge this method and - * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead. - */ + @Override public void setOngoingProcessRequiresStatusBarVisible(boolean visible) { mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible; apply(mCurrentState); @@ -372,4 +352,13 @@ public class StatusBarWindowController { mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars(); } } + + @AssistedFactory + public interface Factory { + /** Creates a new instance. */ + StatusBarWindowControllerImpl create( + Context context, + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt index 1c7debcdf39dd3b2c8a2c997bbde9cc21914dc0f..ebfbac7be9162223c0de8748126bbd49096bbb39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt @@ -1,47 +1,36 @@ package com.android.systemui.statusbar.window -import android.view.LayoutInflater -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton +import dagger.Binds import dagger.Module -import dagger.Provides import javax.inject.Qualifier /** Module providing dependencies related to the status bar window. */ @Module abstract class StatusBarWindowModule { + /** - * Provides a [StatusBarWindowView]. + * Binds a [StatusBarWindowViewInflater]. * - * Only [StatusBarWindowController] should inject the view. + * Only [StatusBarWindowControllerImpl] should inject it. */ - @Module - companion object { - @JvmStatic - @Provides - @SysUISingleton - @InternalWindowView - fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView { - return layoutInflater.inflate( - R.layout.super_status_bar, - /* root= */null - ) as StatusBarWindowView? - ?: throw IllegalStateException( - "R.layout.super_status_bar could not be properly inflated" - ) - } - } + @Binds + @SysUISingleton + @InternalWindowViewInflater + abstract fun providesStatusBarWindowViewInflater( + inflaterImpl: StatusBarWindowViewInflaterImpl + ): StatusBarWindowViewInflater /** - * We want [StatusBarWindowView] to be provided to [StatusBarWindowController]'s constructor via - * dagger so that we can provide a fake window view when testing the controller. However, we wan - * want *only* the controller to be able to inject the window view. + * We want [StatusBarWindowViewInflater] to be provided to [StatusBarWindowControllerImpl]'s + * constructor via dagger so that we can provide a fake window view when testing the controller. + * However, we wan want *only* the controller to be able to inject the window view. * - * This protected qualifier annotation achieves this. [StatusBarWindowView] can only be injected - * if it's annotated with [InternalWindowView], and only classes inside this [statusbar.window] - * package can access the annotation. + * This protected qualifier annotation achieves this. [StatusBarWindowViewInflater] can only be + * injected if it's annotated with [InternalWindowViewInflater], and only classes inside this + * [statusbar.window] package can access the annotation. */ @Retention(AnnotationRetention.BINARY) @Qualifier - protected annotation class InternalWindowView + protected annotation class InternalWindowViewInflater } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt index 8f424b2e251e9e66ae1a4d2d341aa45f89b6fcc8..fa9c6b2c8151216b023f430054a95086f9a1fdcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar.window import android.app.StatusBarManager -import android.app.StatusBarManager.WindowVisibleState import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR +import android.app.StatusBarManager.WindowVisibleState import android.app.StatusBarManager.windowStateToString import android.util.Log import com.android.systemui.dagger.SysUISingleton @@ -31,23 +31,27 @@ import javax.inject.Inject /** * A centralized class maintaining the state of the status bar window. * + * @deprecated use + * [com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepository] instead. + * * Classes that want to get updates about the status bar window state should subscribe to this class * via [addListener] and should NOT add their own callback on [CommandQueue]. */ @SysUISingleton -class StatusBarWindowStateController @Inject constructor( - @DisplayId private val thisDisplayId: Int, - commandQueue: CommandQueue -) { - private val commandQueueCallback = object : CommandQueue.Callbacks { - override fun setWindowState( - displayId: Int, - @StatusBarManager.WindowType window: Int, - @WindowVisibleState state: Int - ) { - this@StatusBarWindowStateController.setWindowState(displayId, window, state) +@Deprecated("Use StatusBarWindowRepository instead") +class StatusBarWindowStateController +@Inject +constructor(@DisplayId private val thisDisplayId: Int, commandQueue: CommandQueue) { + private val commandQueueCallback = + object : CommandQueue.Callbacks { + override fun setWindowState( + displayId: Int, + @StatusBarManager.WindowType window: Int, + @WindowVisibleState state: Int, + ) { + this@StatusBarWindowStateController.setWindowState(displayId, window, state) + } } - } private val listeners: MutableSet = HashSet() @WindowVisibleState private var windowState: Int = WINDOW_STATE_SHOWING @@ -71,7 +75,7 @@ class StatusBarWindowStateController @Inject constructor( private fun setWindowState( displayId: Int, @StatusBarManager.WindowType window: Int, - @WindowVisibleState state: Int + @WindowVisibleState state: Int, ) { if (displayId != thisDisplayId) { return diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt new file mode 100644 index 0000000000000000000000000000000000000000..f030a4ac0d0e40c21ba84c2ae9ad215bb8d842fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.window + +import android.content.Context +import android.view.LayoutInflater +import com.android.systemui.res.R +import javax.inject.Inject + +/** + * Inflates a [StatusBarWindowView]. Exists so that it can be injected into + * [StatusBarWindowControllerImpl] and be swapped for a fake implementation in tests. + */ +interface StatusBarWindowViewInflater { + fun inflate(context: Context): StatusBarWindowView +} + +class StatusBarWindowViewInflaterImpl @Inject constructor() : StatusBarWindowViewInflater { + + override fun inflate(context: Context): StatusBarWindowView { + val layoutInflater = LayoutInflater.from(context) + return layoutInflater.inflate(R.layout.super_status_bar, /* root= */ null) + as StatusBarWindowView? + ?: throw IllegalStateException( + "R.layout.super_status_bar could not be properly inflated" + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..678576d1b4506c8d6f4934a90585540895cdcd05 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.window.data.repository + +import android.app.StatusBarManager +import android.app.StatusBarManager.WINDOW_STATE_HIDDEN +import android.app.StatusBarManager.WINDOW_STATE_HIDING +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR +import android.app.StatusBarManager.WindowVisibleState +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.window.data.model.StatusBarWindowState +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** + * A centralized class maintaining the state of the status bar window. + * + * Classes that want to get updates about the status bar window state should subscribe to + * [windowState] and should NOT add their own callback on [CommandQueue]. + */ +@SysUISingleton +class StatusBarWindowStateRepository +@Inject +constructor( + private val commandQueue: CommandQueue, + @DisplayId private val thisDisplayId: Int, + @Application private val scope: CoroutineScope, +) { + val windowState: StateFlow = + conflatedCallbackFlow { + val callback = + object : CommandQueue.Callbacks { + override fun setWindowState( + displayId: Int, + @StatusBarManager.WindowType window: Int, + @WindowVisibleState state: Int, + ) { + // TODO(b/364360986): Log the window state changes. + if (displayId != thisDisplayId) { + return + } + if (window != WINDOW_STATUS_BAR) { + return + } + trySend(state.toWindowState()) + } + } + commandQueue.addCallback(callback) + awaitClose { commandQueue.removeCallback(callback) } + } + // Use Eagerly because we always need to know about the status bar window state + .stateIn(scope, SharingStarted.Eagerly, StatusBarWindowState.Hidden) + + @WindowVisibleState + private fun Int.toWindowState(): StatusBarWindowState { + return when (this) { + WINDOW_STATE_SHOWING -> StatusBarWindowState.Showing + WINDOW_STATE_HIDING -> StatusBarWindowState.Hiding + WINDOW_STATE_HIDDEN -> StatusBarWindowState.Hidden + else -> StatusBarWindowState.Hidden + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/shared/model/StatusBarWindowState.kt similarity index 70% rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt rename to packages/SystemUI/src/com/android/systemui/statusbar/window/shared/model/StatusBarWindowState.kt index 320c2ec1bb9963ab994c1afe4d001bd04d95ff98..a99046ee05e97586deb964fbce02ab25acef4bb3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/shared/model/StatusBarWindowState.kt @@ -14,11 +14,14 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.domain.interactor +package com.android.systemui.statusbar.window.data.model -import com.android.systemui.kosmos.Kosmos - -val Kosmos.infiniteGridConsistencyInteractor by - Kosmos.Fixture { - InfiniteGridConsistencyInteractor(iconTilesInteractor, fixedColumnsSizeInteractor) - } +/** + * Represents the state of the status bar *window* as a whole (as opposed to individual views within + * the status bar). + */ +enum class StatusBarWindowState { + Showing, + Hiding, + Hidden, +} diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index bbfa32b623ef01803078f4c6f238d2e1b853ea16..32a4f12777ac6f5da59edd0735b5ec8d002c4fee 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -117,7 +117,14 @@ public class ToastUI implements int displayId) { Runnable showToastRunnable = () -> { UserHandle userHandle = UserHandle.getUserHandleForUid(uid); - Context context = mContext.createContextAsUser(userHandle, 0); + Context context; + try { + context = mContext.createContextAsUser(userHandle, 0); + } catch (IllegalStateException e) { + // b/366533044 : Own package not found for systemui + Log.e(TAG, "Cannot create toast because cannot create context", e); + return; + } DisplayManager mDisplayManager = mContext.getSystemService(DisplayManager.class); Display display = mDisplayManager.getDisplay(displayId); diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt index 1a41987a8e5b9ee91824060b533647457f0be1fa..80ea925eabc76ea12c090255b00445d40dfe04cf 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt @@ -30,12 +30,12 @@ class TouchpadGesturesInteractor( private val logger: InputDeviceTutorialLogger, ) { fun disableGestures() { - logger.log("Disabling touchpad gestures across the system") + logger.d("Disabling touchpad gestures across the system") setGesturesState(disabled = true) } fun enableGestures() { - logger.log("Enabling touchpad gestures across the system") + logger.d("Enabling touchpad gestures across the system") setGesturesState(disabled = false) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 5a77c04924dddd3eedf411f4bdb7c71c96cc015c..6acc891e93d54f09171120605421ee532603e662 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -78,21 +78,21 @@ private fun TutorialSelectionButtons( modifier = modifier ) { TutorialButton( - text = stringResource(R.string.touchpad_tutorial_back_gesture_button), - onClick = onBackTutorialClicked, + text = stringResource(R.string.touchpad_tutorial_home_gesture_button), + onClick = onHomeTutorialClicked, color = MaterialTheme.colorScheme.primary, modifier = Modifier.weight(1f) ) TutorialButton( - text = stringResource(R.string.touchpad_tutorial_home_gesture_button), - onClick = onHomeTutorialClicked, - color = MaterialTheme.colorScheme.secondary, + text = stringResource(R.string.touchpad_tutorial_back_gesture_button), + onClick = onBackTutorialClicked, + color = MaterialTheme.colorScheme.tertiary, modifier = Modifier.weight(1f) ) TutorialButton( text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button), onClick = onRecentAppsTutorialClicked, - color = MaterialTheme.colorScheme.tertiary, + color = MaterialTheme.colorScheme.secondary, modifier = Modifier.weight(1f) ) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index 46ea352ff85a13d2bb069cc9be8eebe9135b3dd0..d03b2e71739881c714f5f18c3020de424484100f 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -29,12 +29,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext -import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen -import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.ACTION_KEY import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.RECENT_APPS_GESTURE @@ -59,7 +57,7 @@ constructor( } // required to handle 3+ fingers on touchpad window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) - window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) + logger.logOpenTutorial(TutorialContext.TOUCHPAD_TUTORIAL) } private fun finishTutorial() { @@ -104,10 +102,5 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, onBack = { vm.goTo(TUTORIAL_SELECTION) }, ) - ACTION_KEY -> // TODO(b/358105049) move action key tutorial to OOBE flow - ActionKeyTutorialScreen( - onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, - onBack = { vm.goTo(TUTORIAL_SELECTION) }, - ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt index 599e1b1eff75e80c8dcaf186b8f9a01325cb7ce6..c56dcf3bf062a58459432f392925cc28898811c9 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt @@ -65,5 +65,4 @@ enum class Screen { BACK_GESTURE, HOME_GESTURE, RECENT_APPS_GESTURE, - ACTION_KEY, } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt index 8ecf250e2bbde33217626a385ba4bd26a02fa442..2af84c7e46f03809d5170d55f6a47d297f4f81f1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt @@ -37,7 +37,7 @@ class GlobalCoroutinesModule { @Application fun applicationScope( @Main dispatcherContext: CoroutineContext, - ): CoroutineScope = CoroutineScope(dispatcherContext) + ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope")) @Provides @Singleton @@ -51,15 +51,7 @@ class GlobalCoroutinesModule { @Provides @Singleton @Main - fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext { - return Dispatchers.Main.immediate + tracingCoroutineContext - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Provides - @Tracing - @Singleton - fun tracingCoroutineContext(): CoroutineContext { - return createCoroutineTracingContext() + fun mainCoroutineContext(): CoroutineContext { + return Dispatchers.Main.immediate } } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt index a03221e034678ba5df3f297748762d00a64d87d4..3c0682822564d9af4dba9f5cc46390ab2b3ebaed 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt @@ -91,10 +91,9 @@ class SysUICoroutinesModule { @Background @SysUISingleton fun bgCoroutineContext( - @Tracing tracingCoroutineContext: CoroutineContext, @Background bgCoroutineDispatcher: CoroutineDispatcher, ): CoroutineContext { - return bgCoroutineDispatcher + tracingCoroutineContext + return bgCoroutineDispatcher } /** Coroutine dispatcher for background operations on for UI. */ @@ -112,9 +111,8 @@ class SysUICoroutinesModule { @UiBackground @SysUISingleton fun uiBgCoroutineContext( - @Tracing tracingCoroutineContext: CoroutineContext, @UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher, ): CoroutineContext { - return uiBgCoroutineDispatcher + tracingCoroutineContext + return uiBgCoroutineDispatcher } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt index 82f41a7fd1548897ba2a3813aa9dc1cffd9c47dc..4d9aaa6dc6b034cbd3f8e0d88b9b0fc0bc68e202 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.util.settings +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.content.ContentResolver import android.database.ContentObserver @@ -93,7 +94,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) } @@ -110,7 +111,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) registered.run() } @@ -143,7 +144,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch { registerContentObserverSync(uri, settingsObserver) } @@ -160,7 +161,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch { registerContentObserverSync(uri, settingsObserver) registered.run() } @@ -205,7 +206,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } @@ -223,7 +224,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-F")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) registered.run() } @@ -274,7 +275,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } @@ -292,7 +293,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) registered.run() } @@ -329,7 +330,7 @@ interface SettingsProxy { */ @AnyThread fun unregisterContentObserverAsync(settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) } + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-I")).launch { unregisterContentObserver(settingsObserver) } /** * Look up a name in the database. diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt index 8e3b813a2a8262a5274e949f94f2d424ef0f9c6f..c820c07b61b110c61512ec7209c44000f6db11e4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.util.settings +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.annotation.WorkerThread import android.content.ContentResolver @@ -78,7 +79,7 @@ interface UserSettingsProxy : SettingsProxy { } override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch { registerContentObserverForUserSync(uri, settingsObserver, userId) } @@ -112,7 +113,7 @@ interface UserSettingsProxy : SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -157,7 +158,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -198,7 +199,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } @@ -215,7 +216,7 @@ interface UserSettingsProxy : SettingsProxy { userHandle: Int, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) registered.run() } @@ -274,7 +275,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) { - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, @@ -338,7 +339,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-G")).launch { registerContentObserverForUserSync( uri, notifyForDescendants, diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index db4f9ef13bd6f5ae412e85b8d4394b4b69c8451d..7166428d863f618e00daeb51c61c399f02aa0d23 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -35,7 +35,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix; -import static com.android.systemui.Flags.hapticVolumeSlider; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -928,10 +927,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } private void addSliderHapticsToRow(VolumeRow row) { - if (hapticVolumeSlider()) { - row.createPlugin(mVibratorHelper, mSystemClock); - HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); - } + row.createPlugin(mVibratorHelper, mSystemClock); + HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); } @VisibleForTesting void addSliderHapticsToRows() { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 7385b828695b1395c38e8a72f7b578aedf8540a2..e76401528ff63e58154ee408010038abeb227999 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -22,6 +22,7 @@ import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED; import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; @@ -449,7 +450,8 @@ public class BubblesManager { @Override public void onEntryRemoved(NotificationEntry entry, @NotifCollection.CancellationReason int reason) { - if (reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL) { + if (reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL + || reason == REASON_PACKAGE_BANNED) { BubblesManager.this.onEntryRemoved(entry); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 52fde7ed72c9b47cb8da6522400fc20c3ea23bd6..e609d5f936260639c656c1c28d5805e215de461a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -57,6 +57,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -494,7 +495,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testIgnoresSimStateCallback_rebroadcast() { - Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + Intent intent = defaultSimStateChangedIntent(); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent); mTestableLooper.processAllMessages(); @@ -515,7 +516,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testTelephonyCapable_SimState_Absent() { - Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + + Intent intent = defaultSimStateChangedIntent(); intent.putExtra(Intent.EXTRA_SIM_STATE, Intent.SIM_STATE_ABSENT); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), @@ -526,7 +528,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testTelephonyCapable_SimState_CardIOError() { - Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + Intent intent = defaultSimStateChangedIntent(); intent.putExtra(Intent.EXTRA_SIM_STATE, Intent.SIM_STATE_CARD_IO_ERROR); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), @@ -593,7 +595,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ServiceState state = new ServiceState(); state.setState(ServiceState.STATE_OUT_OF_SERVICE); state.fillInNotifierBundle(data); - Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + Intent intent = defaultSimStateChangedIntent(); intent.putExtra(Intent.EXTRA_SIM_STATE , Intent.SIM_STATE_NOT_READY); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext() @@ -608,7 +610,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ServiceState state = new ServiceState(); state.setState(ServiceState.STATE_OUT_OF_SERVICE); state.fillInNotifierBundle(data); - Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + Intent intent = defaultSimStateChangedIntent(); intent.putExtra(Intent.EXTRA_SIM_STATE , Intent.SIM_STATE_READY); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext() @@ -649,7 +651,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ServiceState state = new ServiceState(); state.setState(ServiceState.STATE_IN_SERVICE); state.fillInNotifierBundle(data); - Intent intentSimState = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + Intent intentSimState = defaultSimStateChangedIntent(); intentSimState.putExtra(Intent.EXTRA_SIM_STATE , Intent.SIM_STATE_LOADED); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext() @@ -1315,6 +1317,33 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isFalse(); } + @Test + public void testKeyguardMonitorStartsWhileUserIsSwitching() { + int userId = UserHandle.myUserId(); + when(mUserTracker.getUserId()).thenReturn(userId); + + /* First test the default behavior: handleUserSwitching() is not invoked */ + when(mUserTracker.isUserSwitching()).thenReturn(false); + boolean invokeStartable = true; + mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext, invokeStartable); + mKeyguardUpdateMonitor.registerCallback(mTestCallback); + mTestableLooper.processAllMessages(); + + verify(mTestCallback, never()).onUserSwitching(userId); + + reset(mTestCallback); + + /* Next test user switching is already in progress when started */ + when(mUserTracker.isUserSwitching()).thenReturn(true); + invokeStartable = false; + mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext, invokeStartable); + mKeyguardUpdateMonitor.registerCallback(mTestCallback); + mKeyguardUpdateMonitor.start(); + mTestableLooper.processAllMessages(); + + verify(mTestCallback).onUserSwitching(userId); + } + @Test public void testSecondaryLockscreenRequirement() { when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(UserHandle.myUserId()); @@ -2256,6 +2285,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { Assert.assertFalse(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked()); } + private Intent defaultSimStateChangedIntent() { + Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0); + return intent; + } + private void verifyFingerprintAuthenticateNeverCalled() { verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(), @@ -2441,6 +2476,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { AtomicInteger mCachedSimState = new AtomicInteger(-1); protected TestableKeyguardUpdateMonitor(Context context) { + this(context, true); + } + + protected TestableKeyguardUpdateMonitor(Context context, boolean invokeStart) { super(context, mUserTracker, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(), mBroadcastDispatcher, mDumpManager, @@ -2461,7 +2500,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { setAlternateBouncerVisibility(false); setPrimaryBouncerVisibility(false); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); - start(); + if (invokeStart) { + start(); + } } public boolean hasSimStateJustChanged() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java index 530ae158cf430f8360b28bd432907434afabea00..5e9f2a2fbe97f6a498c2ce4fe773914e7acc064a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java @@ -104,6 +104,7 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase { mContext = spy(mContext); Display display = mock(Display.class); when(display.getUniqueId()).thenReturn(UNIQUE_DISPLAY_ID_PRIMARY); + when(display.getType()).thenReturn(Display.TYPE_INTERNAL); when(mContext.getDisplayNoVerify()).thenReturn(display); // Override the resources to Display Primary @@ -360,6 +361,7 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase { Display newDisplay = mock(Display.class); when(newDisplay.getUniqueId()).thenReturn(UNIQUE_DISPLAY_ID_SECONDARY); + when(newDisplay.getType()).thenReturn(Display.TYPE_INTERNAL); when(mContext.getDisplayNoVerify()).thenReturn(newDisplay); // Override the resources to Display Secondary mContext.getOrCreateTestableResources() diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 103449b6b0f77d140c54ce717893e60e965cebdd..ee8ce17cecd443f3ff027c35dd7d6b3e7cf973a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.UiModeManager; +import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; import android.platform.test.annotations.EnableFlags; @@ -78,6 +79,12 @@ public class MenuViewTest extends SysuiTestCase { mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); + // Programmatically update the resource's configuration to night mode to reduce flakiness + Configuration nightConfig = new Configuration(mContext.getResources().getConfiguration()); + nightConfig.uiMode = Configuration.UI_MODE_NIGHT_YES; + mContext.getResources().updateConfiguration(nightConfig, + mContext.getResources().getDisplayMetrics(), null); + mSpyContext = spy(mContext); doNothing().when(mSpyContext).startActivity(any()); @@ -101,6 +108,8 @@ public class MenuViewTest extends SysuiTestCase { @Test public void insetsOnDarkTheme_menuOnLeft_matchInsets() { + // In dark theme, the inset is not 0 to avoid weird spacing issue between the menu and + // the edge of the screen. mMenuView.onConfigurationChanged(/* newConfig= */ null); final InstantInsetLayerDrawable insetLayerDrawable = (InstantInsetLayerDrawable) mMenuView.getBackground(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt index 9aaf2958031a0c5b6aaa7f8b3bd2175a13696fb2..a940bc9b3e209ad05af105a645e7565f6fa9af84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt @@ -8,7 +8,8 @@ import android.content.pm.ApplicationInfo import android.graphics.Point import android.graphics.Rect import android.os.Looper -import android.platform.test.flag.junit.SetFlagsRule +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationAdapter @@ -63,7 +64,6 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { private lateinit var activityTransitionAnimator: ActivityTransitionAnimator @get:Rule val rule = MockitoJUnit.rule() - @get:Rule val setFlagsRule = SetFlagsRule() @Before fun setup() { @@ -90,7 +90,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { animator: ActivityTransitionAnimator = this.activityTransitionAnimator, controller: ActivityTransitionAnimator.Controller? = this.controller, animate: Boolean = true, - intentStarter: (RemoteAnimationAdapter?) -> Int + intentStarter: (RemoteAnimationAdapter?) -> Int, ) { // We start in a new thread so that we can ensure that the callbacks are called in the main // thread. @@ -98,7 +98,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { animator.startIntentWithAnimation( controller = controller, animate = animate, - intentStarter = intentStarter + intentStarter = intentStarter, ) } .join() @@ -175,9 +175,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { assertFalse(willAnimateCaptor.value) } + @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) @Test fun registersReturnIffCookieIsPresent() { - setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) `when`(callback.isOnKeyguard()).thenReturn(false) startIntentWithAnimation(activityTransitionAnimator, controller) { _ -> @@ -203,10 +203,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { assertTrue(testShellTransitions.remotesForTakeover.isEmpty()) } + @EnableFlags( + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, + ) @Test fun registersLongLivedTransition() { - setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) - activityTransitionAnimator.register( object : DelegateTransitionAnimatorController(controller) { override val transitionCookie = @@ -226,10 +228,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { assertEquals(4, testShellTransitions.remotes.size) } + @EnableFlags( + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, + ) @Test fun registersLongLivedTransitionOverridingPreviousRegistration() { - setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) - val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie") activityTransitionAnimator.register( object : DelegateTransitionAnimatorController(controller) { @@ -251,9 +255,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { } } + @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED) @Test fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() { - setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) val controller = object : DelegateTransitionAnimatorController(controller) { @@ -266,9 +270,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { } } + @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED) @Test fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() { - setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) // No TransitionCookie val controllerWithoutCookie = @@ -310,9 +314,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { } } + @EnableFlags( + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, + ) @Test fun unregistersLongLivedTransition() { - setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) val cookies = arrayOfNulls(3) @@ -411,7 +418,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { SurfaceControl(), Rect(), taskInfo, - false + false, ) } } @@ -430,7 +437,7 @@ private class FakeShellTransitions : ShellTransitions { override fun registerRemoteForTakeover( filter: TransitionFilter, - remoteTransition: RemoteTransition + remoteTransition: RemoteTransition, ) { remotesForTakeover[filter] = remoteTransition } @@ -460,7 +467,7 @@ private class TestTransitionAnimatorController(override var transitionContainer: left = 300, right = 400, topCornerRadius = 10f, - bottomCornerRadius = 20f + bottomCornerRadius = 20f, ) private fun assertOnMainThread() { @@ -480,7 +487,7 @@ private class TestTransitionAnimatorController(override var transitionContainer: override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float + linearProgress: Float, ) { assertOnMainThread() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index bbff5392f59c658adcc86532b114889734481996..6dc4b10a57da005f6b4b619ea5d9edbfdcb85883 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -18,10 +18,6 @@ package com.android.systemui.biometrics import android.graphics.Point import android.hardware.biometrics.BiometricSourceType -import android.hardware.biometrics.ComponentInfoInternal -import android.hardware.biometrics.SensorLocationInternal -import android.hardware.biometrics.SensorProperties -import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.testing.TestableLooper.RunWithLooper import android.util.DisplayMetrics @@ -47,7 +43,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.mockito.any -import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.After import org.junit.Assert.assertFalse @@ -67,6 +62,8 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.MockitoSession import org.mockito.quality.Strictness +import javax.inject.Provider + @ExperimentalCoroutinesApi @SmallTest @@ -82,28 +79,35 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var authController: AuthController @Mock private lateinit var authRippleInteractor: AuthRippleInteractor @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock private lateinit var biometricUnlockController: BiometricUnlockController - @Mock private lateinit var udfpsControllerProvider: Provider - @Mock private lateinit var udfpsController: UdfpsController - @Mock private lateinit var statusBarStateController: StatusBarStateController - @Mock private lateinit var lightRevealScrim: LightRevealScrim - @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal + @Mock + private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock + private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var biometricUnlockController: BiometricUnlockController + @Mock + private lateinit var udfpsControllerProvider: Provider + @Mock + private lateinit var udfpsController: UdfpsController + @Mock + private lateinit var statusBarStateController: StatusBarStateController + @Mock + private lateinit var lightRevealScrim: LightRevealScrim + @Mock + private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal private val facePropertyRepository = FakeFacePropertyRepository() private val displayMetrics = DisplayMetrics() @Captor private lateinit var biometricUnlockListener: - ArgumentCaptor + ArgumentCaptor @Before fun setUp() { mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) MockitoAnnotations.initMocks(this) - staticMockSession = - mockitoSession() + staticMockSession = mockitoSession() .mockStatic(RotationUtils::class.java) .strictness(Strictness.LENIENT) .startMocking() @@ -112,26 +116,25 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp)) `when`(udfpsControllerProvider.get()).thenReturn(udfpsController) - controller = - AuthRippleController( - context, - authController, - configurationController, - keyguardUpdateMonitor, - keyguardStateController, - wakefulnessLifecycle, - commandRegistry, - notificationShadeWindowController, - udfpsControllerProvider, - statusBarStateController, - displayMetrics, - KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), - biometricUnlockController, - lightRevealScrim, - authRippleInteractor, - facePropertyRepository, - rippleView, - ) + controller = AuthRippleController( + context, + authController, + configurationController, + keyguardUpdateMonitor, + keyguardStateController, + wakefulnessLifecycle, + commandRegistry, + notificationShadeWindowController, + udfpsControllerProvider, + statusBarStateController, + displayMetrics, + KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), + biometricUnlockController, + lightRevealScrim, + authRippleInteractor, + facePropertyRepository, + rippleView, + ) controller.init() } @@ -147,18 +150,13 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) - `when`( - keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT) - ) - ) - .thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) // WHEN fingerprint authenticated verify(biometricUnlockController).addListener(biometricUnlockListener.capture()) - biometricUnlockListener.value.onBiometricUnlockedWithKeyguardDismissal( - BiometricSourceType.FINGERPRINT - ) + biometricUnlockListener.value + .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT) // THEN update sensor location and show ripple verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) @@ -171,12 +169,8 @@ class AuthRippleControllerTest : SysuiTestCase() { val fpsLocation = Point(5, 5) `when`(authController.udfpsLocation).thenReturn(fpsLocation) controller.onViewAttached() - `when`( - keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT) - ) - ) - .thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) // WHEN keyguard is NOT showing & fingerprint authenticated `when`(keyguardStateController.isShowing).thenReturn(false) @@ -185,8 +179,7 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */ - ) + false /* isStrongBiometric */) // THEN no ripple verify(rippleView, never()).startUnlockedRipple(any()) @@ -201,19 +194,14 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(keyguardStateController.isShowing).thenReturn(true) // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated - `when`( - keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT) - ) - ) - .thenReturn(false) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT))).thenReturn(false) val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) verify(keyguardUpdateMonitor).registerCallback(captor.capture()) captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */ - ) + false /* isStrongBiometric */) // THEN no ripple verify(rippleView, never()).startUnlockedRipple(any()) @@ -230,8 +218,7 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */ - ) + false /* isStrongBiometric */) verify(rippleView, never()).startUnlockedRipple(any()) } @@ -246,17 +233,18 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */ - ) + false /* isStrongBiometric */) verify(rippleView, never()).startUnlockedRipple(any()) } @Test fun registersAndDeregisters() { controller.onViewAttached() - val captor = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java) + val captor = ArgumentCaptor + .forClass(KeyguardStateController.Callback::class.java) verify(keyguardStateController).addCallback(captor.capture()) - val captor2 = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) + val captor2 = ArgumentCaptor + .forClass(WakefulnessLifecycle.Observer::class.java) verify(wakefulnessLifecycle).addObserver(captor2.capture()) controller.onViewDetached() verify(keyguardStateController).removeCallback(any()) @@ -271,25 +259,17 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) - `when`( - keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FINGERPRINT - ) - ) - .thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)).thenReturn(true) `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) controller.showUnlockRipple(BiometricSourceType.FINGERPRINT) - assertTrue( - "reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway - ) + assertTrue("reveal didn't start on keyguardFadingAway", + controller.startLightRevealScrimOnKeyguardFadingAway) `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) controller.onKeyguardFadingAwayChanged() - assertFalse( - "reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway - ) + assertFalse("reveal triggers multiple times", + controller.startLightRevealScrimOnKeyguardFadingAway) } @Test @@ -302,27 +282,23 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(keyguardStateController.isShowing).thenReturn(true) `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) `when`(authController.isUdfpsFingerDown).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(eq(BiometricSourceType.FACE))) - .thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FACE))).thenReturn(true) controller.showUnlockRipple(BiometricSourceType.FACE) - assertTrue( - "reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway - ) + assertTrue("reveal didn't start on keyguardFadingAway", + controller.startLightRevealScrimOnKeyguardFadingAway) `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) controller.onKeyguardFadingAwayChanged() - assertFalse( - "reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway - ) + assertFalse("reveal triggers multiple times", + controller.startLightRevealScrimOnKeyguardFadingAway) } @Test fun testUpdateRippleColor() { controller.onViewAttached() - val captor = - ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) + val captor = ArgumentCaptor + .forClass(ConfigurationController.ConfigurationListener::class.java) verify(configurationController).addCallback(captor.capture()) reset(rippleView) @@ -356,40 +332,6 @@ class AuthRippleControllerTest : SysuiTestCase() { verify(rippleView).startDwellRipple(false) } - @Test - fun testUltrasonicUdfps_onFingerDown_runningForDeviceEntry_doNotShowDwellRipple() { - // GIVEN UDFPS is ultrasonic - `when`(authController.udfpsProps) - .thenReturn( - listOf( - FingerprintSensorPropertiesInternal( - 0 /* sensorId */, - SensorProperties.STRENGTH_STRONG, - 5 /* maxEnrollmentsPerUser */, - listOf(), - FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC, - false /* halControlsIllumination */, - true /* resetLockoutRequiresHardwareAuthToken */, - listOf(SensorLocationInternal.DEFAULT), - ) - ) - ) - - // GIVEN fingerprint detection is running on keyguard - `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true) - - // GIVEN view is already attached - controller.onViewAttached() - val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) - verify(udfpsController).addCallback(captor.capture()) - - // WHEN finger is down - captor.value.onFingerDown() - - // THEN never show dwell ripple - verify(rippleView, never()).startDwellRipple(false) - } - @Test fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() { // GIVEN fingerprint detection is NOT running on keyguard diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt index 4b61a0d02f1e929fe80dd484094b8a124aabf33d..088bb02512b59cdd7b18a6eecf8341fd467d6732 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory +import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.lifecycle.activateIn import com.android.systemui.motion.createSysUiComposeMotionTestRule import com.android.systemui.testKosmos @@ -55,6 +56,7 @@ class PatternBouncerTest : SysuiTestCase() { kosmos.patternBouncerViewModelFactory.create( isInputEnabled = MutableStateFlow(true).asStateFlow(), onIntentionalUserInput = {}, + bouncerHapticPlayer = kosmos.bouncerHapticPlayer, ) @Before @@ -75,11 +77,11 @@ class PatternBouncerTest : SysuiTestCase() { content = { play -> if (play) PatternBouncerUnderTest() }, ComposeRecordingSpec.until( recordBefore = false, - checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) } + checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) }, ) { feature(MotionTestKeys.dotAppearFadeIn, floatArray) feature(MotionTestKeys.dotAppearMoveUp, floatArray) - } + }, ) assertThat(motion).timeSeriesMatchesGolden() @@ -100,7 +102,7 @@ class PatternBouncerTest : SysuiTestCase() { viewModel.onDragEnd() // Failure animation starts when animateFailure flips to true... viewModel.animateFailure.takeWhile { !it }.collect {} - } + }, ) { // ... and ends when the composable flips it back to false. viewModel.animateFailure.takeWhile { it }.collect {} @@ -111,7 +113,7 @@ class PatternBouncerTest : SysuiTestCase() { content = { PatternBouncerUnderTest() }, ComposeRecordingSpec(failureAnimationMotionControl) { feature(MotionTestKeys.dotScaling, floatArray) - } + }, ) assertThat(motion).timeSeriesMatchesGolden() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java index c65a1176a55b3d73dcd3435f0b391e5cde691726..d72b72c3d21e604c3a69045e780c64ed40b5aa1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -32,6 +32,7 @@ import android.content.ClipData; import android.content.ClipDescription; import android.content.ClipboardManager; import android.os.PersistableBundle; +import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; @@ -101,8 +102,18 @@ public class ClipboardListenerTest extends SysuiTestCase { when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData); when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource); - mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider, - mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger); + mClipboardListener = new ClipboardListener( + getContext(), + mOverlayControllerProvider, + mClipboardToast, + user -> { + if (UserHandle.CURRENT.equals(user)) { + return mClipboardManager; + } + return null; + }, + mKeyguardManager, + mUiEventLogger); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 597ffef20aceeb30afa52589ae7a0bb06453cfc5..b0810a9edf6b6a409869792b16d273c97c9d7c85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -26,6 +26,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR; +import static com.android.systemui.Flags.FLAG_SIM_PIN_BOUNCER_RESET; import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION; import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT; import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE; @@ -62,6 +63,7 @@ import android.content.Context; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -309,6 +311,28 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } } + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testHandleSystemReadyWhileUserIsSwitching() { + int userId = 1099; + when(mUserTracker.getUserId()).thenReturn(userId); + + /* First test the default behavior: handleUserSwitching() is not invoked */ + when(mUserTracker.isUserSwitching()).thenReturn(false); + mViewMediator.mUpdateCallback = mock(KeyguardUpdateMonitorCallback.class); + mViewMediator.onSystemReady(); + TestableLooper.get(this).processAllMessages(); + + verify(mViewMediator.mUpdateCallback, never()).onUserSwitching(userId); + + /* Next test user switching is already in progress when started */ + when(mUserTracker.isUserSwitching()).thenReturn(true); + mViewMediator.onSystemReady(); + TestableLooper.get(this).processAllMessages(); + + verify(mViewMediator.mUpdateCallback).onUserSwitching(userId); + } + @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() { @@ -586,6 +610,35 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null); } + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + @EnableFlags(FLAG_SIM_PIN_BOUNCER_RESET) + public void resetStateLocked_whenSimNotReadyAndWasLockedPrior() { + // When showing and provisioned + mViewMediator.onSystemReady(); + when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true); + mViewMediator.setShowingLocked(true, ""); + + // and a SIM becomes locked and requires a PIN + mViewMediator.mUpdateCallback.onSimStateChanged( + 1 /* subId */, + 0 /* slotId */, + TelephonyManager.SIM_STATE_PIN_REQUIRED); + TestableLooper.get(this).processAllMessages(); + + reset(mStatusBarKeyguardViewManager); + + // but then disabled by a NOT_READY + mViewMediator.mUpdateCallback.onSimStateChanged( + 1 /* subId */, + 0 /* slotId */, + TelephonyManager.SIM_STATE_NOT_READY); + TestableLooper.get(this).processAllMessages(); + + // A call to reset the keyguard and bouncer was invoked + verify(mStatusBarKeyguardViewManager).reset(true); + } + @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway_initiallyNotShowing() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt index 823a23dec4ead2080008ee0ae48bdb124a1ad33c..d32d8cc4bd51a3f89939b02155e9b31d33807bf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt @@ -82,6 +82,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After @@ -200,7 +201,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Settings.Secure.getInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - 1 + 1, ) private lateinit var staticMockSession: MockitoSession @@ -221,9 +222,8 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Settings.Secure.putInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - 1 + 1, ) - mediaDataManager = LegacyMediaDataManagerImpl( context = context, @@ -334,7 +334,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Settings.Secure.putInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - originalSmartspaceSetting + originalSmartspaceSetting, ) } @@ -365,7 +365,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) runCurrent() @@ -378,7 +378,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true) @@ -404,7 +404,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa metadataBuilder .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT, ) .build() ) @@ -420,7 +420,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.isExplicit).isTrue() } @@ -438,7 +438,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.isExplicit).isFalse() } @@ -451,7 +451,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId), - eq(MediaData.PLAYBACK_LOCAL) + eq(MediaData.PLAYBACK_LOCAL), ) } @@ -467,7 +467,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.active).isTrue() } @@ -483,7 +483,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa anyInt(), eq(SYSTEM_PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId), - eq(MediaData.PLAYBACK_CAST_REMOTE) + eq(MediaData.PLAYBACK_CAST_REMOTE), ) } @@ -511,7 +511,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName) @@ -597,7 +597,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) @@ -627,7 +627,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) @@ -668,7 +668,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE) } @@ -683,7 +683,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa mediaDataManager.onMediaDataLoaded( KEY, null, - data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}) + data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}), ) // WHEN the notification is removed @@ -698,7 +698,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId)) @@ -716,7 +716,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa mediaDataManager.onMediaDataLoaded( KEY, null, - data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}) + data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}), ) // WHEN the notification is removed @@ -731,7 +731,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId)) @@ -756,7 +756,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.isPlaying).isFalse() @@ -777,7 +777,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() @@ -789,7 +789,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val data2 = mediaDataCaptor.value assertThat(data2.resumption).isFalse() @@ -807,7 +807,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false)) @@ -821,7 +821,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false)) @@ -842,7 +842,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId), - eq(MediaData.PLAYBACK_CAST_LOCAL) + eq(MediaData.PLAYBACK_CAST_LOCAL), ) // WHEN the notification is removed @@ -878,7 +878,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() } @@ -932,7 +932,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.isPlaying).isFalse() @@ -982,7 +982,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa // WHEN resumption controls are added with explicit indicator bundle.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT, ) val desc = MediaDescription.Builder().run { @@ -1015,7 +1015,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED, ) putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress) } @@ -1041,7 +1041,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED + MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED, ) } val desc = @@ -1066,7 +1066,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED + MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED, ) } val desc = @@ -1118,7 +1118,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) // Resumption controls are not added. @@ -1130,7 +1130,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -1151,7 +1151,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) // Resumption controls are not added. @@ -1163,7 +1163,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -1230,7 +1230,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -1256,7 +1256,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1280,7 +1280,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1312,7 +1312,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1359,7 +1359,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1393,7 +1393,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1424,7 +1424,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false)) } @@ -1456,7 +1456,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1466,7 +1466,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Settings.Secure.putInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - 0 + 0, ) tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") @@ -1488,7 +1488,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa Settings.Secure.putInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - 0 + 0, ) tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") @@ -1526,7 +1526,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime) } @@ -1553,7 +1553,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime) @@ -1573,7 +1573,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa mediaDataManager.onMediaDataLoaded( KEY, null, - data.copy(resumeAction = Runnable {}, active = false) + data.copy(resumeAction = Runnable {}, active = false), ) // WHEN the notification is removed @@ -1589,7 +1589,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime) @@ -1629,7 +1629,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.actionsToShowInCompact.size) .isEqualTo(LegacyMediaDataManagerImpl.MAX_COMPACT_ACTIONS) @@ -1664,7 +1664,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.actions.size) .isEqualTo(LegacyMediaDataManagerImpl.MAX_NOTIFICATION_ACTIONS) @@ -1695,7 +1695,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.semanticActions).isNull() @@ -1868,7 +1868,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa anyInt(), eq(PACKAGE_NAME), eq(instanceId), - eq(MediaData.PLAYBACK_CAST_LOCAL) + eq(MediaData.PLAYBACK_CAST_LOCAL), ) // update to remote cast @@ -1879,7 +1879,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa anyInt(), eq(SYSTEM_PACKAGE_NAME), eq(instanceId), - eq(MediaData.PLAYBACK_CAST_REMOTE) + eq(MediaData.PLAYBACK_CAST_REMOTE), ) } @@ -1900,7 +1900,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isTrue() } @@ -1948,7 +1948,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNotNull() @@ -1977,7 +1977,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) runCurrent() backgroundExecutor.runAllReady() @@ -1992,7 +1992,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNotNull() @@ -2017,7 +2017,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNull() @@ -2074,7 +2074,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2082,7 +2082,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2141,7 +2141,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2149,7 +2149,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2193,7 +2193,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2201,7 +2201,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2245,7 +2245,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2253,7 +2253,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2279,7 +2279,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -2321,7 +2321,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2329,7 +2329,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2355,7 +2355,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa any(), any(), anyInt(), - anyInt() + anyInt(), ) ) .thenReturn(1) @@ -2385,7 +2385,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa any(), any(), anyInt(), - anyInt() + anyInt(), ) ) .thenThrow(SecurityException("Test no permission")) @@ -2421,7 +2421,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(kosmos.mediaLogger).logDuplicateMediaNotification(eq(KEY)) } @@ -2440,7 +2440,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(kosmos.mediaLogger, never()).logDuplicateMediaNotification(eq(KEY)) } @@ -2448,6 +2448,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) { runCurrent() if (Flags.mediaLoadMetadataViaMediaDataLoader()) { + advanceUntilIdle() // It doesn't make much sense to count tasks when we use coroutines in loader // so this check is skipped in that scenario. backgroundExecutor.runAllReady() @@ -2478,7 +2479,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -2493,7 +2494,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa /** Helper function to add a resumption control and capture the resulting MediaData */ private fun addResumeControlAndLoad( desc: MediaDescription, - packageName: String = PACKAGE_NAME + packageName: String = PACKAGE_NAME, ) { mediaDataManager.addResumptionControls( USER_ID, @@ -2502,7 +2503,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa session.sessionToken, APP_NAME, pendingIntent, - packageName + packageName, ) testScope.assertRunAllReady(foreground = 1, background = 1) @@ -2514,7 +2515,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt index 4cf7de3d7a6318d40270ab22cbebd02e41a06fd3..90af93292de164c47aaa331ca15e3521fd0fec16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt @@ -90,6 +90,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import org.junit.After import org.junit.Before @@ -212,7 +213,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { Settings.Secure.getInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - 1 + 1, ) private lateinit var staticMockSession: MockitoSession @@ -233,7 +234,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { Settings.Secure.putInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - 1 + 1, ) mediaDataProcessor = MediaDataProcessor( @@ -274,7 +275,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { mediaDataCombineLatest = mediaDataCombineLatest, mediaDataFilter = mediaDataFilter, mediaFilterRepository = mediaFilterRepository, - mediaFlags = kosmos.mediaFlags + mediaFlags = kosmos.mediaFlags, ) mediaCarouselInteractor.start() verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor) @@ -356,7 +357,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { Settings.Secure.putInt( context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, - originalSmartspaceSetting + originalSmartspaceSetting, ) } @@ -386,7 +387,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) testScope.runCurrent() @@ -399,7 +400,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) mediaDataProcessor.setInactive(PACKAGE_NAME, timedOut = true) @@ -425,7 +426,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { metadataBuilder .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT, ) .build() ) @@ -440,7 +441,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.isExplicit).isTrue() } @@ -457,7 +458,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.isExplicit).isFalse() } @@ -470,7 +471,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId), - eq(MediaData.PLAYBACK_LOCAL) + eq(MediaData.PLAYBACK_LOCAL), ) } @@ -485,7 +486,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.active).isTrue() } @@ -501,7 +502,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { anyInt(), eq(SYSTEM_PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId), - eq(MediaData.PLAYBACK_CAST_REMOTE) + eq(MediaData.PLAYBACK_CAST_REMOTE), ) } @@ -529,7 +530,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName) @@ -615,7 +616,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) @@ -645,7 +646,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) @@ -686,7 +687,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE) } @@ -701,7 +702,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { mediaDataProcessor.onMediaDataLoaded( KEY, null, - data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}) + data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}), ) // WHEN the notification is removed @@ -716,7 +717,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId)) @@ -734,7 +735,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { mediaDataProcessor.onMediaDataLoaded( KEY, null, - data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}) + data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}), ) // WHEN the notification is removed @@ -749,7 +750,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId)) @@ -774,7 +775,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.isPlaying).isFalse() @@ -795,7 +796,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() @@ -807,7 +808,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) val data2 = mediaDataCaptor.value assertThat(data2.resumption).isFalse() @@ -825,7 +826,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) @@ -839,7 +840,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false)) @@ -860,7 +861,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId), - eq(MediaData.PLAYBACK_CAST_LOCAL) + eq(MediaData.PLAYBACK_CAST_LOCAL), ) // WHEN the notification is removed @@ -896,7 +897,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() } @@ -950,7 +951,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.isPlaying).isFalse() @@ -1000,7 +1001,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { // WHEN resumption controls are added with explicit indicator bundle.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT, ) val desc = MediaDescription.Builder().run { @@ -1033,7 +1034,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED, ) putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress) } @@ -1059,7 +1060,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED + MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED, ) } val desc = @@ -1084,7 +1085,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED + MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED, ) } val desc = @@ -1136,7 +1137,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) // Resumption controls are not added. @@ -1148,7 +1149,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -1169,7 +1170,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) // Resumption controls are not added. @@ -1181,7 +1182,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -1248,7 +1249,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -1274,7 +1275,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1298,7 +1299,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1330,7 +1331,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1377,7 +1378,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1411,7 +1412,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1442,7 +1443,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false)) } @@ -1474,7 +1475,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { expiryTimeMs = SMARTSPACE_EXPIRY_TIME, ) ), - eq(false) + eq(false), ) } @@ -1536,7 +1537,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime) } @@ -1563,7 +1564,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime) @@ -1583,7 +1584,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { mediaDataProcessor.onMediaDataLoaded( KEY, null, - data.copy(resumeAction = Runnable {}, active = false) + data.copy(resumeAction = Runnable {}, active = false), ) // WHEN the notification is removed @@ -1599,7 +1600,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime) @@ -1639,7 +1640,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.actionsToShowInCompact.size) .isEqualTo(MediaDataProcessor.MAX_COMPACT_ACTIONS) @@ -1674,7 +1675,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.actions.size) .isEqualTo(MediaDataProcessor.MAX_NOTIFICATION_ACTIONS) @@ -1705,7 +1706,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value!!.semanticActions).isNull() @@ -1944,7 +1945,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { anyInt(), eq(PACKAGE_NAME), eq(instanceId), - eq(MediaData.PLAYBACK_CAST_LOCAL) + eq(MediaData.PLAYBACK_CAST_LOCAL), ) // update to remote cast @@ -1955,7 +1956,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { anyInt(), eq(SYSTEM_PACKAGE_NAME), eq(instanceId), - eq(MediaData.PLAYBACK_CAST_REMOTE) + eq(MediaData.PLAYBACK_CAST_REMOTE), ) } @@ -1976,7 +1977,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isTrue() } @@ -2024,7 +2025,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNotNull() @@ -2052,7 +2053,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { session.sessionToken, APP_NAME, pendingIntent, - PACKAGE_NAME + PACKAGE_NAME, ) testScope.runCurrent() backgroundExecutor.runAllReady() @@ -2067,7 +2068,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNotNull() @@ -2092,7 +2093,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNull() @@ -2149,7 +2150,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2157,7 +2158,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2216,7 +2217,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2224,7 +2225,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2268,7 +2269,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2276,7 +2277,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2320,7 +2321,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2328,7 +2329,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2354,7 +2355,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -2396,7 +2397,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.active).isFalse() @@ -2404,7 +2405,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { .logActiveConvertedToResume( anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId) + eq(mediaDataCaptor.value.instanceId), ) } @@ -2430,7 +2431,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { any(), any(), anyInt(), - anyInt() + anyInt(), ) ) .thenReturn(1) @@ -2460,7 +2461,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { any(), any(), anyInt(), - anyInt() + anyInt(), ) ) .thenThrow(SecurityException("Test no permission")) @@ -2501,7 +2502,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(kosmos.mediaLogger).logDuplicateMediaNotification(eq(KEY)) } @@ -2525,7 +2526,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) verify(kosmos.mediaLogger, never()).logDuplicateMediaNotification(eq(KEY)) } @@ -2533,6 +2534,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) { runCurrent() if (Flags.mediaLoadMetadataViaMediaDataLoader()) { + advanceUntilIdle() // It doesn't make much sense to count tasks when we use coroutines in loader // so this check is skipped in that scenario. backgroundExecutor.runAllReady() @@ -2563,7 +2565,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } @@ -2578,7 +2580,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { /** Helper function to add a resumption control and capture the resulting MediaData */ private fun addResumeControlAndLoad( desc: MediaDescription, - packageName: String = PACKAGE_NAME + packageName: String = PACKAGE_NAME, ) { mediaDataProcessor.addResumptionControls( USER_ID, @@ -2587,7 +2589,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { session.sessionToken, APP_NAME, pendingIntent, - packageName + packageName, ) testScope.assertRunAllReady(foreground = 1, background = 1) @@ -2598,7 +2600,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() { capture(mediaDataCaptor), eq(true), eq(0), - eq(false) + eq(false), ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt index 2370bca52951e753b645b8a11373f09ad58013ed..0508c2cf0426e0b742694cdc713caafad533e1c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel @@ -34,6 +35,7 @@ import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope @@ -80,6 +82,8 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.lastValue @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -118,6 +122,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var mediaHierarchyManager: MediaHierarchyManager private lateinit var isQsBypassingShade: MutableStateFlow private lateinit var shadeExpansion: MutableStateFlow + private lateinit var anyShadeExpanded: MutableStateFlow private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() private val settings = FakeSettings() @@ -137,8 +142,10 @@ class MediaHierarchyManagerTest : SysuiTestCase() { whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) isQsBypassingShade = MutableStateFlow(false) shadeExpansion = MutableStateFlow(0f) + anyShadeExpanded = MutableStateFlow(false) whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade) whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion) + whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(anyShadeExpanded) mediaHierarchyManager = MediaHierarchyManager( context, @@ -573,6 +580,72 @@ class MediaHierarchyManagerTest : SysuiTestCase() { ) } + @Test + fun testCommunalLocationVisibilityWithShadeShowing() = + testScope.runTest { + whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) + runCurrent() + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), + nullable(), + eq(false), + anyLong(), + anyLong() + ) + + val captor = ArgumentCaptor.forClass(Boolean::class.java) + verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() + + assertThat(captor.lastValue).isTrue() + + clearInvocations(mediaCarouselScrollHandler) + anyShadeExpanded.value = true + runCurrent() + verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() + + assertThat(captor.lastValue).isFalse() + } + + @Test + fun testCommunalLocationVisibilityWithPrimaryBouncerShowing() = + testScope.runTest { + whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) + runCurrent() + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), + nullable(), + eq(false), + anyLong(), + anyLong() + ) + + val captor = ArgumentCaptor.forClass(Boolean::class.java) + verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() + + assertThat(captor.lastValue).isTrue() + + clearInvocations(mediaCarouselScrollHandler) + kosmos.keyguardBouncerRepository.setPrimaryShow(true) + runCurrent() + verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() + + assertThat(captor.lastValue).isFalse() + } + @Test fun testCommunalLocation_showsOverLockscreen() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 411ff91ebc2f5f84abbd81c790d27e7d2b0ade71..8731853e49393baf5ab71fb1dfb8cfdef54eb1f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -77,7 +77,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private static final int TEST_CURRENT_VOLUME = 10; // Mock - private MediaOutputController mMediaOutputController = mock(MediaOutputController.class); + private MediaSwitchingController mMediaSwitchingController = + mock(MediaSwitchingController.class); private MediaOutputDialog mMediaOutputDialog = mock(MediaOutputDialog.class); private MediaDevice mMediaDevice1 = mock(MediaDevice.class); private MediaDevice mMediaDevice2 = mock(MediaDevice.class); @@ -95,13 +96,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Before public void setUp() { - when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems); - when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false); - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); - when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat); - when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat); - when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1); - when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true); + when(mMediaSwitchingController.getMediaItemList()).thenReturn(mMediaItems); + when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(false); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat); + when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat); + when(mMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1); + when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true); when(mIconCompat.toIcon(mContext)).thenReturn(mIcon); when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1); when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1); @@ -116,7 +117,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1)); mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2)); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -142,7 +143,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindPairNew_verifyView() { - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -161,11 +162,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroup_withSessionName_verifyView() { - when(mMediaOutputController.getSelectedMediaDevice()).thenReturn( - mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect( - Collectors.toList())); - when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + when(mMediaSwitchingController.getSelectedMediaDevice()) + .thenReturn( + mMediaItems.stream() + .map((item) -> item.getMediaDevice().get()) + .collect(Collectors.toList())); + when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -181,11 +184,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroup_noSessionName_verifyView() { - when(mMediaOutputController.getSelectedMediaDevice()).thenReturn( - mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect( - Collectors.toList())); - when(mMediaOutputController.getSessionName()).thenReturn(null); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + when(mMediaSwitchingController.getSelectedMediaDevice()) + .thenReturn( + mMediaItems.stream() + .map((item) -> item.getMediaDevice().get()) + .collect(Collectors.toList())); + when(mMediaSwitchingController.getSessionName()).thenReturn(null); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -214,7 +219,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() { - when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); + when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -230,9 +235,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedRemoteDevice_verifyView() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice2)); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice2)); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -249,9 +254,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedRemoteDevice_verifyContentDescriptionNotNull() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice2)); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice2)); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -263,9 +268,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of()); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of()); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -283,9 +287,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedRemoteDeviceWithOnGoingSession_verifyView() { when(mMediaDevice1.hasOngoingSession()).thenReturn(true); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of()); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of()); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -305,9 +308,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { public void onBindViewHolder_bindConnectedRemoteDeviceWithHostOnGoingSession_verifyView() { when(mMediaDevice1.hasOngoingSession()).thenReturn(true); when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of()); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of()); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -326,8 +328,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() { - when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); @@ -340,8 +342,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_isMutingExpectedDevice_verifyView() { when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); - when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -378,14 +380,14 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar); assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); - verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1); + verify(mMediaSwitchingController).logInteractionAdjustVolume(mMediaDevice1); } @Test public void onBindViewHolder_bindSelectableDevice_verifyView() { List selectableDevices = new ArrayList<>(); selectableDevices.add(mMediaDevice2); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); @@ -440,7 +442,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void subStatusSupported_onBindViewHolder_bindHostDeviceWithOngoingSession_verifyView() { - when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); + when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true); when(mMediaDevice1.hasSubtext()).thenReturn(true); when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM); @@ -540,7 +542,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -556,7 +558,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroupingDevice_verifyView() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false); when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_GROUPING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -572,7 +574,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice2.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -586,7 +588,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() { - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -595,16 +597,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout); + verify(mMediaSwitchingController).launchBluetoothPairing(mViewHolder.mContainerLayout); } @Test public void onItemClick_clickDevice_verifyConnectDevice() { - when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false); + when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false); assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -613,16 +615,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).connectDevice(mMediaDevice2); + verify(mMediaSwitchingController).connectDevice(mMediaDevice2); } @Test public void onItemClick_clickDeviceWithSessionOngoing_verifyShowsDialog() { - when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true); + when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true); assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -633,66 +635,68 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1); spyMediaDeviceViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController, never()).connectDevice(mMediaDevice2); + verify(mMediaSwitchingController, never()).connectDevice(mMediaDevice2); verify(spyMediaDeviceViewHolder).showCustomEndSessionDialog(mMediaDevice2); } @Test public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() { - when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); - when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false); when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(false); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).cancelMuteAwaitConnection(); + verify(mMediaSwitchingController).cancelMuteAwaitConnection(); } @Test public void onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() { List selectableDevices = new ArrayList<>(); selectableDevices.add(mMediaDevice2); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); mViewHolder.mEndTouchArea.performClick(); - verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2); + verify(mMediaSwitchingController).addDeviceToPlayMedia(mMediaDevice2); } @Test public void onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() { - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice2)); - when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn( - ImmutableList.of(mMediaDevice1)); - when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice2)); + when(mMediaSwitchingController.getDeselectableMediaDevice()) + .thenReturn(ImmutableList.of(mMediaDevice1)); + when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); mViewHolder.mEndTouchArea.performClick(); - verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1); + verify(mMediaSwitchingController).removeDeviceFromPlayMedia(mMediaDevice1); } @Test public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() { - when(mMediaOutputController.getSelectedMediaDevice()).thenReturn( - mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect( - Collectors.toList())); - mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController); + when(mMediaSwitchingController.getSelectedMediaDevice()) + .thenReturn( + mMediaItems.stream() + .map((item) -> item.getMediaDevice().get()) + .collect(Collectors.toList())); + mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); List selectableDevices = new ArrayList<>(); selectableDevices.add(mMediaDevice1); - when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); - when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true); + when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices); + when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(true); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); mViewHolder.mContainerLayout.performClick(); @@ -702,11 +706,11 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() { - when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false); + when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse(); - when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); + when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue(); @@ -719,7 +723,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.updateColorScheme(wallpaperColors, true); - verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true); + verify(mMediaSwitchingController).setCurrentColorScheme(wallpaperColors, true); } @Test @@ -727,7 +731,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.updateItems(); List updatedList = new ArrayList<>(); updatedList.add(MediaItem.createPairNewDeviceMediaItem()); - when(mMediaOutputController.getMediaItemList()).thenReturn(updatedList); + when(mMediaSwitchingController.getMediaItemList()).thenReturn(updatedList); assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size()); mMediaOutputAdapter.updateItems(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index c8cc6b5fdf9347432804f17801ab170721427342..47371dfd88954ee3748f08ec4658b468efbb3892 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -104,7 +104,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private List mMediaControllers = new ArrayList<>(); private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; private int mHeaderIconRes; private IconCompat mIconCompat; private CharSequence mHeaderTitle; @@ -132,8 +132,8 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( mKosmos); - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mContext, TEST_PACKAGE, mContext.getUser(), @@ -153,12 +153,13 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { // Using a fake package will cause routing operations to fail, so we intercept // scanning-related operations. - mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class); - doNothing().when(mMediaOutputController.mLocalMediaManager).startScan(); - doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan(); + mMediaSwitchingController.mLocalMediaManager = mock(LocalMediaManager.class); + doNothing().when(mMediaSwitchingController.mLocalMediaManager).startScan(); + doNothing().when(mMediaSwitchingController.mLocalMediaManager).stopScan(); - mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, - mMediaOutputController); + mMediaOutputBaseDialogImpl = + new MediaOutputBaseDialogImpl( + mContext, mBroadcastSender, mMediaSwitchingController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); } @@ -176,7 +177,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { public void refresh_withIconCompat_iconIsVisible() { mIconCompat = IconCompat.createWithBitmap( Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)); - when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaOutputController); + when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaSwitchingController); mMediaOutputBaseDialogImpl.refresh(); final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById( @@ -263,7 +264,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { when(mMediaOutputBaseAdapter.isDragging()).thenReturn(true); mMediaOutputBaseDialogImpl.refresh(); - assertThat(mMediaOutputController.isRefreshing()).isFalse(); + assertThat(mMediaSwitchingController.isRefreshing()).isFalse(); } @Test @@ -335,12 +336,14 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog { - MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController) { + MediaOutputBaseDialogImpl( + Context context, + BroadcastSender broadcastSender, + MediaSwitchingController mediaSwitchingController) { super( context, broadcastSender, - mediaOutputController, /* includePlaybackAndAppMetadata */ + mediaSwitchingController, /* includePlaybackAndAppMetadata */ true); mAdapter = mMediaOutputBaseAdapter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 189a56145d27208269f0e67f8251be28bb2940e6..f0902e35b837a8fd8e3dd8cb0284f4a815f1e822 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -119,7 +119,7 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { private UserTracker mUserTracker = mock(UserTracker.class); private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; @Before public void setUp() { @@ -133,8 +133,8 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( mKosmos); - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mContext, TEST_PACKAGE, mContext.getUser(), @@ -151,9 +151,10 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { mFlags, volumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false, - mBroadcastSender, mMediaOutputController); + mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; + mMediaOutputBroadcastDialog = + new MediaOutputBroadcastDialog( + mContext, false, mBroadcastSender, mMediaSwitchingController); mMediaOutputBroadcastDialog.show(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 90c2930f8e499eef7ee23c080f72be5121607123..d3ecb3d8c94415b6595498b670536f30480408df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -119,7 +119,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private List mMediaControllers = new ArrayList<>(); private MediaOutputDialog mMediaOutputDialog; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; private final List mFeatures = new ArrayList<>(); @Override @@ -146,8 +146,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( mKosmos); - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mContext, TEST_PACKAGE, mContext.getUser(), @@ -164,8 +164,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { mFlags, volumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputDialog = makeTestDialog(mMediaOutputController); + mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; + mMediaOutputDialog = makeTestDialog(mMediaSwitchingController); mMediaOutputDialog.show(); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); @@ -388,12 +388,15 @@ public class MediaOutputDialogTest extends SysuiTestCase { public void getStopButtonText_notSupportsBroadcast_returnsDefaultText() { String stopText = mContext.getText( R.string.media_output_dialog_button_stop_casting).toString(); - MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); - when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); - - withTestDialog(mockMediaOutputController, testDialog -> { - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); - }); + MediaSwitchingController mockMediaSwitchingController = + mock(MediaSwitchingController.class); + when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false); + + withTestDialog( + mockMediaSwitchingController, + testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -401,28 +404,35 @@ public class MediaOutputDialogTest extends SysuiTestCase { public void getStopButtonText_supportsBroadcast_returnsBroadcastText() { String stopText = mContext.getText(R.string.media_output_broadcast).toString(); MediaDevice mMediaDevice = mock(MediaDevice.class); - MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); - when(mockMediaOutputController.isBroadcastSupported()).thenReturn(true); - when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice); - when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true); - when(mockMediaOutputController.isPlaying()).thenReturn(true); - when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false); - withTestDialog(mockMediaOutputController, testDialog -> { - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); - }); + MediaSwitchingController mockMediaSwitchingController = + mock(MediaSwitchingController.class); + when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(true); + when(mockMediaSwitchingController.getCurrentConnectedMediaDevice()) + .thenReturn(mMediaDevice); + when(mockMediaSwitchingController.isBluetoothLeDevice(any())).thenReturn(true); + when(mockMediaSwitchingController.isPlaying()).thenReturn(true); + when(mockMediaSwitchingController.isBluetoothLeBroadcastEnabled()).thenReturn(false); + withTestDialog( + mockMediaSwitchingController, + testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test public void onStopButtonClick_notPlaying_releaseSession() { - MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); - when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); - when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null); - when(mockMediaOutputController.isPlaying()).thenReturn(false); - withTestDialog(mockMediaOutputController, testDialog -> { - testDialog.onStopButtonClick(); - }); - - verify(mockMediaOutputController).releaseSession(); + MediaSwitchingController mockMediaSwitchingController = + mock(MediaSwitchingController.class); + when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false); + when(mockMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(null); + when(mockMediaSwitchingController.isPlaying()).thenReturn(false); + withTestDialog( + mockMediaSwitchingController, + testDialog -> { + testDialog.onStopButtonClick(); + }); + + verify(mockMediaSwitchingController).releaseSession(); verify(mDialogTransitionAnimator).disableAllCurrentDialogsExitAnimations(); } @@ -430,14 +440,14 @@ public class MediaOutputDialogTest extends SysuiTestCase { // Check the visibility metric logging by creating a new MediaOutput dialog, // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { - withTestDialog(mMediaOutputController, testDialog -> {}); + withTestDialog(mMediaSwitchingController, testDialog -> {}); verify(mUiEventLogger, times(2)) .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW); } @NonNull - private MediaOutputDialog makeTestDialog(MediaOutputController controller) { + private MediaOutputDialog makeTestDialog(MediaSwitchingController controller) { return new MediaOutputDialog( mContext, false, @@ -448,7 +458,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { true); } - private void withTestDialog(MediaOutputController controller, Consumer c) { + private void withTestDialog( + MediaSwitchingController controller, Consumer c) { MediaOutputDialog testDialog = makeTestDialog(controller); testDialog.show(); c.accept(testDialog); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java similarity index 73% rename from packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java rename to packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index 714fad9d747835f9f5f6b441b48fc3e7cc7e312e..53f0800a7d1394f33911d203754be1299579b8ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -43,6 +43,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.MediaDescription; import android.media.MediaMetadata; @@ -58,6 +59,7 @@ import android.os.Bundle; import android.os.PowerExemptionManager; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; import android.text.TextUtils; @@ -67,8 +69,11 @@ import androidx.core.graphics.drawable.IconCompat; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.media.flags.Flags; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.media.InputMediaDevice; +import com.android.settingslib.media.InputRouteManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; @@ -96,12 +101,13 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class MediaOutputControllerTest extends SysuiTestCase { +public class MediaSwitchingControllerTest extends SysuiTestCase { private static final String TEST_DEVICE_1_ID = "test_device_1_id"; private static final String TEST_DEVICE_2_ID = "test_device_2_id"; private static final String TEST_DEVICE_3_ID = "test_device_3_id"; @@ -111,6 +117,10 @@ public class MediaOutputControllerTest extends SysuiTestCase { private static final String TEST_SONG = "test_song"; private static final String TEST_SESSION_ID = "test_session_id"; private static final String TEST_SESSION_NAME = "test_session_name"; + private static final int MAX_VOLUME = 1; + private static final int CURRENT_VOLUME = 0; + private static final boolean VOLUME_FIXED_TRUE = true; + @Mock private DialogTransitionAnimator mDialogTransitionAnimator; @Mock @@ -126,8 +136,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; - @Mock - private MediaOutputController.Callback mCb; + @Mock private MediaSwitchingController.Callback mCb; @Mock private MediaDevice mMediaDevice1; @Mock @@ -166,7 +175,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { private FeatureFlags mFlags = mock(FeatureFlags.class); private View mDialogLaunchView = mock(View.class); - private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class); + private MediaSwitchingController.Callback mCallback = + mock(MediaSwitchingController.Callback.class); final Notification mNotification = mock(Notification.class); private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor = @@ -175,8 +185,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { private Context mSpyContext; private String mPackageName = null; - private MediaOutputController mMediaOutputController; + private MediaSwitchingController mMediaSwitchingController; private LocalMediaManager mLocalMediaManager; + private InputRouteManager mInputRouteManager; private List mMediaControllers = new ArrayList<>(); private List mMediaDevices = new ArrayList<>(); private List mNearbyDevices = new ArrayList<>(); @@ -203,9 +214,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn( mCachedBluetoothDeviceManager); - - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, mPackageName, mContext.getUser(), @@ -222,9 +232,13 @@ public class MediaOutputControllerTest extends SysuiTestCase { mFlags, mVolumePanelGlobalStateInteractor, mUserTracker); - mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); + mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager); when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); - mMediaOutputController.mLocalMediaManager = mLocalMediaManager; + mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; + mMediaSwitchingController.mInputRouteManager = + new InputRouteManager(mContext, mAudioManager); + mInputRouteManager = spy(mMediaSwitchingController.mInputRouteManager); + mMediaSwitchingController.mInputRouteManager = mInputRouteManager; MediaDescription.Builder builder = new MediaDescription.Builder(); builder.setTitle(TEST_SONG); builder.setSubtitle(TEST_ARTIST); @@ -264,26 +278,26 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void start_verifyLocalMediaManagerInit() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - verify(mLocalMediaManager).registerCallback(mMediaOutputController); + verify(mLocalMediaManager).registerCallback(mMediaSwitchingController); verify(mLocalMediaManager).startScan(); } @Test public void stop_verifyLocalMediaManagerDeinit() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mLocalMediaManager); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); - verify(mLocalMediaManager).unregisterCallback(mMediaOutputController); + verify(mLocalMediaManager).unregisterCallback(mMediaSwitchingController); verify(mLocalMediaManager).stopScan(); } @Test public void start_notificationNotFound_mediaControllerInitFromSession() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mSessionMediaController).registerCallback(any()); } @@ -291,7 +305,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void start_MediaNotificationFound_mediaControllerNotInitFromSession() { when(mNotification.isMediaNotification()).thenReturn(true); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mSessionMediaController, never()).registerCallback(any()); verifyZeroInteractions(mMediaSessionManager); @@ -299,8 +313,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void start_withoutPackageName_verifyMediaControllerInit() { - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mContext.getUser(), @@ -318,32 +332,32 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mSessionMediaController, never()).registerCallback(any()); } @Test public void start_nearbyMediaDevicesManagerNotNull_registersNearbyDevicesCallback() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); verify(mNearbyMediaDevicesManager).registerNearbyDevicesCallback(any()); } @Test public void stop_withPackageName_verifyMediaControllerDeinit() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mSessionMediaController); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); verify(mSessionMediaController).unregisterCallback(any()); } @Test public void stop_withoutPackageName_verifyMediaControllerDeinit() { - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -361,26 +375,26 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); verify(mSessionMediaController, never()).unregisterCallback(any()); } @Test public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mSessionMediaController); - mMediaOutputController.stop(); + mMediaSwitchingController.stop(); verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any()); } @Test public void tryToLaunchMediaApplication_nullIntent_skip() { - mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView); + mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView); verify(mCb, never()).dismissDialog(); } @@ -391,9 +405,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { .thenReturn(mController); Intent intent = new Intent(mPackageName); doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(mPackageName); - mMediaOutputController.start(mCallback); + mMediaSwitchingController.start(mCallback); - mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView); + mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView); verify(mStarter).startActivity(any(Intent.class), anyBoolean(), Mockito.eq(mController)); @@ -403,11 +417,12 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void tryToLaunchInAppRoutingIntent_componentNameNotNull_startActivity() { when(mDialogTransitionAnimator.createActivityTransitionController(any(View.class))) .thenReturn(mController); - mMediaOutputController.start(mCallback); + mMediaSwitchingController.start(mCallback); when(mLocalMediaManager.getLinkedItemComponentName()).thenReturn( new ComponentName(mPackageName, "")); - mMediaOutputController.tryToLaunchInAppRoutingIntent(TEST_DEVICE_1_ID, mDialogLaunchView); + mMediaSwitchingController.tryToLaunchInAppRoutingIntent( + TEST_DEVICE_1_ID, mDialogLaunchView); verify(mStarter).startActivity(any(Intent.class), anyBoolean(), Mockito.eq(mController)); @@ -415,9 +430,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDevicesUpdated_unregistersNearbyDevicesCallback() throws RemoteException { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - mMediaOutputController.onDevicesUpdated(ImmutableList.of()); + mMediaSwitchingController.onDevicesUpdated(ImmutableList.of()); verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any()); } @@ -425,11 +440,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_withNearbyDevices_updatesRangeInformation() throws RemoteException { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDevicesUpdated(mNearbyDevices); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDevicesUpdated(mNearbyDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); verify(mMediaDevice1).setRangeZone(NearbyDevice.RANGE_FAR); verify(mMediaDevice2).setRangeZone(NearbyDevice.RANGE_CLOSE); @@ -438,11 +453,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_withNearbyDevices_rankByRangeInformation() throws RemoteException { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDevicesUpdated(mNearbyDevices); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDevicesUpdated(mNearbyDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); assertThat(mMediaDevices.get(0).getId()).isEqualTo(TEST_DEVICE_1_ID); } @@ -451,11 +466,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation() throws RemoteException { when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDevicesUpdated(mNearbyDevices); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDevicesUpdated(mNearbyDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); verify(mMediaDevice1, never()).setRangeZone(anyInt()); verify(mMediaDevice2, never()).setRangeZone(anyInt()); @@ -463,7 +478,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_verifyDeviceListCallback() { - // This test relies on mMediaOutputController.start being called while the selected device + // This test relies on mMediaSwitchingController.start being called while the selected + // device // list has exactly one item, and that item's id is: // - Different from both ids in mMediaDevices. // - Different from the id of the route published by the device under test (usually the @@ -475,12 +491,12 @@ public class MediaOutputControllerTest extends SysuiTestCase { .when(mLocalMediaManager) .getSelectedMediaDevice(); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); final List devices = new ArrayList<>(); - for (MediaItem item : mMediaOutputController.getMediaItemList()) { + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { if (item.getMediaDevice().isPresent()) { devices.add(item.getMediaDevice().get()); } @@ -488,14 +504,15 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(devices.containsAll(mMediaDevices)).isTrue(); assertThat(devices.size()).isEqualTo(mMediaDevices.size()); - assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo( - mMediaDevices.size() + 2); + assertThat(mMediaSwitchingController.getMediaItemList().size()) + .isEqualTo(mMediaDevices.size() + 2); verify(mCb).onDeviceListChanged(); } @Test public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() { - // This test relies on mMediaOutputController.start being called while the selected device + // This test relies on mMediaSwitchingController.start being called while the selected + // device // list has exactly one item, and that item's id is: // - Different from both ids in mMediaDevices. // - Different from the id of the route published by the device under test (usually the @@ -510,12 +527,12 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice1.getFeatures()).thenReturn( ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); final List devices = new ArrayList<>(); - for (MediaItem item : mMediaOutputController.getMediaItemList()) { + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { if (item.getMediaDevice().isPresent()) { devices.add(item.getMediaDevice().get()); } @@ -523,23 +540,69 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(devices.containsAll(mMediaDevices)).isTrue(); assertThat(devices.size()).isEqualTo(mMediaDevices.size()); - assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo( - mMediaDevices.size() + 1); + assertThat(mMediaSwitchingController.getMediaItemList().size()) + .isEqualTo(mMediaDevices.size() + 1); verify(mCb).onDeviceListChanged(); } + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) + @Test + public void onInputDeviceListUpdate_verifyDeviceListCallback() { + AudioDeviceInfo[] audioDeviceInfos = {}; + when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) + .thenReturn(audioDeviceInfos); + mMediaSwitchingController.start(mCb); + + // Output devices have changed. + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + final MediaDevice mediaDevice3 = + InputMediaDevice.create( + mContext, + TEST_DEVICE_3_ID, + AudioDeviceInfo.TYPE_BUILTIN_MIC, + MAX_VOLUME, + CURRENT_VOLUME, + VOLUME_FIXED_TRUE); + final MediaDevice mediaDevice4 = + InputMediaDevice.create( + mContext, + TEST_DEVICE_4_ID, + AudioDeviceInfo.TYPE_WIRED_HEADSET, + MAX_VOLUME, + CURRENT_VOLUME, + VOLUME_FIXED_TRUE); + final List inputDevices = new ArrayList<>(); + inputDevices.add(mediaDevice3); + inputDevices.add(mediaDevice4); + + // Input devices have changed. + mMediaSwitchingController.mInputDeviceCallback.onInputDeviceListUpdated(inputDevices); + + final List devices = new ArrayList<>(); + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { + if (item.getMediaDevice().isPresent()) { + devices.add(item.getMediaDevice().get()); + } + } + + assertThat(devices).containsAtLeastElementsIn(mMediaDevices); + assertThat(devices).hasSize(mMediaDevices.size() + inputDevices.size()); + verify(mCb, atLeastOnce()).onDeviceListChanged(); + } + @Test public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() { when(mMediaDevice1.isSuggestedDevice()).thenReturn(true); when(mMediaDevice2.isSuggestedDevice()).thenReturn(false); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.getMediaItemList().clear(); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); final List devices = new ArrayList<>(); int dividerSize = 0; - for (MediaItem item : mMediaOutputController.getMediaItemList()) { + for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { if (item.getMediaDevice().isPresent()) { devices.add(item.getMediaDevice().get()); } @@ -556,33 +619,33 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.mIsRefreshing = true; + mMediaSwitchingController.mIsRefreshing = true; - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.mNeedRefresh).isTrue(); + assertThat(mMediaSwitchingController.mNeedRefresh).isTrue(); } @Test public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.mIsRefreshing = true; + mMediaSwitchingController.mIsRefreshing = true; - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.mNeedRefresh).isTrue(); + assertThat(mMediaSwitchingController.mNeedRefresh).isTrue(); } @Test public void cancelMuteAwaitConnection_cancelsWithMediaManager() { when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class)); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.cancelMuteAwaitConnection(); + mMediaSwitchingController.cancelMuteAwaitConnection(); verify(mAudioManager).cancelMuteAwaitConnection(any()); } @@ -590,17 +653,17 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() { when(mAudioManager.getMutingExpectedDevice()).thenReturn(null); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.cancelMuteAwaitConnection(); + mMediaSwitchingController.cancelMuteAwaitConnection(); verify(mAudioManager, never()).cancelMuteAwaitConnection(any()); } @Test public void getAppSourceName_packageNameIsNull_returnsNull() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, "", mSpyContext.getUser(), @@ -617,25 +680,25 @@ public class MediaOutputControllerTest extends SysuiTestCase { mFlags, mVolumePanelGlobalStateInteractor, mUserTracker); - testMediaOutputController.start(mCb); + testMediaSwitchingController.start(mCb); reset(mCb); - testMediaOutputController.getAppSourceName(); + testMediaSwitchingController.getAppSourceName(); - assertThat(testMediaOutputController.getAppSourceName()).isNull(); + assertThat(testMediaSwitchingController.getAppSourceName()).isNull(); } @Test public void isActiveItem_deviceNotConnected_returnsFalse() { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); - assertThat(mMediaOutputController.isActiveItem(mMediaDevice1)).isFalse(); + assertThat(mMediaSwitchingController.isActiveItem(mMediaDevice1)).isFalse(); } @Test public void getNotificationSmallIcon_packageNameIsNull_returnsNull() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, "", mSpyContext.getUser(), @@ -652,23 +715,23 @@ public class MediaOutputControllerTest extends SysuiTestCase { mFlags, mVolumePanelGlobalStateInteractor, mUserTracker); - testMediaOutputController.start(mCb); + testMediaSwitchingController.start(mCb); reset(mCb); - testMediaOutputController.getAppSourceName(); + testMediaSwitchingController.getAppSourceName(); - assertThat(testMediaOutputController.getNotificationSmallIcon()).isNull(); + assertThat(testMediaSwitchingController.getNotificationSmallIcon()).isNull(); } @Test public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.mNeedRefresh = true; + mMediaSwitchingController.mNeedRefresh = true; - mMediaOutputController.refreshDataSetIfNeeded(); + mMediaSwitchingController.refreshDataSetIfNeeded(); - assertThat(mMediaOutputController.mNeedRefresh).isFalse(); + assertThat(mMediaSwitchingController.mNeedRefresh).isFalse(); } @Test @@ -677,13 +740,13 @@ public class MediaOutputControllerTest extends SysuiTestCase { ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); - assertThat(mMediaOutputController.isCurrentConnectedDeviceRemote()).isTrue(); + assertThat(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).isTrue(); } @Test public void addDeviceToPlayMedia_callsLocalMediaManager() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -702,16 +765,16 @@ public class MediaOutputControllerTest extends SysuiTestCase { mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); - testMediaOutputController.mLocalMediaManager = mockLocalMediaManager; + testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager; - testMediaOutputController.addDeviceToPlayMedia(mMediaDevice2); + testMediaSwitchingController.addDeviceToPlayMedia(mMediaDevice2); verify(mockLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2); } @Test public void removeDeviceFromPlayMedia_callsLocalMediaManager() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -730,15 +793,15 @@ public class MediaOutputControllerTest extends SysuiTestCase { mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); - testMediaOutputController.mLocalMediaManager = mockLocalMediaManager; + testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager; - testMediaOutputController.removeDeviceFromPlayMedia(mMediaDevice2); + testMediaSwitchingController.removeDeviceFromPlayMedia(mMediaDevice2); verify(mockLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2); } @Test public void getDeselectableMediaDevice_triggersFromLocalMediaManager() { - mMediaOutputController.getDeselectableMediaDevice(); + mMediaSwitchingController.getDeselectableMediaDevice(); verify(mLocalMediaManager).getDeselectableMediaDevice(); } @@ -746,108 +809,108 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() { int testVolume = 10; - mMediaOutputController.adjustSessionVolume(testVolume); + mMediaSwitchingController.adjustSessionVolume(testVolume); verify(mLocalMediaManager).adjustSessionVolume(testVolume); } @Test public void logInteractionAdjustVolume_triggersFromMetricLogger() { - MediaOutputMetricLogger spyMediaOutputMetricLogger = spy( - mMediaOutputController.mMetricLogger); - mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger; + MediaOutputMetricLogger spyMediaOutputMetricLogger = + spy(mMediaSwitchingController.mMetricLogger); + mMediaSwitchingController.mMetricLogger = spyMediaOutputMetricLogger; - mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1); + mMediaSwitchingController.logInteractionAdjustVolume(mMediaDevice1); verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1); } @Test public void getSessionVolumeMax_triggersFromLocalMediaManager() { - mMediaOutputController.getSessionVolumeMax(); + mMediaSwitchingController.getSessionVolumeMax(); verify(mLocalMediaManager).getSessionVolumeMax(); } @Test public void getSessionVolume_triggersFromLocalMediaManager() { - mMediaOutputController.getSessionVolume(); + mMediaSwitchingController.getSessionVolume(); verify(mLocalMediaManager).getSessionVolume(); } @Test public void getSessionName_triggersFromLocalMediaManager() { - mMediaOutputController.getSessionName(); + mMediaSwitchingController.getSessionName(); verify(mLocalMediaManager).getSessionName(); } @Test public void releaseSession_triggersFromLocalMediaManager() { - mMediaOutputController.releaseSession(); + mMediaSwitchingController.releaseSession(); verify(mLocalMediaManager).releaseSession(); } @Test public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.isAnyDeviceTransferring()).isFalse(); + assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isFalse(); } @Test public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() { when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue(); + assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue(); } @Test public void isAnyDeviceTransferring_advancedLayoutSupport() { when(mMediaDevice1.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_CONNECTING); - mMediaOutputController.start(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); + mMediaSwitchingController.start(mCb); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue(); + assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue(); } @Test public void isPlaying_stateIsNull() { when(mSessionMediaController.getPlaybackState()).thenReturn(null); - assertThat(mMediaOutputController.isPlaying()).isFalse(); + assertThat(mMediaSwitchingController.isPlaying()).isFalse(); } @Test public void onSelectedDeviceStateChanged_verifyCallback() { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.connectDevice(mMediaDevice1); + mMediaSwitchingController.connectDevice(mMediaDevice1); - mMediaOutputController.onSelectedDeviceStateChanged(mMediaDevice1, - LocalMediaManager.MediaDeviceState.STATE_CONNECTED); + mMediaSwitchingController.onSelectedDeviceStateChanged( + mMediaDevice1, LocalMediaManager.MediaDeviceState.STATE_CONNECTED); verify(mCb).onRouteChanged(); } @Test public void onDeviceAttributesChanged_verifyCallback() { - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.onDeviceAttributesChanged(); + mMediaSwitchingController.onDeviceAttributesChanged(); verify(mCb).onRouteChanged(); } @@ -855,11 +918,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onRequestFailed_verifyCallback() { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); reset(mCb); - mMediaOutputController.connectDevice(mMediaDevice2); + mMediaSwitchingController.connectDevice(mMediaDevice2); - mMediaOutputController.onRequestFailed(0 /* reason */); + mMediaSwitchingController.onRequestFailed(0 /* reason */); verify(mCb, atLeastOnce()).onRouteChanged(); } @@ -868,37 +931,40 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void getHeaderTitle_withoutMetadata_returnDefaultString() { when(mSessionMediaController.getMetadata()).thenReturn(null); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo( - mContext.getText(R.string.controls_media_title)); + assertThat( + mMediaSwitchingController + .getHeaderTitle() + .equals(mContext.getText(R.string.controls_media_title))) + .isTrue(); } @Test public void getHeaderTitle_withMetadata_returnSongName() { when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo(TEST_SONG); + assertThat(mMediaSwitchingController.getHeaderTitle().equals(TEST_SONG)).isTrue(); } @Test public void getHeaderSubTitle_withoutMetadata_returnNull() { when(mSessionMediaController.getMetadata()).thenReturn(null); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderSubTitle()).isNull(); + assertThat(mMediaSwitchingController.getHeaderSubTitle()).isNull(); } @Test public void getHeaderSubTitle_withMetadata_returnArtistName() { when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata); - mMediaOutputController.start(mCb); + mMediaSwitchingController.start(mCb); - assertThat(mMediaOutputController.getHeaderSubTitle()).isEqualTo(TEST_ARTIST); + assertThat(mMediaSwitchingController.getHeaderSubTitle().equals(TEST_ARTIST)).isTrue(); } @Test @@ -911,8 +977,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { mRoutingSessionInfos.add(mRemoteSessionInfo); when(mLocalMediaManager.getRemoteRoutingSessions()).thenReturn(mRoutingSessionInfos); - assertThat(mMediaOutputController.getActiveRemoteMediaDevices()).containsExactly( - mRemoteSessionInfo); + assertThat(mMediaSwitchingController.getActiveRemoteMediaDevices()) + .containsExactly(mRemoteSessionInfo); } @Test @@ -933,7 +999,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice2); doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice(); doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice(); - final List groupMediaDevices = mMediaOutputController.getGroupMediaDevices(); + final List groupMediaDevices = + mMediaSwitchingController.getGroupMediaDevices(); // Reset order selectedMediaDevices.clear(); selectedMediaDevices.add(selectedMediaDevice2); @@ -941,7 +1008,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.clear(); selectableMediaDevices.add(selectableMediaDevice2); selectableMediaDevices.add(selectableMediaDevice1); - final List newDevices = mMediaOutputController.getGroupMediaDevices(); + final List newDevices = mMediaSwitchingController.getGroupMediaDevices(); assertThat(newDevices.size()).isEqualTo(groupMediaDevices.size()); for (int i = 0; i < groupMediaDevices.size(); i++) { @@ -970,7 +1037,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice2); doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice(); doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice(); - final List groupMediaDevices = mMediaOutputController.getGroupMediaDevices(); + final List groupMediaDevices = + mMediaSwitchingController.getGroupMediaDevices(); // Reset order selectedMediaDevices.clear(); selectedMediaDevices.add(selectedMediaDevice2); @@ -979,7 +1047,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice3); selectableMediaDevices.add(selectableMediaDevice2); selectableMediaDevices.add(selectableMediaDevice1); - final List newDevices = mMediaOutputController.getGroupMediaDevices(); + final List newDevices = mMediaSwitchingController.getGroupMediaDevices(); assertThat(newDevices.size()).isEqualTo(5); for (int i = 0; i < groupMediaDevices.size(); i++) { @@ -991,8 +1059,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void getNotificationLargeIcon_withoutPackageName_returnsNull() { - mMediaOutputController = - new MediaOutputController( + mMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -1010,7 +1078,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - assertThat(mMediaOutputController.getNotificationIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationIcon()).isNull(); } @Test @@ -1028,7 +1096,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getLargeIcon()).thenReturn(null); - assertThat(mMediaOutputController.getNotificationIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationIcon()).isNull(); } @Test @@ -1047,7 +1115,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getLargeIcon()).thenReturn(icon); - assertThat(mMediaOutputController.getNotificationIcon()).isInstanceOf(IconCompat.class); + assertThat(mMediaSwitchingController.getNotificationIcon()).isInstanceOf(IconCompat.class); } @Test @@ -1066,7 +1134,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(false); when(notification.getLargeIcon()).thenReturn(icon); - assertThat(mMediaOutputController.getNotificationIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationIcon()).isNull(); } @Test @@ -1084,7 +1152,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getSmallIcon()).thenReturn(null); - assertThat(mMediaOutputController.getNotificationSmallIcon()).isNull(); + assertThat(mMediaSwitchingController.getNotificationSmallIcon()).isNull(); } @Test @@ -1103,8 +1171,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(notification.isMediaNotification()).thenReturn(true); when(notification.getSmallIcon()).thenReturn(icon); - assertThat(mMediaOutputController.getNotificationSmallIcon()).isInstanceOf( - IconCompat.class); + assertThat(mMediaSwitchingController.getNotificationSmallIcon()) + .isInstanceOf(IconCompat.class); } @Test @@ -1112,8 +1180,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); when(mMediaDevice1.getIcon()).thenReturn(mDrawable); - assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf( - IconCompat.class); + assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)) + .isInstanceOf(IconCompat.class); } @Test @@ -1121,13 +1189,13 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); when(mMediaDevice1.getIcon()).thenReturn(null); - assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf( - IconCompat.class); + assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)) + .isInstanceOf(IconCompat.class); } @Test public void setColorFilter_setColorFilterToDrawable() { - mMediaOutputController.setColorFilter(mDrawable, true); + mMediaSwitchingController.setColorFilter(mDrawable, true); verify(mDrawable).setColorFilter(any(PorterDuffColorFilter.class)); } @@ -1150,11 +1218,11 @@ public class MediaOutputControllerTest extends SysuiTestCase { selectableMediaDevices.add(selectableMediaDevice2); doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice(); doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice(); - assertThat(mMediaOutputController.getGroupMediaDevices().isEmpty()).isFalse(); + assertThat(mMediaSwitchingController.getGroupMediaDevices().isEmpty()).isFalse(); - mMediaOutputController.resetGroupMediaDevices(); + mMediaSwitchingController.resetGroupMediaDevices(); - assertThat(mMediaOutputController.mGroupMediaDevices.isEmpty()).isTrue(); + assertThat(mMediaSwitchingController.mGroupMediaDevices.isEmpty()).isTrue(); } @Test @@ -1164,7 +1232,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice1.isVolumeFixed()).thenReturn(true); - assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isFalse(); + assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isFalse(); } @Test @@ -1174,7 +1242,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice1.isVolumeFixed()).thenReturn(false); - assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isTrue(); + assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isTrue(); } @Test @@ -1187,7 +1255,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice2.getDeviceType()).thenReturn( MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); - mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); + mMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(), anyLong()); @@ -1195,8 +1263,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() { - MediaOutputController testMediaOutputController = - new MediaOutputController( + MediaSwitchingController testMediaSwitchingController = + new MediaSwitchingController( mSpyContext, null, mSpyContext.getUser(), @@ -1214,7 +1282,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mVolumePanelGlobalStateInteractor, mUserTracker); - testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); + testMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); verify(mPowerExemptionManager, never()).addToTemporaryAllowList(anyString(), anyInt(), anyString(), @@ -1223,22 +1291,22 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void onMetadataChanged_triggersOnMetadataChanged() { - mMediaOutputController.mCallback = this.mCallback; + mMediaSwitchingController.mCallback = this.mCallback; - mMediaOutputController.mCb.onMetadataChanged(mMediaMetadata); + mMediaSwitchingController.mCb.onMetadataChanged(mMediaMetadata); - verify(mMediaOutputController.mCallback).onMediaChanged(); + verify(mMediaSwitchingController.mCallback).onMediaChanged(); } @Test public void onPlaybackStateChanged_updateWithNullState_onMediaStoppedOrPaused() { when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING); - mMediaOutputController.mCallback = this.mCallback; - mMediaOutputController.start(mCb); + mMediaSwitchingController.mCallback = this.mCallback; + mMediaSwitchingController.start(mCb); - mMediaOutputController.mCb.onPlaybackStateChanged(null); + mMediaSwitchingController.mCb.onPlaybackStateChanged(null); - verify(mMediaOutputController.mCallback).onMediaStoppedOrPaused(); + verify(mMediaSwitchingController.mCallback).onMediaStoppedOrPaused(); } @Test @@ -1246,10 +1314,29 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mDialogTransitionAnimator.createActivityTransitionController(mDialogLaunchView)) .thenReturn(mActivityTransitionAnimatorController); when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); - mMediaOutputController.mCallback = this.mCallback; + mMediaSwitchingController.mCallback = this.mCallback; - mMediaOutputController.launchBluetoothPairing(mDialogLaunchView); + mMediaSwitchingController.launchBluetoothPairing(mDialogLaunchView); verify(mCallback).dismissDialog(); } + + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) + @Test + public void getSelectedMediaDevice() { + // Mock MediaDevice since none of the output media device constructor is publicly available + // outside of SettingsLib package. + final MediaDevice selectedOutputMediaDevice = mock(MediaDevice.class); + doReturn(Collections.singletonList(selectedOutputMediaDevice)) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + + // Mock selected input media device. + final MediaDevice selectedInputMediaDevice = mock(MediaDevice.class); + doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice(); + + List selectedMediaDevices = mMediaSwitchingController.getSelectedMediaDevice(); + assertThat(selectedMediaDevices) + .containsExactly(selectedOutputMediaDevice, selectedInputMediaDevice); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 1ec4814181ec628d9f9a325e568f4fb5669f5792..ab846f143caf10059aa79c81dac4b9d63f132949 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -17,8 +17,13 @@ package com.android.systemui.notetask import android.app.role.RoleManager import android.app.role.RoleManager.ROLE_NOTES +import android.hardware.input.InputManager +import android.hardware.input.KeyGestureEvent import android.os.UserHandle import android.os.UserManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import android.view.KeyEvent.ACTION_DOWN import android.view.KeyEvent.ACTION_UP @@ -28,6 +33,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT +import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.concurrency.FakeExecutor @@ -42,6 +49,7 @@ import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -57,7 +65,10 @@ import org.mockito.MockitoAnnotations.initMocks @RunWith(AndroidJUnit4::class) internal class NoteTaskInitializerTest : SysuiTestCase() { + @get:Rule val setFlagsRule = SetFlagsRule() + @Mock lateinit var commandQueue: CommandQueue + @Mock lateinit var inputManager: InputManager @Mock lateinit var bubbles: Bubbles @Mock lateinit var controller: NoteTaskController @Mock lateinit var roleManager: RoleManager @@ -74,10 +85,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true) } - private fun createUnderTest( - isEnabled: Boolean, - bubbles: Bubbles?, - ): NoteTaskInitializer = + private fun createUnderTest(isEnabled: Boolean, bubbles: Bubbles?): NoteTaskInitializer = NoteTaskInitializer( controller = controller, commandQueue = commandQueue, @@ -86,6 +94,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { roleManager = roleManager, userTracker = userTracker, keyguardUpdateMonitor = keyguardMonitor, + inputManager = inputManager, backgroundExecutor = executor, ) @@ -94,7 +103,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { code: Int, downTime: Long = 0L, eventTime: Long = 0L, - metaState: Int = 0 + metaState: Int = 0, ): KeyEvent = KeyEvent(downTime, eventTime, action, code, 0 /*repeat*/, metaState) @Test @@ -103,7 +112,6 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { createUnderTest(isEnabled = true, bubbles = bubbles).initialize() - verify(commandQueue).addCallback(any()) verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles() verify(keyguardMonitor).registerCallback(any()) @@ -115,7 +123,6 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { createUnderTest(isEnabled = true, bubbles = bubbles).initialize() - verify(commandQueue).addCallback(any()) verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) verify(controller, never()).setNoteTaskShortcutEnabled(any(), any()) verify(keyguardMonitor).registerCallback(any()) @@ -155,12 +162,13 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun initialize_handleSystemKey() { val expectedKeyEvent = createKeyEvent( ACTION_DOWN, KEYCODE_N, - metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, ) val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() @@ -171,6 +179,70 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { verify(controller).showNoteTask(any()) } + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun handlesShortcut_metaCtrlN() { + val gestureEvent = + KeyGestureEvent.Builder() + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N)) + .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) + .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + .build() + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { + verify(inputManager).registerKeyGestureEventHandler(capture()) + } + + assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue() + + executor.runAllReady() + verify(controller).showNoteTask(eq(KEYBOARD_SHORTCUT)) + } + + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun handlesShortcut_stylusTailButton() { + val gestureEvent = + KeyGestureEvent.Builder() + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) + .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + .build() + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { + verify(inputManager).registerKeyGestureEventHandler(capture()) + } + + assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue() + + executor.runAllReady() + verify(controller).showNoteTask(eq(TAIL_BUTTON)) + } + + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun ignoresUnrelatedShortcuts() { + val gestureEvent = + KeyGestureEvent.Builder() + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + .build() + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { + verify(inputManager).registerKeyGestureEventHandler(capture()) + } + + assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isFalse() + + executor.runAllReady() + verify(controller, never()).showNoteTask(any()) + } + @Test fun initialize_userUnlocked_shouldUpdateNoteTask() { whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(false) @@ -219,6 +291,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun tailButtonGestureDetection_singlePress_shouldShowNoteTaskOnUp() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() @@ -237,6 +310,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun tailButtonGestureDetection_doublePress_shouldNotShowNoteTaskTwice() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() @@ -259,6 +333,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun tailButtonGestureDetection_longPress_shouldNotShowNoteTask() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt index 70af5e75105d644e81f6004992c61563c93467ea..6423d25cb88a789fa3f3f50a9848a6744ae8e154 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.test.assert import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onChildAt import androidx.compose.ui.test.onChildren import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag @@ -40,6 +39,7 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.infinitegrid.DefaultEditTileGrid import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.TileCategory @@ -57,14 +57,13 @@ class DragAndDropTest : SysuiTestCase() { @Composable private fun EditTileGridUnderTest( listState: EditTileListState, - onSetTiles: (List) -> Unit + onSetTiles: (List) -> Unit, ) { DefaultEditTileGrid( currentListState = listState, otherTiles = listOf(), columns = 4, modifier = Modifier.fillMaxSize(), - onAddTile = { _, _ -> }, onRemoveTile = {}, onSetTiles = onSetTiles, onResize = {}, @@ -182,7 +181,7 @@ class DragAndDropTest : SysuiTestCase() { private fun ComposeContentTestRule.assertTileGridContainsExactly(specs: List) { onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG).onChildren().apply { fetchSemanticsNodes().forEachIndexed { index, _ -> - get(index).onChildAt(0).assert(hasContentDescription(specs[index])) + get(index).assert(hasContentDescription(specs[index])) } } } @@ -198,7 +197,7 @@ class DragAndDropTest : SysuiTestCase() { icon = Icon.Resource( android.R.drawable.star_on, - ContentDescription.Loaded(tileSpec) + ContentDescription.Loaded(tileSpec), ), label = AnnotatedString(tileSpec), appName = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..682ed92cc593014803c13e73397b014f5901273b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel +import androidx.compose.ui.text.AnnotatedString +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.infinitegrid.DefaultEditTileGrid +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory +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 ResizingTest : SysuiTestCase() { + @get:Rule val composeRule = createComposeRule() + + @Composable + private fun EditTileGridUnderTest(listState: EditTileListState, onResize: (TileSpec) -> Unit) { + DefaultEditTileGrid( + currentListState = listState, + otherTiles = listOf(), + columns = 4, + modifier = Modifier.fillMaxSize(), + onRemoveTile = {}, + onSetTiles = {}, + onResize = onResize, + ) + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun resizedIcon_shouldBeLarge() { + var tiles by mutableStateOf(TestEditTiles) + val listState = EditTileListState(tiles, 4) + composeRule.setContent { + EditTileGridUnderTest(listState) { spec -> + tiles = + tiles.map { + if (it.tile.tileSpec == spec) { + toggleWidth(it) + } else { + it + } + } + } + } + composeRule.waitForIdle() + + composeRule + .onNodeWithContentDescription("tileA") + .performCustomAccessibilityActionWithLabel("Toggle size") + + assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(2) + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun resizedLarge_shouldBeIcon() { + var tiles by mutableStateOf(TestEditTiles) + val listState = EditTileListState(tiles, 4) + composeRule.setContent { + EditTileGridUnderTest(listState) { spec -> + tiles = + tiles.map { + if (it.tile.tileSpec == spec) { + toggleWidth(it) + } else { + it + } + } + } + } + composeRule.waitForIdle() + + composeRule + .onNodeWithContentDescription("tileD_large") + .performCustomAccessibilityActionWithLabel("Toggle size") + + assertThat(tiles.find { it.tile.tileSpec.spec == "tileD_large" }?.width).isEqualTo(1) + } + + companion object { + private fun toggleWidth(tile: SizedTile): SizedTile { + return SizedTileImpl(tile.tile, width = if (tile.isIcon) 2 else 1) + } + + private fun createEditTile(tileSpec: String): SizedTile { + return SizedTileImpl( + EditTileViewModel( + tileSpec = TileSpec.create(tileSpec), + icon = + Icon.Resource( + android.R.drawable.star_on, + ContentDescription.Loaded(tileSpec), + ), + label = AnnotatedString(tileSpec), + appName = null, + isCurrent = true, + availableEditActions = emptySet(), + category = TileCategory.UNKNOWN, + ), + getWidth(tileSpec), + ) + } + + private fun getWidth(tileSpec: String): Int { + return if (tileSpec.endsWith("large")) { + 2 + } else { + 1 + } + } + + private val TestEditTiles = + listOf( + createEditTile("tileA"), + createEditTile("tileB"), + createEditTile("tileC"), + createEditTile("tileD_large"), + createEditTile("tileE"), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index 2e2ac3eb7183459e528db76dd7b7df3547ffdbd0..a0ecb802dd61455430eb480c8507fdae0f6797f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -231,7 +231,7 @@ class UserTrackerImplTest : SysuiTestCase() { "", "", UserInfo.FLAG_MANAGED_PROFILE, - UserManager.USER_TYPE_PROFILE_MANAGED + UserManager.USER_TYPE_PROFILE_MANAGED, ) infoProfile.profileGroupId = id listOf(info, infoProfile) @@ -261,7 +261,7 @@ class UserTrackerImplTest : SysuiTestCase() { "", "", UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE, - UserManager.USER_TYPE_PROFILE_MANAGED + UserManager.USER_TYPE_PROFILE_MANAGED, ) infoProfile.profileGroupId = id listOf(info, infoProfile) @@ -291,7 +291,7 @@ class UserTrackerImplTest : SysuiTestCase() { "", "", UserInfo.FLAG_MANAGED_PROFILE, - UserManager.USER_TYPE_PROFILE_MANAGED + UserManager.USER_TYPE_PROFILE_MANAGED, ) infoProfile.profileGroupId = id listOf(info, infoProfile) @@ -423,7 +423,7 @@ class UserTrackerImplTest : SysuiTestCase() { "", "", UserInfo.FLAG_MANAGED_PROFILE, - UserManager.USER_TYPE_PROFILE_MANAGED + UserManager.USER_TYPE_PROFILE_MANAGED, ) infoProfile.profileGroupId = id listOf(info, infoProfile) @@ -469,6 +469,24 @@ class UserTrackerImplTest : SysuiTestCase() { assertThat(callback.calledOnProfilesChanged).isEqualTo(0) } + @Test + fun testisUserSwitching() = + testScope.runTest { + tracker.initialize(0) + val newID = 5 + val profileID = newID + 10 + + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + assertThat(tracker.isUserSwitching).isFalse() + + captor.value.onUserSwitching(newID, userSwitchingReply) + assertThat(tracker.isUserSwitching).isTrue() + + captor.value.onUserSwitchComplete(newID) + assertThat(tracker.isUserSwitching).isFalse() + } + private class TestCallback : UserTracker.Callback { var calledOnUserChanging = 0 var calledOnUserChanged = 0 diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index c0444fecb2bdf40bbdf39885cd5c594e7c84b5c1..b4a0f23be9b1db61964fd4205cc2ee8db3a12b72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -118,7 +118,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { override fun create( lifecycleOwner: LifecycleOwner, touchHandlers: Set, - loggingName: String + loggingName: String, ): AmbientTouchComponent = object : AmbientTouchComponent { override fun getTouchMonitor(): TouchMonitor = touchMonitor @@ -141,7 +141,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, kosmos.lockscreenSmartspaceController, - logcatLogBuffer("GlanceableHubContainerControllerTest") + logcatLogBuffer("GlanceableHubContainerControllerTest"), ) } testableLooper = TestableLooper.get(this) @@ -150,7 +150,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH) overrideResource( R.dimen.communal_bottom_edge_swipe_region_height, - BOTTOM_SWIPE_REGION_WIDTH + BOTTOM_SWIPE_REGION_WIDTH, ) // Make communal available so that communalInteractor.desiredScene accurately reflects @@ -188,7 +188,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, kosmos.lockscreenSmartspaceController, - logcatLogBuffer("GlanceableHubContainerControllerTest") + logcatLogBuffer("GlanceableHubContainerControllerTest"), ) // First call succeeds. @@ -217,7 +217,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, kosmos.lockscreenSmartspaceController, - logcatLogBuffer("GlanceableHubContainerControllerTest") + logcatLogBuffer("GlanceableHubContainerControllerTest"), ) assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED) @@ -241,7 +241,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, kosmos.lockscreenSmartspaceController, - logcatLogBuffer("GlanceableHubContainerControllerTest") + logcatLogBuffer("GlanceableHubContainerControllerTest"), ) // Only initView without attaching a view as we don't want the flows to start collecting @@ -342,7 +342,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.GLANCEABLE_HUB, value = 1.0f, - transitionState = TransitionState.RUNNING + transitionState = TransitionState.RUNNING, ) ) testableLooper.processAllMessages() @@ -449,7 +449,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { fun gestureExclusionZone_setAfterInit() = with(kosmos) { testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR) goToScene(CommunalScenes.Communal) assertThat(containerView.systemGestureExclusionRects) @@ -458,13 +457,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { /* left= */ 0, /* top= */ TOP_SWIPE_REGION_WIDTH, /* right= */ CONTAINER_WIDTH, - /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH - ), - Rect( - /* left= */ 0, - /* top= */ 0, - /* right= */ 0, - /* bottom= */ CONTAINER_HEIGHT + /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH, ) ) } @@ -475,67 +468,14 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { fun gestureExclusionZone_setAfterInit_fullSwipe() = with(kosmos) { testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR) goToScene(CommunalScenes.Communal) - assertThat(containerView.systemGestureExclusionRects) - .containsExactly( - Rect( - /* left= */ 0, - /* top= */ 0, - /* right= */ 0, - /* bottom= */ CONTAINER_HEIGHT - ) - ) + assertThat(containerView.systemGestureExclusionRects).isEmpty() } } @Test @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) - fun gestureExclusionZone_setAfterInit_rtl() = - with(kosmos) { - testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL) - goToScene(CommunalScenes.Communal) - - assertThat(containerView.systemGestureExclusionRects) - .containsExactly( - Rect( - /* left= */ 0, - /* top= */ TOP_SWIPE_REGION_WIDTH, - /* right= */ CONTAINER_WIDTH, - /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH - ), - Rect( - /* left= */ 0, - /* top= */ 0, - /* right= */ CONTAINER_WIDTH, - /* bottom= */ CONTAINER_HEIGHT - ) - ) - } - } - - @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) - fun gestureExclusionZone_setAfterInit_rtl_fullSwipe() = - with(kosmos) { - testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL) - goToScene(CommunalScenes.Communal) - - assertThat(containerView.systemGestureExclusionRects) - .containsExactly( - Rect( - /* left= */ 0, - /* top= */ 0, - /* right= */ CONTAINER_WIDTH, - /* bottom= */ CONTAINER_HEIGHT - ) - ) - } - } - - @Test fun gestureExclusionZone_unsetWhenShadeOpen() = with(kosmos) { testScope.runTest { @@ -554,6 +494,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_unsetWhenBouncerOpen() = with(kosmos) { testScope.runTest { @@ -572,6 +513,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_unsetWhenHubClosed() = with(kosmos) { testScope.runTest { @@ -597,7 +539,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { whenever( notificationStackScrollLayoutController.isBelowLastNotification( any(), - any() + any(), ) ) .thenReturn(false) @@ -675,7 +617,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { from = KeyguardState.GONE, to = KeyguardState.GLANCEABLE_HUB, value = 1.0f, - transitionState = TransitionState.RUNNING + transitionState = TransitionState.RUNNING, ) ) testableLooper.processAllMessages() @@ -696,7 +638,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { whenever( notificationStackScrollLayoutController.isBelowLastNotification( any(), - any() + any(), ) ) .thenReturn(true) @@ -728,7 +670,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { whenever( notificationStackScrollLayoutController.isBelowLastNotification( any(), - any() + any(), ) ) .thenReturn(true) @@ -752,7 +694,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { whenever( notificationStackScrollLayoutController.isBelowLastNotification( any(), - any() + any(), ) ) .thenReturn(true) @@ -805,13 +747,13 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB, - kosmos.testScope + kosmos.testScope, ) } else { kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN, - kosmos.testScope + kosmos.testScope, ) } testableLooper.processAllMessages() @@ -836,7 +778,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat() / 2, CONTAINER_HEIGHT.toFloat() / 2, - 0 + 0, ) private val CANCEL_EVENT = @@ -846,7 +788,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { MotionEvent.ACTION_CANCEL, CONTAINER_WIDTH.toFloat() / 2, CONTAINER_HEIGHT.toFloat() / 2, - 0 + 0, ) private val MOVE_EVENT = @@ -856,7 +798,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { MotionEvent.ACTION_MOVE, CONTAINER_WIDTH.toFloat() / 2, CONTAINER_HEIGHT.toFloat() / 2, - 0 + 0, ) private val UP_EVENT = @@ -866,7 +808,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { MotionEvent.ACTION_UP, CONTAINER_WIDTH.toFloat() / 2, CONTAINER_HEIGHT.toFloat() / 2, - 0 + 0, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/QuickStepContractTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/QuickStepContractTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6254fb128859f59ef28ac964770801e9bf194033 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/QuickStepContractTest.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.systemui.shared.system + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class QuickStepContractTest : SysuiTestCase() { + @Test + fun isBackGestureDisabled_hubShowing() { + val sysuiStateFlags = SYSUI_STATE_COMMUNAL_HUB_SHOWING + + // Gestures are disabled while on the hub. + assertThat( + QuickStepContract.isBackGestureDisabled(sysuiStateFlags, /* forTrackpad= */ false) + ) + .isTrue() + } + + @Test + fun isBackGestureDisabled_hubAndShadeShowing() { + val sysuiStateFlags = + SYSUI_STATE_COMMUNAL_HUB_SHOWING and SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE + + // Gestures are enabled because the shade shows over the hub. + assertThat( + QuickStepContract.isBackGestureDisabled(sysuiStateFlags, /* forTrackpad= */ false) + ) + .isFalse() + } + + @Test + fun isBackGestureDisabled_hubAndBouncerShowing() { + val sysuiStateFlags = SYSUI_STATE_COMMUNAL_HUB_SHOWING and SYSUI_STATE_BOUNCER_SHOWING + + // Gestures are enabled because the bouncer shows over the hub. + assertThat( + QuickStepContract.isBackGestureDisabled(sysuiStateFlags, /* forTrackpad= */ false) + ) + .isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt index d2dfc9257e7e74d596abf8a3ad4e21c4909ef055..907c68440b557c452cc4b5d2bf8a144b1b16d0e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt @@ -17,16 +17,19 @@ package com.android.systemui.statusbar.disableflags.data.repository import android.app.StatusBarManager.DISABLE2_NONE import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS +import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS import android.app.StatusBarManager.DISABLE_CLOCK import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO import android.content.res.Configuration import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBufferFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel @@ -82,7 +85,7 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { @Test fun disableFlags_initialValue_none() { assertThat(underTest.disableFlags.value) - .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)) + .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false)) } @Test @@ -182,12 +185,7 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { fun disableFlags_quickSettingsDisabled_quickSettingsEnabledFalse() = testScope.runTest { getCommandQueueCallback() - .disable( - DISPLAY_ID, - DISABLE_NONE, - DISABLE2_QUICK_SETTINGS, - /* animate= */ false, - ) + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_QUICK_SETTINGS, /* animate= */ false) assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() } @@ -217,21 +215,84 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { configuration.orientation = Configuration.ORIENTATION_LANDSCAPE mContext.orCreateTestableResources.addOverride( R.bool.config_use_split_notification_shade, - /* value= */ false + /* value= */ false, ) remoteInputQuickSettingsDisabler.setRemoteInputActive(true) remoteInputQuickSettingsDisabler.onConfigChanged(configuration) + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + // THEN quick settings is disabled (even if the disable flags don't say so) + assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() + } + + @Test + fun disableFlags_clockDisabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_CLOCK, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isClockEnabled).isFalse() + } + + @Test + fun disableFlags_clockEnabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isClockEnabled).isTrue() + } + + @Test + fun disableFlags_notificationIconsDisabled() = + testScope.runTest { getCommandQueueCallback() .disable( DISPLAY_ID, - DISABLE_NONE, + DISABLE_NOTIFICATION_ICONS, DISABLE2_NONE, /* animate= */ false, ) - // THEN quick settings is disabled (even if the disable flags don't say so) - assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() + assertThat(underTest.disableFlags.value.areNotificationIconsEnabled).isFalse() + } + + @Test + fun disableFlags_notificationIconsEnabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.areNotificationIconsEnabled).isTrue() + } + + @Test + fun disableFlags_systemInfoDisabled_viaDisable1() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_SYSTEM_INFO, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isFalse() + } + + @Test + fun disableFlags_systemInfoDisabled_viaDisable2() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_SYSTEM_ICONS, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isFalse() + } + + @Test + fun disableFlags_systemInfoEnabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isTrue() } @Test @@ -267,6 +328,34 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() } + @Test + fun disableFlags_animateFalse() = + testScope.runTest { + getCommandQueueCallback() + .disable( + DISPLAY_ID, + DISABLE_NOTIFICATION_ALERTS, + DISABLE2_NONE, + /* animate= */ false, + ) + + assertThat(underTest.disableFlags.value.animate).isFalse() + } + + @Test + fun disableFlags_animateTrue() = + testScope.runTest { + getCommandQueueCallback() + .disable( + DISPLAY_ID, + DISABLE_NOTIFICATION_ALERTS, + DISABLE2_NONE, + /* animate= */ true, + ) + + assertThat(underTest.disableFlags.value.animate).isTrue() + } + private fun getCommandQueueCallback(): CommandQueue.Callbacks { val callbackCaptor = argumentCaptor() verify(commandQueue).addCallback(callbackCaptor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index af043093b6f7933ebead4a834780b20980201787..c710c56fd516685a4b91bedd53784ee53f6cca11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -168,7 +168,7 @@ import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; -import com.android.systemui.statusbar.core.StatusBarInitializer; +import com.android.systemui.statusbar.core.StatusBarInitializerImpl; import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -504,7 +504,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mock(FragmentService.class), mLightBarController, mAutoHideController, - new StatusBarInitializer( + new StatusBarInitializerImpl( mStatusBarWindowController, mCollapsedStatusBarFragmentProvider, emptySet()), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 135fab877d5794c4b8bd51b1a669a046415713ca..63a560ffd2c11c339dafc259fac8799cd4395a9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -18,6 +18,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS; import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS; +import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; @@ -157,6 +158,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableNone() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -167,6 +169,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableSystemInfo_systemAnimationIdle_doesHide() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -184,6 +187,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -213,6 +217,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -228,8 +233,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -245,6 +250,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -268,6 +274,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableNotifications() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -285,6 +292,25 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + public void testDisableNotifications_doesNothingWhenFlagEnabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); + + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); + + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableClock() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -301,8 +327,27 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getClockView().getVisibility()); } + @Test + @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + public void testDisableClock_doesNothingWhenFlagEnabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_shadeOpenAndShouldHide_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -320,6 +365,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_shadeOpenButNotShouldHide_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -338,6 +384,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { /** Regression test for b/279790651. */ @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -365,6 +412,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_notTransitioningToOccluded_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -380,6 +428,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isTransitioningToOccluded_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -395,6 +444,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -425,7 +475,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_noOngoingCall_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -437,7 +487,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -450,7 +500,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -463,7 +513,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCallButAlsoHun_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -476,7 +526,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_ongoingCallEnded_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -500,7 +550,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -517,7 +567,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void screenSharingChipsDisabled_ignoresNewCallback() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -551,6 +601,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void noOngoingActivity_chipHidden() { resumeAndGetFragment(); @@ -568,6 +619,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -580,9 +632,37 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } + @Test + @EnableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + FLAG_STATUS_BAR_RON_CHIPS, + FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() { + resumeAndGetFragment(); + + assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasPrimaryOngoingActivity= */ true, + /* hasSecondaryOngoingActivity= */ false, + /* shouldAnimate= */ false); + + assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasPrimaryOngoingActivity= */ false, + /* hasSecondaryOngoingActivity= */ false, + /* shouldAnimate= */ false); + + assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + } + @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasSecondaryOngoingActivity_butRonsFlagOff_secondaryChipHidden() { resumeAndGetFragment(); @@ -596,6 +676,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -610,7 +691,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -627,6 +708,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -644,7 +726,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasOngoingActivityButAlsoHun_chipHidden_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -661,6 +743,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasOngoingActivitiesButAlsoHun_chipsHidden_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -678,7 +761,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void primaryOngoingActivityEnded_chipHidden_ronsFlagOff() { resumeAndGetFragment(); @@ -701,6 +784,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void primaryOngoingActivityEnded_chipHidden_ronsFlagOn() { resumeAndGetFragment(); @@ -723,6 +807,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void secondaryOngoingActivityEnded_chipHidden() { resumeAndGetFragment(); @@ -745,7 +830,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -764,6 +849,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -782,7 +868,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -815,6 +901,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -848,6 +935,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void isHomeStatusBarAllowedByScene_false_everythingHidden() { resumeAndGetFragment(); @@ -861,6 +949,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void isHomeStatusBarAllowedByScene_true_everythingShown() { resumeAndGetFragment(); @@ -874,6 +963,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -891,6 +981,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -908,6 +999,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() { resumeAndGetFragment(); @@ -921,6 +1013,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); @@ -932,6 +1025,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_NotDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(false); @@ -943,6 +1037,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); @@ -953,6 +1048,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false); @@ -1006,6 +1102,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testStatusBarIcons_hiddenThroughoutCameraLaunch() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -1028,6 +1125,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -1063,6 +1161,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt index 997c00cf49a482d984dcafbc75efb483a784015b..c435d3d996804fb0b4f8bfa2e309b44c8e41665d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -16,10 +16,12 @@ package com.android.systemui.statusbar.phone.fragment +import android.platform.test.annotations.DisableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule import junit.framework.Assert.assertEquals @@ -36,6 +38,7 @@ private const val INITIAL_ALPHA = 1f @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) class MultiSourceMinAlphaControllerTest : SysuiTestCase() { private val view = View(context) @@ -60,7 +63,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { multiSourceMinAlphaController.animateToAlpha( alpha = 0.5f, sourceId = TEST_SOURCE_1, - duration = TEST_ANIMATION_DURATION + duration = TEST_ANIMATION_DURATION, ) animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION) assertEquals(0.5f, view.alpha) @@ -71,7 +74,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { multiSourceMinAlphaController.animateToAlpha( alpha = 0.5f, sourceId = TEST_SOURCE_1, - duration = TEST_ANIMATION_DURATION + duration = TEST_ANIMATION_DURATION, ) multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2) multiSourceMinAlphaController.reset() @@ -94,7 +97,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { multiSourceMinAlphaController.animateToAlpha( alpha = 0f, sourceId = TEST_SOURCE_1, - duration = TEST_ANIMATION_DURATION + duration = TEST_ANIMATION_DURATION, ) animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2) multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..5036e775211e918d9832db7bd204a89304b61515 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.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.systemui.statusbar.pipeline.shared.domain.interactor + +import android.app.StatusBarManager.DISABLE2_NONE +import android.app.StatusBarManager.DISABLE_CLOCK +import android.app.StatusBarManager.DISABLE_NONE +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.test.runTest + +@SmallTest +class CollapsedStatusBarInteractorTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + val disableFlagsRepo = kosmos.fakeDisableFlagsRepository + + val underTest = kosmos.collapsedStatusBarInteractor + + @Test + fun visibilityViaDisableFlags_allDisabled() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel( + DISABLE_CLOCK or DISABLE_NOTIFICATION_ICONS or DISABLE_SYSTEM_INFO, + DISABLE2_NONE, + animate = false, + ) + + assertThat(latest!!.isClockAllowed).isFalse() + assertThat(latest!!.areNotificationIconsAllowed).isFalse() + assertThat(latest!!.isSystemInfoAllowed).isFalse() + } + + @Test + fun visibilityViaDisableFlags_allEnabled() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false) + + assertThat(latest!!.isClockAllowed).isTrue() + assertThat(latest!!.areNotificationIconsAllowed).isTrue() + assertThat(latest!!.isSystemInfoAllowed).isTrue() + } + + @Test + fun visibilityViaDisableFlags_animateFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false) + + assertThat(latest!!.animate).isFalse() + } + + @Test + fun visibilityViaDisableFlags_animateTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = true) + + assertThat(latest!!.animate).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 7ae6ea51b91247d2d6a8a0b11f98423d127982be..bd857807851cafa5f9338f5a1d5c9aab498d103c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -16,21 +16,27 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import android.app.StatusBarManager.DISABLE2_NONE +import android.app.StatusBarManager.DISABLE_CLOCK +import android.app.StatusBarManager.DISABLE_NONE +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -38,27 +44,25 @@ import com.android.systemui.log.assertLogsWtf import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor -import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip -import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel import com.android.systemui.statusbar.data.model.StatusBarMode import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository +import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor -import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow @@ -83,21 +87,15 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository - private val underTest = - CollapsedStatusBarViewModelImpl( - kosmos.lightsOutInteractor, - kosmos.activeNotificationsInteractor, - kosmos.keyguardTransitionInteractor, - kosmos.sceneInteractor, - kosmos.sceneContainerOcclusionInteractor, - kosmos.ongoingActivityChipsViewModel, - kosmos.applicationCoroutineScope, - ) + private lateinit var underTest: CollapsedStatusBarViewModel @Before fun setUp() { setUpPackageManagerForMediaProjection(kosmos) + // Initialize here because some flags are checked when this class is constructed + underTest = kosmos.collapsedStatusBarViewModel } @Test @@ -495,14 +493,272 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { assertThat(latest).isTrue() } + @Test + fun isClockVisible_allowedByDisableFlags_visible() = + testScope.runTest { + val latest by collectLastValue(underTest.isClockVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun isClockVisible_notAllowedByDisableFlags_gone() = + testScope.runTest { + val latest by collectLastValue(underTest.isClockVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.GONE) + } + + @Test + fun isNotificationIconContainerVisible_allowedByDisableFlags_visible() = + testScope.runTest { + val latest by collectLastValue(underTest.isNotificationIconContainerVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun isNotificationIconContainerVisible_notAllowedByDisableFlags_gone() = + testScope.runTest { + val latest by collectLastValue(underTest.isNotificationIconContainerVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NOTIFICATION_ICONS, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.GONE) + } + + @Test + fun isSystemInfoVisible_allowedByDisableFlags_visible() = + testScope.runTest { + val latest by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun isSystemInfoVisible_notAllowedByDisableFlags_gone() = + testScope.runTest { + val latest by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.GONE) + } + + @Test + @DisableSceneContainer + fun lockscreenVisible_sceneFlagOff_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + testScope = this, + ) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableSceneContainer + fun lockscreenVisible_sceneFlagOn_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @DisableSceneContainer + fun bouncerVisible_sceneFlagOff_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + testScope = this, + ) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableSceneContainer + fun bouncerVisible_sceneFlagOn_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @DisableSceneContainer + fun keyguardIsOccluded_sceneFlagOff_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + testScope = this, + ) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @EnableSceneContainer + fun keyguardIsOccluded_sceneFlagOn_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun keyguardNotShown_sceneFlagOff_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + transitionKeyguardToGone() + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun shadeNotShown_sceneFlagOff_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + kosmos.shadeTestUtil.setShadeExpansion(0f) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @EnableSceneContainer + fun keyguardNotShownAndShadeNotShown_sceneFlagOn_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Gone) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun shadeShown_sceneFlagOff_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + kosmos.shadeTestUtil.setShadeExpansion(1f) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableSceneContainer + fun shadeShown_sceneFlagOn_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + kosmos.sceneContainerRepository.snapToScene(Scenes.Shade) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + private fun activeNotificationsStore(notifications: List) = ActiveNotificationsStore.Builder() .apply { notifications.forEach(::addIndividualNotif) } .build() private val testNotifications = - listOf( - activeNotificationModel(key = "notif1"), - activeNotificationModel(key = "notif2"), + listOf(activeNotificationModel(key = "notif1"), activeNotificationModel(key = "notif2")) + + private suspend fun transitionKeyguardToGone() { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt index 4834d367d4be602f107ef47e747390ff7ea19a21..cc90c1167ef1e5707d72ea90423451179c8d1c6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import android.view.View import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import kotlinx.coroutines.flow.Flow @@ -36,9 +37,29 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { override val isHomeStatusBarAllowedByScene = MutableStateFlow(false) - override fun areNotificationsLightsOut(displayId: Int): Flow = areNotificationLightsOut + override val isClockVisible = + MutableStateFlow( + CollapsedStatusBarViewModel.VisibilityModel( + visibility = View.GONE, + shouldAnimateChange = false, + ) + ) + + override val isNotificationIconContainerVisible = + MutableStateFlow( + CollapsedStatusBarViewModel.VisibilityModel( + visibility = View.GONE, + shouldAnimateChange = false, + ) + ) - fun setNotificationLightsOut(lightsOut: Boolean) { - areNotificationLightsOut.value = lightsOut - } + override val isSystemInfoVisible = + MutableStateFlow( + CollapsedStatusBarViewModel.VisibilityModel( + visibility = View.GONE, + shouldAnimateChange = false, + ) + ) + + override fun areNotificationsLightsOut(displayId: Int): Flow = areNotificationLightsOut } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..38e04bb1d00fbea880589db32823e736c29d24b2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.window.data.repository + +import android.app.StatusBarManager.WINDOW_NAVIGATION_BAR +import android.app.StatusBarManager.WINDOW_STATE_HIDDEN +import android.app.StatusBarManager.WINDOW_STATE_HIDING +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.commandQueue +import com.android.systemui.statusbar.window.data.model.StatusBarWindowState +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.Mockito.verify +import org.mockito.kotlin.argumentCaptor + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class StatusBarWindowStateRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val commandQueue = kosmos.commandQueue + private val underTest = + StatusBarWindowStateRepository(commandQueue, DISPLAY_ID, testScope.backgroundScope) + + private val callback: CommandQueue.Callbacks + get() { + testScope.runCurrent() + val callbackCaptor = argumentCaptor() + verify(commandQueue).addCallback(callbackCaptor.capture()) + return callbackCaptor.firstValue + } + + @Test + fun windowState_notSameDisplayId_notUpdated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + + callback.setWindowState(DISPLAY_ID + 1, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + } + + @Test + fun windowState_notStatusBarWindow_notUpdated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + + callback.setWindowState(DISPLAY_ID, WINDOW_NAVIGATION_BAR, WINDOW_STATE_SHOWING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + } + + @Test + fun windowState_showing_updated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Showing) + } + + @Test + fun windowState_hiding_updated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hiding) + } + + @Test + fun windowState_hidden_updated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + assertThat(latest).isEqualTo(StatusBarWindowState.Showing) + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + } +} + +private const val DISPLAY_ID = 10 diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 1e2648b228f3173879bc4139d8873fa121dcf99e..ecc7909a857a529523a47c43b652b6a99bce59c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -20,7 +20,6 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; -import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST; @@ -51,7 +50,6 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.SystemClock; -import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; @@ -285,23 +283,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() { - // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows - mDialog.addSliderHapticsToRows(); - - // WHEN haptics try to be delivered to a volume stream - boolean canDeliverHaptics = - mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50); - - // THEN the result is that haptics are not successfully delivered - assertFalse(canDeliverHaptics); - } - - @Test - @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() { - // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows + public void addSliderHaptics_canDeliverOnProgressChangedHaptics() { + // GIVEN that the slider haptics are added to rows mDialog.addSliderHapticsToRows(); // WHEN haptics try to be delivered to a volume stream diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 3e7980da87e3003f06f8cff2f2334a4646ca98d9..0d398348bda2d66b306d9a58a672036a46a2f974 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -24,6 +24,7 @@ import static android.service.notification.NotificationListenerService.NOTIFICAT import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED; import static androidx.test.ext.truth.content.IntentSubject.assertThat; @@ -1099,6 +1100,18 @@ public class BubblesTest extends SysuiTestCase { assertFalse(mBubbleController.hasBubbles()); } + @Test + public void testNotifsBanned_entryListenerRemove() { + mEntryListener.onEntryAdded(mRow); + mBubbleController.updateBubble(mBubbleEntry); + + assertTrue(mBubbleController.hasBubbles()); + + // Removes the notification + mEntryListener.onEntryRemoved(mRow, REASON_PACKAGE_BANNED); + assertFalse(mBubbleController.hasBubbles()); + } + @Test public void removeBubble_intercepted() { mEntryListener.onEntryAdded(mRow); diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt index ee36cadd848048329c101ba3196554390b086f97..de4bbecaaf0e982e2e4e08c279f41463df75290f 100644 --- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt +++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt @@ -84,7 +84,7 @@ class FakeInputManager { if (devices.containsKey(deviceId)) { return } - addPhysicalKeyboard(deviceId, enabled) + addPhysicalKeyboard(deviceId, enabled = enabled) } fun registerInputDeviceListener(listener: InputDeviceListener) { @@ -92,9 +92,15 @@ class FakeInputManager { inputDeviceListener = listener } - fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) { + fun addPhysicalKeyboard( + id: Int, + vendorId: Int = 0, + productId: Int = 0, + isFullKeyboard: Boolean = true, + enabled: Boolean = true + ) { check(id > 0) { "Physical keyboard ids have to be > 0" } - addKeyboard(id, enabled) + addKeyboard(id, vendorId, productId, isFullKeyboard, enabled) } fun removeKeysFromKeyboard(deviceId: Int, vararg keyCodes: Int) { @@ -102,20 +108,38 @@ class FakeInputManager { supportedKeyCodesByDeviceId[deviceId]!!.removeAll(keyCodes.asList()) } - private fun addKeyboard(id: Int, enabled: Boolean = true) { - devices[id] = + private fun addKeyboard( + id: Int, + vendorId: Int = 0, + productId: Int = 0, + isFullKeyboard: Boolean = true, + enabled: Boolean = true + ) { + val keyboardType = + if (isFullKeyboard) InputDevice.KEYBOARD_TYPE_ALPHABETIC + else InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC + // VendorId and productId are set to 0 if not specified, which is the same as the default + // values used in InputDevice.Builder + val builder = InputDevice.Builder() .setId(id) - .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC) + .setVendorId(vendorId) + .setProductId(productId) + .setKeyboardType(keyboardType) .setSources(InputDevice.SOURCE_KEYBOARD) .setEnabled(enabled) .setKeyCharacterMap(keyCharacterMap) - .build() + devices[id] = builder.build() + inputDeviceListener?.onInputDeviceAdded(id) supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet() } - fun addDevice(id: Int, sources: Int) { - devices[id] = InputDevice.Builder().setId(id).setSources(sources).build() + fun addDevice(id: Int, sources: Int, isNotFound: Boolean = false) { + // there's not way of differentiate device connection vs registry in current implementation. + // If the device isNotFound, it means that we connect an unregistered device. + if (!isNotFound) { + devices[id] = InputDevice.Builder().setId(id).setSources(sources).build() + } inputDeviceListener?.onInputDeviceAdded(id) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt index 649e4e8a6f7e51ddb7f36c36f7cc64eef522b6c1..1b1d8c5d0f630a16d1046436705297ca6ded0fa8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt @@ -25,6 +25,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer +import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -34,9 +36,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.StateFlow val Kosmos.bouncerUserActionsViewModel by Fixture { - BouncerUserActionsViewModel( - bouncerInteractor = bouncerInteractor, - ) + BouncerUserActionsViewModel(bouncerInteractor = bouncerInteractor) } val Kosmos.bouncerUserActionsViewModelFactory by Fixture { @@ -59,6 +59,7 @@ val Kosmos.bouncerSceneContentViewModel by Fixture { pinViewModelFactory = pinBouncerViewModelFactory, patternViewModelFactory = patternBouncerViewModelFactory, passwordViewModelFactory = passwordBouncerViewModelFactory, + bouncerHapticPlayer = bouncerHapticPlayer, ) } @@ -76,6 +77,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture { isInputEnabled: StateFlow, onIntentionalUserInput: () -> Unit, authenticationMethod: AuthenticationMethodModel, + bouncerHapticPlayer: BouncerHapticPlayer, ): PinBouncerViewModel { return PinBouncerViewModel( applicationContext = applicationContext, @@ -84,6 +86,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture { isInputEnabled = isInputEnabled, onIntentionalUserInput = onIntentionalUserInput, authenticationMethod = authenticationMethod, + bouncerHapticPlayer = bouncerHapticPlayer, ) } } @@ -92,6 +95,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture { val Kosmos.patternBouncerViewModelFactory by Fixture { object : PatternBouncerViewModel.Factory { override fun create( + bouncerHapticPlayer: BouncerHapticPlayer, isInputEnabled: StateFlow, onIntentionalUserInput: () -> Unit, ): PatternBouncerViewModel { @@ -100,6 +104,7 @@ val Kosmos.patternBouncerViewModelFactory by Fixture { interactor = bouncerInteractor, isInputEnabled = isInputEnabled, onIntentionalUserInput = onIntentionalUserInput, + bouncerHapticPlayer = bouncerHapticPlayer, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt index 81242244b7a6f38bb7009e1d9393c6bc4dc06a19..3d4136252ca445aa5f2f1e61f85e65349516c683 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt @@ -16,9 +16,11 @@ package com.android.systemui.communal.domain.interactor +import android.service.dream.dreamManager import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter import com.android.systemui.shared.system.taskStackChangeListeners @@ -32,6 +34,8 @@ val Kosmos.widgetTrampolineInteractor: WidgetTrampolineInteractor by keyguardTransitionInteractor = keyguardTransitionInteractor, taskStackChangeListeners = taskStackChangeListeners, usageStatsInteractor = usageStatsInteractor, + dreamManager = dreamManager, + bgScope = applicationCoroutineScope, logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt index 1ed10fbe94c3cb3e2f5cdfc180b71c72c6b41a44..8922b2f5c5eff7b4f2f16ad51adf374372d72cba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.deviceentry.data.repository.deviceEntryRepository import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.flags.systemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.trustInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -37,5 +38,6 @@ val Kosmos.deviceUnlockedInteractor by Fixture { powerInteractor = powerInteractor, biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, systemPropertiesHelper = fakeSystemPropertiesHelper, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt index 1d2bce2f9b997dd24e66560ad3cd485407cd740f..1df3ef48d5a7b310c8c36dc6e5bbb8f71fd4e79c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt @@ -19,7 +19,7 @@ package com.android.systemui.education.data.repository import com.android.systemui.kosmos.Kosmos import java.time.Instant -var Kosmos.contextualEducationRepository: ContextualEducationRepository by +var Kosmos.contextualEducationRepository: FakeContextualEducationRepository by Kosmos.Fixture { FakeContextualEducationRepository() } var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt index fb4e2fb5fd14a5ad4e8f937c817d6dd503df2aa1..4667bf5292fa3ec9a274139b30109f5a1d9fe941 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt @@ -17,39 +17,77 @@ package com.android.systemui.education.data.repository import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS +import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.education.data.model.EduDeviceConnectionTime import com.android.systemui.education.data.model.GestureEduModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull class FakeContextualEducationRepository : ContextualEducationRepository { - private val userGestureMap = mutableMapOf() - private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0)) - private val gestureEduModelsFlow = _gestureEduModels.asStateFlow() + private val userGestureMap = mutableMapOf>() + + private val _backGestureEduModels = MutableStateFlow(GestureEduModel(BACK, userId = 0)) + private val backGestureEduModelsFlow = _backGestureEduModels.asStateFlow() + + private val _homeGestureEduModels = MutableStateFlow(GestureEduModel(HOME, userId = 0)) + private val homeEduModelsFlow = _homeGestureEduModels.asStateFlow() + + private val _allAppsGestureEduModels = MutableStateFlow(GestureEduModel(ALL_APPS, userId = 0)) + private val allAppsGestureEduModels = _allAppsGestureEduModels.asStateFlow() + + private val _overviewsGestureEduModels = MutableStateFlow(GestureEduModel(OVERVIEW, userId = 0)) + private val overviewsGestureEduModels = _overviewsGestureEduModels.asStateFlow() private val userEduDeviceConnectionTimeMap = mutableMapOf() private val _eduDeviceConnectionTime = MutableStateFlow(EduDeviceConnectionTime()) private val eduDeviceConnectionTime = _eduDeviceConnectionTime.asStateFlow() + private val _keyboardShortcutTriggered = MutableStateFlow(null) + private var currentUser: Int = 0 override fun setUser(userId: Int) { if (!userGestureMap.contains(userId)) { - userGestureMap[userId] = GestureEduModel(userId = userId) + userGestureMap[userId] = createGestureEduModelMap(userId = userId) userEduDeviceConnectionTimeMap[userId] = EduDeviceConnectionTime() } // save data of current user to the map - userGestureMap[currentUser] = _gestureEduModels.value - userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value + val currentUserMap = userGestureMap[currentUser]!! + currentUserMap[BACK] = _backGestureEduModels.value + currentUserMap[HOME] = _homeGestureEduModels.value + currentUserMap[ALL_APPS] = _allAppsGestureEduModels.value + currentUserMap[OVERVIEW] = _overviewsGestureEduModels.value + // switch to data of new user - _gestureEduModels.value = userGestureMap[userId]!! + val newUserGestureMap = userGestureMap[userId]!! + newUserGestureMap[BACK]?.let { _backGestureEduModels.value = it } + newUserGestureMap[HOME]?.let { _homeGestureEduModels.value = it } + newUserGestureMap[ALL_APPS]?.let { _allAppsGestureEduModels.value = it } + newUserGestureMap[OVERVIEW]?.let { _overviewsGestureEduModels.value = it } + + userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value _eduDeviceConnectionTime.value = userEduDeviceConnectionTimeMap[userId]!! } + private fun createGestureEduModelMap(userId: Int): MutableMap { + val gestureModelMap = mutableMapOf() + GestureType.values().forEach { gestureModelMap[it] = GestureEduModel(it, userId = userId) } + return gestureModelMap + } + override fun readGestureEduModelFlow(gestureType: GestureType): Flow { - return gestureEduModelsFlow + return when (gestureType) { + BACK -> backGestureEduModelsFlow + HOME -> homeEduModelsFlow + ALL_APPS -> allAppsGestureEduModels + OVERVIEW -> overviewsGestureEduModels + } } override fun readEduDeviceConnectionTime(): Flow { @@ -60,8 +98,16 @@ class FakeContextualEducationRepository : ContextualEducationRepository { gestureType: GestureType, transform: (GestureEduModel) -> GestureEduModel ) { - val currentModel = _gestureEduModels.value - _gestureEduModels.value = transform(currentModel) + val gestureModels = + when (gestureType) { + BACK -> _backGestureEduModels + HOME -> _homeGestureEduModels + ALL_APPS -> _allAppsGestureEduModels + OVERVIEW -> _overviewsGestureEduModels + } + + val currentModel = gestureModels.value + gestureModels.value = transform(currentModel) } override suspend fun updateEduDeviceConnectionTime( @@ -70,4 +116,11 @@ class FakeContextualEducationRepository : ContextualEducationRepository { val currentModel = _eduDeviceConnectionTime.value _eduDeviceConnectionTime.value = transform(currentModel) } + + override val keyboardShortcutTriggered: Flow + get() = _keyboardShortcutTriggered.filterNotNull() + + fun setKeyboardShortcutTriggered(gestureType: GestureType) { + _keyboardShortcutTriggered.value = gestureType + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt index 80f6fc24ef2c96e3f7f8c151a6d0e3a4ff912587..2d275f9e96914009b6bb9d33bd56e6a0e6452111 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt @@ -40,8 +40,7 @@ var Kosmos.keyboardTouchpadEduInteractor by touchpadRepository, userRepository ), - clock = fakeEduClock, - inputManager = mockEduInputManager + clock = fakeEduClock ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt index c252924f4d2d87e0dc339a47cf1f050b50c6ef40..c0152b26d7a3d72ebab5def7b4cbcaa6508beef0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import android.platform.test.annotations.EnableFlags -import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR @@ -31,7 +30,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER * that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites. */ @EnableFlags( - FLAG_COMPOSE_LOCKSCREEN, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt index 5ad973a542526a507eb51de1d8fcf9857dafba93..2b81da33b9bc78f269f437c74d02a3a8f65195ac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt @@ -20,8 +20,10 @@ import com.google.android.msdl.data.model.FeedbackLevel import com.google.android.msdl.data.model.MSDLToken import com.google.android.msdl.domain.InteractionProperties import com.google.android.msdl.domain.MSDLPlayer +import com.google.android.msdl.logging.MSDLEvent class FakeMSDLPlayer : MSDLPlayer { + private val history = arrayListOf() var currentFeedbackLevel = FeedbackLevel.DEFAULT var latestTokenPlayed: MSDLToken? = null private set @@ -34,5 +36,8 @@ class FakeMSDLPlayer : MSDLPlayer { override fun playToken(token: MSDLToken, properties: InteractionProperties?) { latestTokenPlayed = token latestPropertiesPlayed = properties + history.add(MSDLEvent(token, properties)) } + + override fun getHistory(): List = history } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt index ca748b661d044775ed79b5eee15417c017f26544..80db1e9b5e2940878b6cad26f7410718aa37fa57 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.haptics.qs +import com.android.systemui.classifier.fakeFalsingManager import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.Kosmos import com.android.systemui.log.core.FakeLogBuffer @@ -26,6 +27,7 @@ val Kosmos.qsLongPressEffect by QSLongPressEffect( vibratorHelper, keyguardStateController, + fakeFalsingManager, FakeLogBuffer.Factory.create(), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt index 574bbcd6106c6124cf5799c50dcffc0409b5475f..e2b283b0656203b95b6d6b3e3b2db9eca1342918 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt @@ -39,5 +39,6 @@ val Kosmos.keyguardDismissActionInteractor by powerInteractor = powerInteractor, alternateBouncerInteractor = alternateBouncerInteractor, shadeInteractor = { shadeInteractor }, + keyguardInteractor = { keyguardInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt index ace11573c7c6ab06d3b6b0cb3e66c7f6c2490860..52416bae0d9de7c1a13d78e93e69afb18e979fce 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt @@ -41,5 +41,6 @@ val Kosmos.keyguardDismissInteractor by trustRepository = trustRepository, alternateBouncerInteractor = alternateBouncerInteractor, powerInteractor = powerInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt index 6e650a3c5391a86b502bff57f5cd4bfebf24b473..77afa7989e83cac28c6889f86a4f6444a9bb87a4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.bluetooth.mockBroadcastDialogController import com.android.systemui.kosmos.Kosmos import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor +import com.android.systemui.media.controls.shared.mediaLogger import com.android.systemui.media.controls.util.mediaInstanceId import com.android.systemui.media.mediaOutputDialogManager import com.android.systemui.plugins.activityStarter @@ -39,5 +40,6 @@ val Kosmos.mediaControlInteractor by lockscreenUserManager = notificationLockscreenUserManager, mediaOutputDialogManager = mediaOutputDialogManager, broadcastDialogController = mockBroadcastDialogController, + mediaLogger = mediaLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt index e490b7502894d9cd39f929e78e54a895371bd634..9ea660fd370456429569b333599f5bf8d1e72c93 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor +import com.android.systemui.media.controls.shared.mediaLogger import com.android.systemui.media.mediaOutputDialogManager import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.notificationLockscreenUserManager @@ -42,6 +43,7 @@ val Kosmos.mediaControlInteractorFactory by lockscreenUserManager = notificationLockscreenUserManager, mediaOutputDialogManager = mediaOutputDialogManager, broadcastDialogController = mockBroadcastDialogController, + mediaLogger = mediaLogger, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt deleted file mode 100644 index edbc4c1cd65ef8d59caedd636745374c89f2fc1e..0000000000000000000000000000000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt +++ /dev/null @@ -1,34 +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.systemui.qs.panels.domain.interactor - -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.log.core.FakeLogBuffer -import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor - -val Kosmos.gridConsistencyInteractor by - Kosmos.Fixture { - GridConsistencyInteractor( - gridLayoutTypeInteractor, - currentTilesInteractor, - gridConsistencyInteractorsMap, - noopGridConsistencyInteractor, - FakeLogBuffer.Factory.create(), - applicationCoroutineScope, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt index 34e99d3a9a3c9f5c05adaf0afcd0be2721486b65..c9516429553b1932ef05e7de3191ad9e3e666d56 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt @@ -27,6 +27,3 @@ val Kosmos.gridLayoutTypeInteractor by val Kosmos.gridLayoutMap: Map by Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) } - -var Kosmos.gridConsistencyInteractorsMap: Map by - Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt index be00152cb51103f04185777c308658ab1d01b373..3f62b4d9f9cb7984ca6e46bf02d84bc2c61e890e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt index f3d5b7d77669ed69d9b81244c3a767651b7cf28b..cd76a749cc9ac0df4413fe5ad9bc6024014d546c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -33,6 +33,7 @@ class FakeUserTracker( private var _userHandle: UserHandle = UserHandle.of(_userId), private var _userInfo: UserInfo = mock(), private var _userProfiles: List = emptyList(), + private var _isUserSwitching: Boolean = false, userContentResolverProvider: () -> ContentResolver = { MockContentResolver() }, userContext: Context = mock(), private val onCreateCurrentUserContext: (Context) -> Context = { mock() }, @@ -51,6 +52,9 @@ class FakeUserTracker( override val userProfiles: List get() = _userProfiles + override val isUserSwitching: Boolean + get() = _isUserSwitching + // userContentResolver is lazy because Ravenwood doesn't support MockContentResolver() // and we still want to allow people use this class for tests that don't use it. override val userContentResolver: ContentResolver by lazy { userContentResolverProvider() } @@ -86,11 +90,13 @@ class FakeUserTracker( } fun onUserChanging(userId: Int = _userId) { + _isUserSwitching = true val copy = callbacks.toList() copy.forEach { it.onUserChanging(userId, userContext) {} } } fun onUserChanged(userId: Int = _userId) { + _isUserSwitching = false val copy = callbacks.toList() copy.forEach { it.onUserChanged(userId, userContext) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt index c416ea1c1b3950a2f7878b161656c55e08d6dbe5..91602c23ac17ec7b2a9c5d16dae1026894db01de 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt @@ -16,8 +16,13 @@ package com.android.systemui.statusbar.data.repository +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf class FakeRemoteInputRepository : RemoteInputRepository { override val isRemoteInputActive = MutableStateFlow(false) + override val remoteInputRowBottomBound: Flow = flowOf(null) + + override fun setRemoteInputRowBottomBound(bottom: Float?) {} } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt index 6370a5d9c80b538b3d167a4db5e44969d58ece61..7244d465ed7e6d3559382746712b6a8c62cc1d4e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor val Kosmos.notificationScrollViewModel by Fixture { @@ -29,6 +30,7 @@ val Kosmos.notificationScrollViewModel by Fixture { dumpManager = dumpManager, stackAppearanceInteractor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, + remoteInputInteractor = remoteInputInteractor, sceneInteractor = sceneInteractor, keyguardInteractor = { keyguardInteractor }, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index 8bfc390ecfa3ed48a00fcab24251f953642e94a7..e5cf0a90ebbd11613a720d5ebd6efb8f452e6dd6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor @@ -31,6 +32,7 @@ val Kosmos.notificationsPlaceholderViewModel by Fixture { sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, headsUpNotificationInteractor = headsUpNotificationInteractor, + remoteInputInteractor = remoteInputInteractor, featureFlags = featureFlagsClassic, dumpManager = dumpManager, ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt similarity index 60% rename from packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt index 4cdabaedc49e4fd34989364ce46c010fa0a87ccc..385a813996ff7eab86ce18c0b7da8c1d2f5da2d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt @@ -14,14 +14,10 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.domain.interactor +package com.android.systemui.statusbar.pipeline.shared.domain.interactor -import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository -interface GridTypeConsistencyInteractor { - /** - * Given a list of tiles, return the best list of the same tiles (preserving as much order as - * possible, such that it's consistent with the current layout. - */ - fun reconcileTiles(tiles: List): List -} +val Kosmos.collapsedStatusBarInteractor: CollapsedStatusBarInteractor by + Kosmos.Fixture { CollapsedStatusBarInteractor(fakeDisableFlagsRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt new file mode 100644 index 0000000000000000000000000000000000000000..1c7fd4817498876ffd5149a4b3a9263f1303d556 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor +import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor + +val Kosmos.collapsedStatusBarViewModel: CollapsedStatusBarViewModel by + Kosmos.Fixture { + CollapsedStatusBarViewModelImpl( + collapsedStatusBarInteractor, + lightsOutInteractor, + activeNotificationsInteractor, + keyguardTransitionInteractor, + sceneInteractor, + sceneContainerOcclusionInteractor, + shadeInteractor, + ongoingActivityChipsViewModel, + applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt index 61b53c9a206770a301dad2e1d4d2ad197da3237c..99cd8309631edb9a2d8e49b455c1d83e45383de8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt @@ -22,6 +22,8 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository +import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository +import com.android.systemui.statusbar.policy.data.repository.userSetupRepository import com.android.systemui.statusbar.policy.data.repository.zenModeRepository val Kosmos.zenModeInteractor by Fixture { @@ -31,5 +33,7 @@ val Kosmos.zenModeInteractor by Fixture { notificationSettingsRepository = notificationSettingsRepository, bgDispatcher = testDispatcher, iconLoader = zenIconLoader, + deviceProvisioningRepository = deviceProvisioningRepository, + userSetupRepository = userSetupRepository, ) } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index a10097427ae5dbf03f14d83a05b638951878c9cc..57b58d8741da9b64d60e87970d2879ef98021dcf 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -128,7 +128,11 @@ constructor( val currentDirection = if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING - if (isTransitionInProgress && currentDirection != lastFoldUpdate) { + val changedDirectionWhileInTransition = + isTransitionInProgress && currentDirection != lastFoldUpdate + val unfoldedPastThresholdSinceLastTransition = + angle - lastHingeAngleBeforeTransition > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES + if (changedDirectionWhileInTransition || unfoldedPastThresholdSinceLastTransition) { lastHingeAngleBeforeTransition = lastHingeAngle } @@ -153,7 +157,7 @@ constructor( isOnLargeScreen // Avoids sending closing event when on small screen. // Start event is sent regardless due to hall sensor. ) { - notifyFoldUpdate(transitionUpdate, lastHingeAngle) + notifyFoldUpdate(transitionUpdate, angle) } if (isTransitionInProgress) { diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index acb74d30689c9b2f94af1bb3d50f73d7c62a81e3..75d07bb80c05a0d5b613d8c47360730b89b414a9 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -529,167 +529,6 @@ public class CameraExtensionsProxyService extends Service { */ public static Pair initializeExtension( int extensionType) { - if (Flags.concertModeApi()) { - if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { - // Basic extensions are deprecated starting with extension version 1.5 - return new Pair<>(new PreviewExtenderImpl() { - @Override - public boolean isExtensionAvailable(String cameraId, - CameraCharacteristics cameraCharacteristics) { - return false; - } - - @Override - public void init(String cameraId, CameraCharacteristics cameraCharacteristics) { - - } - - @Override - public androidx.camera.extensions.impl.CaptureStageImpl getCaptureStage() { - return null; - } - - @Override - public ProcessorType getProcessorType() { - return null; - } - - @Override - public ProcessorImpl getProcessor() { - return null; - } - - @Nullable - @Override - public List> getSupportedResolutions() { - return null; - } - - @Override - public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics, - Context context) { } - - @Override - public void onDeInit() { } - - @Override - public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() { - return null; - } - - @Override - public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() { - return null; - } - - @Override - public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() { - return null; - } - - @Override - public int onSessionType() { - return 0; - } - }, new ImageCaptureExtenderImpl() { - @Override - public boolean isExtensionAvailable(String cameraId, - CameraCharacteristics cameraCharacteristics) { - return false; - } - - @Override - public void init(String cameraId, - CameraCharacteristics cameraCharacteristics) { } - - @Override - public CaptureProcessorImpl getCaptureProcessor() { - return null; - } - - @Override - public - List getCaptureStages() { - return null; - } - - @Override - public int getMaxCaptureStage() { - return 0; - } - - @Override - public List> getSupportedResolutions() { - return null; - } - - @Override - public List> getSupportedPostviewResolutions( - Size captureSize) { - return null; - } - - @Override - public Range getEstimatedCaptureLatencyRange( - Size captureOutputSize) { - return null; - } - - @Override - public List getAvailableCaptureRequestKeys() { - return null; - } - - @Override - public List getAvailableCaptureResultKeys() { - return null; - } - - @Override - public boolean isCaptureProcessProgressAvailable() { - return false; - } - - @Override - public Pair getRealtimeCaptureLatency() { - return null; - } - - @Override - public boolean isPostviewAvailable() { - return false; - } - - @Override - public void onInit(String cameraId, - CameraCharacteristics cameraCharacteristics, Context context) { } - - @Override - public void onDeInit() { } - - @Override - public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() { - return null; - } - - @Override - public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() { - return null; - } - - @Override - public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() { - return null; - } - - @Override - public int onSessionType() { - return 0; - } - }); - } - } - switch (extensionType) { case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC: return new Pair<>(new AutoPreviewExtenderImpl(), @@ -714,88 +553,6 @@ public class CameraExtensionsProxyService extends Service { * @hide */ public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) { - if (Flags.concertModeApi()) { - if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { - if (EFV_SUPPORTED) { - return new EyesFreeVideographyAdvancedExtenderImpl(); - } else { - return new AdvancedExtenderImpl() { - @Override - public boolean isExtensionAvailable(String cameraId, - Map characteristicsMap) { - return false; - } - - @Override - public void init(String cameraId, - Map characteristicsMap) { - - } - - @Override - public Range getEstimatedCaptureLatencyRange(String cameraId, - Size captureOutputSize, int imageFormat) { - return null; - } - - @Override - public Map> getSupportedPreviewOutputResolutions( - String cameraId) { - return null; - } - - @Override - public Map> getSupportedCaptureOutputResolutions( - String cameraId) { - return null; - } - - @Override - public Map> getSupportedPostviewResolutions( - Size captureSize) { - return null; - } - - @Override - public List getSupportedYuvAnalysisResolutions(String cameraId) { - return null; - } - - @Override - public SessionProcessorImpl createSessionProcessor() { - return null; - } - - @Override - public List getAvailableCaptureRequestKeys() { - return null; - } - - @Override - public List getAvailableCaptureResultKeys() { - return null; - } - - @Override - public boolean isCaptureProcessProgressAvailable() { - return false; - } - - @Override - public boolean isPostviewAvailable() { - return false; - } - - @Override - public List> - getAvailableCharacteristicsKeyValues() { - return Collections.emptyList(); - } - }; - } - } - } - switch (extensionType) { case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC: return new AutoAdvancedExtenderImpl(); @@ -1280,14 +1037,12 @@ public class CameraExtensionsProxyService extends Service { @Override public void onCaptureFailed(int captureSequenceId, int reason) { - if (Flags.concertMode()) { - if (mCaptureCallback != null) { - try { - mCaptureCallback.onCaptureProcessFailed(captureSequenceId, reason); - } catch (RemoteException e) { - Log.e(TAG, "Failed to notify capture failure due to remote " + - "exception!"); - } + if (mCaptureCallback != null) { + try { + mCaptureCallback.onCaptureProcessFailed(captureSequenceId, reason); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture failure due to remote " + + "exception!"); } } } @@ -1643,16 +1398,14 @@ public class CameraExtensionsProxyService extends Service { CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this); mSessionProcessor.deInitSession(); - if (Flags.surfaceLeakFix()) { - if (mOutputImageCaptureSurfaceImpl.mSurface != null) { - mOutputImageCaptureSurfaceImpl.mSurface.release(); - } - if (mOutputPreviewSurfaceImpl.mSurface != null) { - mOutputPreviewSurfaceImpl.mSurface.release(); - } - if (mOutputPostviewSurfaceImpl.mSurface != null) { - mOutputPostviewSurfaceImpl.mSurface.release(); - } + if (mOutputImageCaptureSurfaceImpl.mSurface != null) { + mOutputImageCaptureSurfaceImpl.mSurface.release(); + } + if (mOutputPreviewSurfaceImpl.mSurface != null) { + mOutputPreviewSurfaceImpl.mSurface.release(); + } + if (mOutputPostviewSurfaceImpl.mSurface != null) { + mOutputPostviewSurfaceImpl.mSurface.release(); } } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index d1a3bf9b529f996852d1f24c49a66b82e15d7803..10e4f3820cd7fad9e693524af3e2d6d8a973211b 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -343,6 +343,8 @@ android_ravenwood_libgroup { data: [ ":framework-res", ":ravenwood-empty-res", + ":framework-platform-compat-config", + ":services-platform-compat-config", ], libs: [ "100-framework-minus-apex.ravenwood", diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING index f9b5d2c6282845f7c6fc075dac4216048e155f38..d4d188ddf692a85d14576f6a4b9dc6f1cda4114a 100644 --- a/ravenwood/TEST_MAPPING +++ b/ravenwood/TEST_MAPPING @@ -12,28 +12,12 @@ // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING { - "name": "SystemUIGoogleTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "SystemUIGoogleTests" } ], "presubmit-large": [ { - "name": "SystemUITests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "SystemUITests" } ], "ravenwood-presubmit": [ @@ -175,11 +159,13 @@ { "name": "RavenwoodServicesTest", "host": true - }, + } + // AUTO-GENERATED-END + ], + "ravenwood-postsubmit": [ { "name": "SystemUiRavenTests", "host": true } - // AUTO-GENERATED-END ] } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java index 6727327c99bec534ba1bce6111e181be94c3849c..b69c63748d81fd42c9baffdc5facc84ae5fae56d 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java @@ -35,4 +35,18 @@ import java.lang.annotation.Target; @Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) @Retention(RetentionPolicy.CLASS) public @interface RavenwoodRemove { + /** + * One or more classes that aren't yet supported by Ravenwood, which is why this method throws. + */ + Class[] blockedBy() default {}; + + /** + * General free-form description of why this method throws. + */ + String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java index 83a7b6e54389ac6d3bbd0e295a812ba2e9d52f80..57cdfd2240d03e15553aff93a1e63cb7df8c3fd8 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java @@ -42,4 +42,9 @@ public @interface RavenwoodReplace { * General free-form description of why this method is being replaced. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java index 0bb1f39cd453b7d20ccb0f7b8f401501ca79387b..19e6af1c478d83a6bbd9d4c8bf58bfaaca40e5a2 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java @@ -43,4 +43,9 @@ public @interface RavenwoodThrow { * General free-form description of why this method throws. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d473305688285e8557eccafb019d9c05a3bc3feb --- /dev/null +++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ravenwoodtest.bivalenttest; + +import static org.junit.Assert.assertNotNull; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test to make sure the environment is still initialized when no config and no rules are set. + */ +@RunWith(AndroidJUnit4.class) +public class RavenwoodNoConfigNoRuleTest { + + @Test + public void testInitialization() { + assertNotNull(InstrumentationRegistry.getInstrumentation()); + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java index 7a6f9e3669bb8ce00661a171cf65ce9a3b1ef81e..478bead1354f4f40f912518be274878c6714703e 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java @@ -15,8 +15,6 @@ */ package android.platform.test.ravenwood; -import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; - import static org.junit.Assert.fail; import android.os.Bundle; @@ -27,10 +25,6 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.os.RuntimeInit; -import com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook; -import com.android.ravenwood.common.RavenwoodCommonUtils; - import org.junit.runner.Description; import org.junit.runners.model.TestClass; @@ -49,15 +43,17 @@ public class RavenwoodAwareTestRunnerHook { private RavenwoodAwareTestRunnerHook() { } + /** + * Called before any code starts. Internally it will only initialize the environment once. + */ + public static void performGlobalInitialization() { + RavenwoodRuntimeEnvironmentController.globalInitOnce(); + } + /** * Called when a runner starts, before the inner runner gets a chance to run. */ public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) { - // TODO: Move the initialization code to a better place. - - initOnce(); - - // This log call also ensures the framework JNI is loaded. Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass() + " runner=" + runner); @@ -65,33 +61,6 @@ public class RavenwoodAwareTestRunnerHook { InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); } - private static boolean sInitialized = false; - - private static void initOnce() { - if (sInitialized) { - return; - } - sInitialized = true; - - // We haven't initialized liblog yet, so directly write to System.out here. - RavenwoodCommonUtils.log(TAG, "initOnce()"); - - // Make sure libandroid_runtime is loaded. - ClassLoadHook.onClassLoaded(Log.class); - - // Redirect stdout/stdin to liblog. - RuntimeInit.redirectLogStreams(); - - // This will let AndroidJUnit4 use the original runner. - System.setProperty("android.junit.runner", - "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); - System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); - - // Do the basic set up for the android sysprops. - RavenwoodRuntimeEnvironmentController.setSystemProperties( - RavenwoodSystemProperties.DEFAULT_VALUES); - } - /** * Called when a whole test class is skipped. */ @@ -227,4 +196,4 @@ public class RavenwoodAwareTestRunnerHook { runner.mState.exitRavenwoodRule(rule); } -} \ No newline at end of file +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..e5486117e7f288409c2466ef0213dafdb6fd4c4d --- /dev/null +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java @@ -0,0 +1,134 @@ +/* + * 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 android.platform.test.ravenwood; + +import com.android.ravenwood.common.RavenwoodCommonUtils; + +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * We use this class to load libandroid_runtime. + * In the future, we may load other native libraries. + */ +public final class RavenwoodNativeLoader { + public static final String CORE_NATIVE_CLASSES = "core_native_classes"; + public static final String ICU_DATA_PATH = "icu.data.path"; + public static final String KEYBOARD_PATHS = "keyboard_paths"; + public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes"; + + public static final String LIBANDROID_RUNTIME_NAME = "android_runtime"; + + /** + * Classes with native methods that are backed by libandroid_runtime. + * + * See frameworks/base/core/jni/platform/host/HostRuntime.cpp + */ + private static final Class[] sLibandroidClasses = { + android.util.Log.class, + android.os.Parcel.class, + android.os.Binder.class, + android.content.res.ApkAssets.class, + android.content.res.AssetManager.class, + android.content.res.StringBlock.class, + android.content.res.XmlBlock.class, + }; + + /** + * Classes with native methods that are backed by libhwui. + * + * See frameworks/base/libs/hwui/apex/LayoutlibLoader.cpp + */ + private static final Class[] sLibhwuiClasses = { + android.graphics.Interpolator.class, + android.graphics.Matrix.class, + android.graphics.Path.class, + android.graphics.Color.class, + android.graphics.ColorSpace.class, + }; + + /** + * Extra strings needed to pass to register_android_graphics_classes(). + * + * `android.graphics.Graphics` is not actually a class, so we just hardcode it here. + */ + public final static String[] GRAPHICS_EXTRA_INIT_PARAMS = new String[] { + "android.graphics.Graphics" + }; + + private RavenwoodNativeLoader() { + } + + private static void log(String message) { + System.out.println("RavenwoodNativeLoader: " + message); + } + + private static void log(String fmt, Object... args) { + log(String.format(fmt, args)); + } + + private static void ensurePropertyNotSet(String key) { + if (System.getProperty(key) != null) { + throw new RuntimeException("System property \"" + key + "\" is set unexpectedly"); + } + } + + private static void setProperty(String key, String value) { + System.setProperty(key, value); + log("Property set: %s=\"%s\"", key, value); + } + + private static void dumpSystemProperties() { + for (var prop : System.getProperties().entrySet()) { + log(" %s=\"%s\"", prop.getKey(), prop.getValue()); + } + } + + /** + * libandroid_runtime uses Java's system properties to decide what JNI methods to set up. + * Set up these properties and load the native library + */ + public static void loadFrameworkNativeCode() { + if ("1".equals(System.getenv("RAVENWOOD_DUMP_PROPERTIES"))) { + log("Java system properties:"); + dumpSystemProperties(); + } + + // Make sure these properties are not set. + ensurePropertyNotSet(CORE_NATIVE_CLASSES); + ensurePropertyNotSet(ICU_DATA_PATH); + ensurePropertyNotSet(KEYBOARD_PATHS); + ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES); + + // Build the property values + final var joiner = Collectors.joining(","); + final var libandroidClasses = + Arrays.stream(sLibandroidClasses).map(Class::getName).collect(joiner); + final var libhwuiClasses = Stream.concat( + Arrays.stream(sLibhwuiClasses).map(Class::getName), + Arrays.stream(GRAPHICS_EXTRA_INIT_PARAMS) + ).collect(joiner); + + // Load the libraries + setProperty(CORE_NATIVE_CLASSES, libandroidClasses); + setProperty(GRAPHICS_NATIVE_CLASSES, libhwuiClasses); + log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libandroidClasses + "' and '" + + libhwuiClasses + "'"); + RavenwoodCommonUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME); + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java index 04b67c45ad8e66a316a6383a85eaf71cf2ebd5d7..03513ab0a2af814eb93d6859d3e93bbd65c7dcb7 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java @@ -63,6 +63,7 @@ public final class RavenwoodRunnerState { private RavenwoodConfig mCurrentConfig; private RavenwoodRule mCurrentRule; + private boolean mHasRavenwoodRule; public Description getClassDescription() { return mClassDescription; @@ -71,6 +72,7 @@ public final class RavenwoodRunnerState { public void enterTestClass(Description classDescription) throws IOException { mClassDescription = classDescription; + mHasRavenwoodRule = hasRavenwoodRule(mRunner.getTestClass().getJavaClass()); mCurrentConfig = extractConfiguration(mRunner.getTestClass().getJavaClass()); if (mCurrentConfig != null) { @@ -97,9 +99,13 @@ public final class RavenwoodRunnerState { } public void enterRavenwoodRule(RavenwoodRule rule) throws IOException { + if (!mHasRavenwoodRule) { + fail("If you have a RavenwoodRule in your test, make sure the field type is" + + " RavenwoodRule so Ravenwood can detect it."); + } if (mCurrentConfig != null) { - fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class." - + " Suggest migrating to RavenwoodConfiguration."); + fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class." + + " Suggest migrating to RavenwoodConfig."); } if (mCurrentRule != null) { fail("Multiple nesting RavenwoodRule's are detected in the same class," @@ -125,16 +131,20 @@ public final class RavenwoodRunnerState { * @return a configuration from a test class, if any. */ @Nullable - private static RavenwoodConfig extractConfiguration(Class testClass) { - final boolean hasRavenwoodRule = hasRavenwoodRule(testClass); - + private RavenwoodConfig extractConfiguration(Class testClass) { var field = findConfigurationField(testClass); if (field == null) { - return null; + if (mHasRavenwoodRule) { + // Should be handled by RavenwoodRule + return null; + } + + // If no RavenwoodConfig and no RavenwoodRule, return a default config + return new RavenwoodConfig.Builder().build(); } - if (hasRavenwoodRule) { - fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class." - + " Suggest migrating to RavenwoodConfiguration."); + if (mHasRavenwoodRule) { + fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class." + + " Suggest migrating to RavenwoodConfig."); } try { @@ -155,7 +165,7 @@ public final class RavenwoodRunnerState { private static boolean hasRavenwoodRule(Class testClass) { for (var field : testClass.getDeclaredFields()) { if (!field.isAnnotationPresent(Rule.class) - && field.isAnnotationPresent(ClassRule.class)) { + && !field.isAnnotationPresent(ClassRule.class)) { continue; } if (field.getType().equals(RavenwoodRule.class)) { @@ -165,7 +175,7 @@ public final class RavenwoodRunnerState { // JUnit supports rules as methods, so we need to check them too. for (var method : testClass.getDeclaredMethods()) { if (!method.isAnnotationPresent(Rule.class) - && method.isAnnotationPresent(ClassRule.class)) { + && !method.isAnnotationPresent(ClassRule.class)) { continue; } if (method.getReturnType().equals(RavenwoodRule.class)) { @@ -180,8 +190,8 @@ public final class RavenwoodRunnerState { } /** - * Find and return a field with @RavenwoodConfiguration.Config, which must be of type - * RavenwoodConfiguration. + * Find and return a field with @RavenwoodConfig.Config, which must be of type + * RavenwoodConfig. */ @Nullable private static Field findConfigurationField(Class testClass) { @@ -198,7 +208,7 @@ public final class RavenwoodRunnerState { fail(String.format( "Class %s has multiple fields with %s", testClass.getCanonicalName(), - "@RavenwoodConfiguration.Config")); + "@RavenwoodConfig.Config")); } // Make sure it's static public ensureIsPublicMember(field, true); @@ -209,8 +219,8 @@ public final class RavenwoodRunnerState { "Field %s.%s has %s but type is not %s", testClass.getCanonicalName(), field.getName(), - "@RavenwoodConfiguration.Config", - "RavenwoodConfiguration")); + "@RavenwoodConfig.Config", + "RavenwoodConfig")); return null; // unreachable } } else { @@ -219,8 +229,8 @@ public final class RavenwoodRunnerState { "Field %s.%s does not have %s but type is %s", testClass.getCanonicalName(), field.getName(), - "@RavenwoodConfiguration.Config", - "RavenwoodConfiguration")); + "@RavenwoodConfig.Config", + "RavenwoodConfig")); return null; // unreachable } else { // Unrelated field, ignore. diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index f4756c5c58b3bc6c92d2062d6d488ceba7c0d12a..90bb93de3bc4a2d2f2ed0fa1f79bb4d22ed65e73 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -16,10 +16,10 @@ package android.platform.test.ravenwood; +import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; - -import static org.junit.Assert.fail; +import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; import android.app.ActivityManager; import android.app.Instrumentation; @@ -31,10 +31,14 @@ import android.os.Bundle; import android.os.HandlerThread; import android.os.Looper; import android.os.ServiceManager; +import android.system.ErrnoException; +import android.system.Os; import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.os.RuntimeInit; +import com.android.ravenwood.common.RavenwoodCommonUtils; import com.android.ravenwood.common.RavenwoodRuntimeException; import com.android.ravenwood.common.SneakyThrow; import com.android.server.LocalServices; @@ -114,6 +118,43 @@ public class RavenwoodRuntimeEnvironmentController { } private static RavenwoodConfig sConfig; + private static boolean sInitialized = false; + + /** + * Initialize the global environment. + */ + public static void globalInitOnce() { + if (sInitialized) { + return; + } + sInitialized = true; + + // We haven't initialized liblog yet, so directly write to System.out here. + RavenwoodCommonUtils.log(TAG, "globalInit()"); + + // Do the basic set up for the android sysprops. + setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES); + + // Make sure libandroid_runtime is loaded. + RavenwoodNativeLoader.loadFrameworkNativeCode(); + + // Redirect stdout/stdin to liblog. + RuntimeInit.redirectLogStreams(); + + if (RAVENWOOD_VERBOSE_LOGGING) { + RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging"); + try { + Os.setenv("ANDROID_LOG_TAGS", "*:v", true); + } catch (ErrnoException e) { + // Shouldn't happen. + } + } + + System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); + // This will let AndroidJUnit4 use the original runner. + System.setProperty("android.junit.runner", + "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); + } /** * Initialize the environment. @@ -169,23 +210,21 @@ public class RavenwoodRuntimeEnvironmentController { var file = new File(RAVENWOOD_RESOURCE_APK); return config.mState.loadResources(file.exists() ? file : null); }; - // Set up test context's resources. + + // Set up test context's (== instrumentation context's) resources. // If the target package name == test package name, then we use the main resources. - // Otherwise, we don't simulate loading resources from the test APK yet. - // (we need to add `test_resource_apk` to `android_ravenwood_test`) - final Supplier testResourcesLoader; + final Supplier instResourcesLoader; if (isSelfInstrumenting) { - testResourcesLoader = targetResourcesLoader; + instResourcesLoader = targetResourcesLoader; } else { - testResourcesLoader = () -> { - fail("Cannot load resources from the test context (yet)." - + " Use target context's resources instead."); - return null; // unreachable. + instResourcesLoader = () -> { + var file = new File(RAVENWOOD_INST_RESOURCE_APK); + return config.mState.loadResources(file.exists() ? file : null); }; } - var testContext = new RavenwoodContext( - config.mTestPackageName, main, testResourcesLoader); + var instContext = new RavenwoodContext( + config.mTestPackageName, main, instResourcesLoader); var targetContext = new RavenwoodContext( config.mTargetPackageName, main, targetResourcesLoader); @@ -194,18 +233,18 @@ public class RavenwoodRuntimeEnvironmentController { config.mTargetPackageName, main, targetResourcesLoader); appContext.setApplicationContext(appContext); if (isSelfInstrumenting) { - testContext.setApplicationContext(appContext); + instContext.setApplicationContext(appContext); targetContext.setApplicationContext(appContext); } else { // When instrumenting into another APK, the test context doesn't have an app context. targetContext.setApplicationContext(appContext); } - config.mTestContext = testContext; + config.mInstContext = instContext; config.mTargetContext = targetContext; // Prepare other fields. config.mInstrumentation = new Instrumentation(); - config.mInstrumentation.basicInit(config.mTestContext, config.mTargetContext); + config.mInstrumentation.basicInit(config.mInstContext, config.mTargetContext); InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY); RavenwoodSystemServer.init(config); @@ -242,13 +281,13 @@ public class RavenwoodRuntimeEnvironmentController { InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); config.mInstrumentation = null; - if (config.mTestContext != null) { - ((RavenwoodContext) config.mTestContext).cleanUp(); + if (config.mInstContext != null) { + ((RavenwoodContext) config.mInstContext).cleanUp(); } if (config.mTargetContext != null) { ((RavenwoodContext) config.mTargetContext).cleanUp(); } - config.mTestContext = null; + config.mInstContext = null; config.mTargetContext = null; if (config.mProvideMainThread) { diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java index d4090e26223ae8f3500e7df8e185f3686cc40edf..3946dd8471b062ae73fd48474cb9d3721aa99e1c 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java @@ -67,7 +67,7 @@ public class RavenwoodSystemServer { sStartedServices = new ArraySet<>(); sTimings = new TimingsTraceAndSlog(); - sServiceManager = new SystemServiceManager(config.mTestContext); + sServiceManager = new SystemServiceManager(config.mInstContext); sServiceManager.setStartInfo(false, SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java index 1adb0f31da543b763ca44a952fffb78307c5c244..470165c6da43c6d907e539463d3ceb4ba8a3c304 100644 --- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java +++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java @@ -51,4 +51,9 @@ public @interface DisabledOnRavenwood { * General free-form description of why this test is being ignored. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java index 7faa654b903cb1bfacf58e5d5389ff855adeda5f..1c06829dba06e8d46411df1131ddeafc3dc1425f 100644 --- a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java +++ b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java @@ -51,4 +51,9 @@ public @interface IgnoreUnderRavenwood { * General free-form description of why this test is being ignored. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index cb8af0c01a1cf093a4145f87339c872974f74dae..5ba972df1193ea53f6be7a1add8b6cd362c691b0 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -46,6 +46,7 @@ import org.junit.runner.notification.RunListener; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Suite; import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.RunnerBuilder; import org.junit.runners.model.Statement; @@ -166,10 +167,12 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable return runner; } + private final Class mTestJavaClass; private TestClass mTestClass = null; private Runner mRealRunner = null; private Description mDescription = null; private Throwable mExceptionInConstructor = null; + private boolean mRealRunnerTakesRunnerBuilder = false; /** * Stores internal states / methods associated with this runner that's only needed in @@ -190,24 +193,29 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable * Constructor. */ public RavenwoodAwareTestRunner(Class testClass) { + mTestJavaClass = testClass; try { - mTestClass = new TestClass(testClass); - - Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); - - onRunnerInitializing(); + performGlobalInitialization(); /* * If the class has @DisabledOnRavenwood, then we'll delegate to * ClassSkippingTestRunner, which simply skips it. + * + * We need to do it before instantiating TestClass for b/367694651. */ if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood( - mTestClass.getJavaClass())) { - mRealRunner = new ClassSkippingTestRunner(mTestClass); + testClass)) { + mRealRunner = new ClassSkippingTestRunner(testClass); mDescription = mRealRunner.getDescription(); return; } + mTestClass = new TestClass(testClass); + + Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); + + onRunnerInitializing(); + // Find the real runner. final Class realRunnerClass; final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class); @@ -243,7 +251,7 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable } } - private static Runner instantiateRealRunner( + private Runner instantiateRealRunner( Class realRunnerClass, Class testClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, @@ -251,12 +259,19 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable try { return realRunnerClass.getConstructor(Class.class).newInstance(testClass); } catch (NoSuchMethodException e) { - var runnerBuilder = new AllDefaultPossibilitiesBuilder(); - return realRunnerClass.getConstructor(Class.class, - RunnerBuilder.class).newInstance(testClass, runnerBuilder); + var constructor = realRunnerClass.getConstructor(Class.class, RunnerBuilder.class); + mRealRunnerTakesRunnerBuilder = true; + return constructor.newInstance(testClass, new AllDefaultPossibilitiesBuilder()); } } + private void performGlobalInitialization() { + if (!isOnRavenwood()) { + return; + } + RavenwoodAwareTestRunnerHook.performGlobalInitialization(); + } + /** * Run the bare minimum setup to initialize the wrapped runner. */ @@ -265,7 +280,6 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable if (!isOnRavenwood()) { return; } - // DO NOT USE android.util.Log before calling onRunnerInitializing(). RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass); @@ -308,7 +322,7 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable return; } - Log.v(TAG, "Starting " + mTestClass.getJavaClass().getCanonicalName()); + Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName()); if (RAVENWOOD_VERBOSE_LOGGING) { dumpDescription(getDescription()); } @@ -317,14 +331,20 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable return; } + // TODO(b/365976974): handle nested classes better + final boolean skipRunnerHook = + mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite; + sCurrentRunner.set(this); try { - try { - RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart( - this, getDescription()); - } catch (Throwable th) { - notifier.reportBeforeTestFailure(getDescription(), th); - return; + if (!skipRunnerHook) { + try { + RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart( + this, getDescription()); + } catch (Throwable th) { + notifier.reportBeforeTestFailure(getDescription(), th); + return; + } } // Delegate to the inner runner. @@ -332,12 +352,13 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable } finally { sCurrentRunner.remove(); - try { - RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished( - this, getDescription()); - } catch (Throwable th) { - notifier.reportAfterTestFailure(th); - return; + if (!skipRunnerHook) { + try { + RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished( + this, getDescription()); + } catch (Throwable th) { + notifier.reportAfterTestFailure(th); + } } } } @@ -427,14 +448,11 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable * filter. */ private static class ClassSkippingTestRunner extends Runner implements Filterable { - private final TestClass mTestClass; private final Description mDescription; private boolean mFilteredOut; - ClassSkippingTestRunner(TestClass testClass) { - mTestClass = testClass; - mDescription = Description.createTestDescription( - testClass.getJavaClass(), testClass.getJavaClass().getSimpleName()); + ClassSkippingTestRunner(Class testClass) { + mDescription = Description.createTestDescription(testClass, testClass.getSimpleName()); mFilteredOut = false; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index ea33aa6901730770f59aeed5403c92ef54ca1ef8..446f819ad41b8e9cadc18626b23812912cf543af 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -74,7 +74,7 @@ public final class RavenwoodConfig { final List> mServicesRequired = new ArrayList<>(); - volatile Context mTestContext; + volatile Context mInstContext; volatile Context mTargetContext; volatile Instrumentation mInstrumentation; diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 984106b21e9a1ea8a94ae22f778311f797857546..4196d8e2261033759f8a0a051728511b3d4f0988 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -216,7 +216,7 @@ public final class RavenwoodRule implements TestRule { */ @Deprecated public Context getContext() { - return Objects.requireNonNull(mConfiguration.mTestContext, + return Objects.requireNonNull(mConfiguration.mInstContext, "Context is only available during @Test execution"); } diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java index 0178b934a649492ce075d526b0eb9b591062fcb7..aa8c29936082a50faf09b704ad02193292c53711 100644 --- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java +++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java @@ -34,6 +34,12 @@ public class RavenwoodAwareTestRunnerHook { private RavenwoodAwareTestRunnerHook() { } + /** + * Called before any code starts. Internally it will only initialize the environment once. + */ + public static void performGlobalInitialization() { + } + /** * Called when a runner starts, before the inner runner gets a chance to run. */ diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java index 96746c679020e78897293b119886c835d4e6b46e..989bb6be17822e3e49392ebb2509931542e0b45e 100644 --- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java +++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java @@ -63,6 +63,8 @@ public class RavenwoodCommonUtils { public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood"; public static final String RAVENWOOD_RESOURCE_APK = "ravenwood-res-apks/ravenwood-res.apk"; + public static final String RAVENWOOD_INST_RESOURCE_APK = + "ravenwood-res-apks/ravenwood-inst-res.apk"; public static final String RAVENWOOD_EMPTY_RESOURCES_APK = RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk"; diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index 790bb1c2373b913b4cda0a716a23848743d13005..be8c4438843592d7968011fa4aecd28afe7a2d94 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -15,54 +15,10 @@ */ package com.android.platform.test.ravenwood.runtimehelper; -import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; - -import android.system.ErrnoException; -import android.system.Os; - -import com.android.ravenwood.common.RavenwoodCommonUtils; - -import java.io.File; -import java.lang.reflect.Modifier; -import java.util.ArrayList; - /** * Standard class loader hook. - * - * Currently, we use this class to load libandroid_runtime (if needed). In the future, we may - * load other JNI or do other set up here. */ public class ClassLoadHook { - /** - * If true, we won't load `libandroid_runtime` - * - *

Looks like there's some complexity in running a host test with JNI with `atest`, - * so we need a way to remove the dependency. - */ - private static final boolean SKIP_LOADING_LIBANDROID = "1".equals(System.getenv( - "RAVENWOOD_SKIP_LOADING_LIBANDROID")); - - public static final String CORE_NATIVE_CLASSES = "core_native_classes"; - public static final String ICU_DATA_PATH = "icu.data.path"; - public static final String KEYBOARD_PATHS = "keyboard_paths"; - public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes"; - - public static final String LIBANDROID_RUNTIME_NAME = "android_runtime"; - - /** - * Extra strings needed to pass to register_android_graphics_classes(). - * - * `android.graphics.Graphics` is not actually a class, so we can't use the same initialization - * strategy than the "normal" classes. So we just hardcode it here. - */ - public static final String GRAPHICS_EXTRA_INIT_PARAMS = ",android.graphics.Graphics"; - - private static String sInitialDir = new File("").getAbsolutePath(); - - static { - log("Initialized. Current dir=" + sInitialDir); - } - private ClassLoadHook() { } @@ -75,144 +31,12 @@ public class ClassLoadHook { public static void onClassLoaded(Class clazz) { System.out.println("Framework class loaded: " + clazz.getCanonicalName()); - loadFrameworkNativeCode(); - } - - private static void log(String message) { - System.out.println("ClassLoadHook: " + message); - } - - private static void log(String fmt, Object... args) { - log(String.format(fmt, args)); - } - - private static void ensurePropertyNotSet(String key) { - if (System.getProperty(key) != null) { - throw new RuntimeException("System property \"" + key + "\" is set unexpectedly"); + // Always try to initialize the environment in case classes are loaded before + // RavenwoodAwareTestRunner is initialized + try { + Class.forName("android.platform.test.ravenwood.RavenwoodRuntimeEnvironmentController") + .getMethod("globalInitOnce").invoke(null); + } catch (ReflectiveOperationException ignored) { } } - - private static void setProperty(String key, String value) { - System.setProperty(key, value); - log("Property set: %s=\"%s\"", key, value); - } - - private static void dumpSystemProperties() { - for (var prop : System.getProperties().entrySet()) { - log(" %s=\"%s\"", prop.getKey(), prop.getValue()); - } - } - - private static boolean sLoadFrameworkNativeCodeCalled = false; - - /** - * Load `libandroid_runtime` if needed. - */ - private static void loadFrameworkNativeCode() { - // This is called from class-initializers, so no synchronization is needed. - if (sLoadFrameworkNativeCodeCalled) { - return; - } - sLoadFrameworkNativeCodeCalled = true; - - // libandroid_runtime uses Java's system properties to decide what JNI methods to set up. - // Set up these properties for host-side tests. - - if ("1".equals(System.getenv("RAVENWOOD_DUMP_PROPERTIES"))) { - log("Java system properties:"); - dumpSystemProperties(); - } - - if (SKIP_LOADING_LIBANDROID) { - log("Skip loading native runtime."); - return; - } - - if (RAVENWOOD_VERBOSE_LOGGING) { - log("Force enabling verbose logging"); - try { - Os.setenv("ANDROID_LOG_TAGS", "*:v", true); - } catch (ErrnoException e) { - // Shouldn't happen. - } - } - - // Make sure these properties are not set. - ensurePropertyNotSet(CORE_NATIVE_CLASSES); - ensurePropertyNotSet(ICU_DATA_PATH); - ensurePropertyNotSet(KEYBOARD_PATHS); - ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES); - - // Load the libraries, if needed. - final var libanrdoidClasses = getClassesWithNativeMethods(sLibandroidClasses); - final var libhwuiClasses = getClassesWithNativeMethods(sLibhwuiClasses); - if (libanrdoidClasses.isEmpty() && libhwuiClasses.isEmpty()) { - log("No classes require JNI methods, skip loading native runtime."); - return; - } - setProperty(CORE_NATIVE_CLASSES, libanrdoidClasses); - setProperty(GRAPHICS_NATIVE_CLASSES, libhwuiClasses + GRAPHICS_EXTRA_INIT_PARAMS); - - log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libanrdoidClasses + "' and '" - + libhwuiClasses + "'"); - RavenwoodCommonUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME); - } - - /** - * Classes with native methods that are backed by libandroid_runtime. - * - * See frameworks/base/core/jni/platform/host/HostRuntime.cpp - */ - private static final Class[] sLibandroidClasses = { - android.util.Log.class, - android.os.Parcel.class, - android.os.Binder.class, - android.content.res.ApkAssets.class, - android.content.res.AssetManager.class, - android.content.res.StringBlock.class, - android.content.res.XmlBlock.class, - }; - - /** - * Classes with native methods that are backed by libhwui. - * - * See frameworks/base/libs/hwui/apex/LayoutlibLoader.cpp - */ - private static final Class[] sLibhwuiClasses = { - android.graphics.Interpolator.class, - android.graphics.Matrix.class, - android.graphics.Path.class, - android.graphics.Color.class, - android.graphics.ColorSpace.class, - }; - - /** - * @return if a given class and its nested classes, if any, have any native method or not. - */ - private static boolean hasNativeMethod(Class clazz) { - for (var nestedClass : clazz.getNestMembers()) { - for (var method : nestedClass.getDeclaredMethods()) { - if (Modifier.isNative(method.getModifiers())) { - return true; - } - } - } - return false; - } - /** - * Create a list of classes as comma-separated that require JNI methods to be set up from - * a given class list, ignoring classes with no native methods. - */ - private static String getClassesWithNativeMethods(Class[] classes) { - final var coreNativeClassesToLoad = new ArrayList(); - - for (var clazz : classes) { - if (hasNativeMethod(clazz)) { - log("Class %s has native methods", clazz.getCanonicalName()); - coreNativeClassesToLoad.add(clazz.getName()); - } - } - - return String.join(",", coreNativeClassesToLoad); - } } diff --git a/ravenwood/scripts/update-test-mapping.sh b/ravenwood/scripts/update-test-mapping.sh index b6cf5b857682138a7fe391f675005c4bb99e8c2e..e478b50cc2b903dc14336e0d8a4bc900654f1f14 100755 --- a/ravenwood/scripts/update-test-mapping.sh +++ b/ravenwood/scripts/update-test-mapping.sh @@ -20,6 +20,9 @@ set -e +# Tests that shouldn't be in presubmit. +EXEMPT='^(SystemUiRavenTests)$' + main() { local script_name="${0##*/}" local script_dir="${0%/*}" @@ -30,7 +33,7 @@ main() { local footer="$(sed -ne '/AUTO-GENERATED-END/,$p' "$test_mapping")" echo "Getting all tests" - local tests=( $("$script_dir/list-ravenwood-tests.sh") ) + local tests=( $("$script_dir/list-ravenwood-tests.sh" | grep -vP "$EXEMPT") ) local num_tests="${#tests[@]}" diff --git a/ravenwood/tests/bivalentinst/Android.bp b/ravenwood/tests/bivalentinst/Android.bp index 38d1b299b00235630af2b3052fe63afc20bd0478..41e45e5a6d954c67300f200e066b71c0431f5007 100644 --- a/ravenwood/tests/bivalentinst/Android.bp +++ b/ravenwood/tests/bivalentinst/Android.bp @@ -27,8 +27,7 @@ android_ravenwood_test { "junit", "truth", ], - // TODO(b/366246777) uncomment it and the test. - // resource_apk: "RavenwoodBivalentInstTest_self_inst_device", + resource_apk: "RavenwoodBivalentInstTest_self_inst_device", auto_gen_config: true, } @@ -53,8 +52,8 @@ android_ravenwood_test { "junit", "truth", ], - // TODO(b/366246777) uncomment it and the test. - // resource_apk: "RavenwoodBivalentInstTestTarget", + resource_apk: "RavenwoodBivalentInstTestTarget", + inst_resource_apk: "RavenwoodBivalentInstTest_nonself_inst_device", auto_gen_config: true, } diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java index 9f3ca6ffcd265759f6bf917da77b095b35f89738..92d43d714e14df4bd3aefb85e41c84b9a0f26599 100644 --- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java +++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Instrumentation; import android.content.Context; -import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.ravenwood.RavenwoodConfig; import android.platform.test.ravenwood.RavenwoodConfig.Config; @@ -97,7 +96,6 @@ public class RavenwoodInstrumentationTest_nonself { } @Test - @DisabledOnRavenwood(reason = "b/366246777") public void testTargetAppResource() { assertThat(sTargetContext.getString( com.android.ravenwood.bivalentinst_target_app.R.string.test_string_in_target)) @@ -105,8 +103,6 @@ public class RavenwoodInstrumentationTest_nonself { } @Test - @DisabledOnRavenwood( - reason = "Loading resources from non-self-instrumenting test APK isn't supported yet") public void testTestAppResource() { assertThat(sTestContext.getString( com.android.ravenwood.bivalentinsttest_nonself_inst.R.string.test_string_in_test)) diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java index fdff22210c16de7be07e69517b8bf30a1d206619..2f35923dead2517c8d14c64b8b4c635a87f34a70 100644 --- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java +++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Instrumentation; import android.content.Context; -import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.ravenwood.RavenwoodConfig; import android.platform.test.ravenwood.RavenwoodConfig.Config; @@ -109,7 +108,6 @@ public class RavenwoodInstrumentationTest_self { } @Test - @DisabledOnRavenwood(reason = "b/366246777") public void testTargetAppResource() { assertThat(sTargetContext.getString( com.android.ravenwood.bivalentinsttest_self_inst.R.string.test_string_in_test)) @@ -117,7 +115,6 @@ public class RavenwoodInstrumentationTest_self { } @Test - @DisabledOnRavenwood(reason = "b/366246777") public void testTestAppResource() { assertThat(sTestContext.getString( com.android.ravenwood.bivalentinsttest_self_inst.R.string.test_string_in_test)) diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java index 6d8fb983504bfc65c19abc1037b7062ce57847c9..bd013133d3a4d26d1f0db48933870768b2656160 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java @@ -17,12 +17,14 @@ package com.android.ravenwoodtest.runnercallbacktests; import static org.junit.Assume.assumeTrue; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.NoRavenizer; import android.platform.test.ravenwood.RavenwoodAwareTestRunner; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -31,6 +33,7 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import java.util.ArrayList; @@ -353,4 +356,103 @@ public class RavenwoodRunnerCallbackTest extends RavenwoodRunnerTestBase { public void test2() { } } + + /** + * The test class is unloadable, but has a @DisabledOnRavenwood. + */ + @RunWith(AndroidJUnit4.class) + @DisabledOnRavenwood + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testSuiteStarted: ClassUnloadbleAndDisabledTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndDisabledTest) + testIgnored: ClassUnloadbleAndDisabledTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndDisabledTest) + testSuiteFinished: ClassUnloadbleAndDisabledTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndDisabledTest) + testSuiteFinished: classes + testRunFinished: 0,0,0,1 + """) + // CHECKSTYLE:ON + public static class ClassUnloadbleAndDisabledTest { + static { + Assert.fail("Class unloadable!"); + } + + @Test + public void test1() { + } + + @Test + public void test2() { + } + } + + /** + * The test class is unloadable, but has a @DisabledOnRavenwood. + */ + @RunWith(AndroidJUnit4.class) + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest + testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest + testStarted: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testFailure: Class unloadable! + testFinished: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testStarted: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testFailure: Class unloadable! + testFinished: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testSuiteFinished: classes + testRunFinished: 2,2,0,0 + """) + // CHECKSTYLE:ON + public static class ClassUnloadbleAndEnabledTest { + static { + Assert.fail("Class unloadable!"); + } + + @Test + public void test1() { + } + + @Test + public void test2() { + } + } + + public static class BrokenTestRunner extends BlockJUnit4ClassRunner { + public BrokenTestRunner(Class testClass) throws InitializationError { + super(testClass); + + if (true) { + throw new RuntimeException("This is a broken test runner!"); + } + } + } + + /** + * The test runner throws an exception from the ctor. + */ + @RunWith(BrokenTestRunner.class) + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testStarted: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest) + testFailure: Exception detected in constructor + testFinished: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest) + testSuiteFinished: classes + testRunFinished: 1,1,0,0 + """) + // CHECKSTYLE:ON + public static class BrokenRunnerTest { + @Test + public void test1() { + } + + @Test + public void test2() { + } + } } diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java index 6ee443fac8743b1c7442bffef37072a4bb4bac21..73ea64f57997f2d6fbcc71dd568e8fd2e2fa5dc5 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java @@ -146,7 +146,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunStarted: classes testSuiteStarted: classes testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest) - testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfiguration.Config + testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfig.Config testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest) testSuiteFinished: classes testRunFinished: 1,1,0,0 @@ -220,7 +220,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase } /** - * @Config's must be of type RavenwoodConfiguration. + * @Config's must be of type RavenwoodConfig. */ @RunWith(AndroidJUnit4.class) // CHECKSTYLE:OFF @@ -228,7 +228,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunStarted: classes testSuiteStarted: classes testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest) - testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfiguration.Config but type is not RavenwoodConfiguration + testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfig.Config but type is not RavenwoodConfig testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest) testSuiteFinished: classes testRunFinished: 1,1,0,0 @@ -237,7 +237,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase public static class WrongTypeConfigTest { @RavenwoodConfig.Config - public Object sConfig = + public static Object sConfig = new RavenwoodConfig.Builder().build(); @Test @@ -246,6 +246,34 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase } + /** + * @Rule must be of type RavenwoodRule. + */ + @RunWith(AndroidJUnit4.class) + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest + testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest) + testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it. + testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest) + testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest + testSuiteFinished: classes + testRunFinished: 1,1,0,0 + """) + // CHECKSTYLE:ON + public static class WrongTypeRuleTest { + + @Rule + public TestRule mRule = new RavenwoodRule.Builder().build(); + + @Test + public void testConfig() { + } + + } + /** * Config can't be used with a (instance) Rule. */ @@ -255,7 +283,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunStarted: classes testSuiteStarted: classes testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest) - testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration. + testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig. testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest) testSuiteFinished: classes testRunFinished: 1,1,0,0 @@ -373,7 +401,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunStarted: classes testSuiteStarted: classes testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest) - testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration. + testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig. testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest) testSuiteFinished: classes testRunFinished: 1,1,0,0 @@ -408,10 +436,11 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testSuiteStarted: classes testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest) + testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it. testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest) testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest testSuiteFinished: classes - testRunFinished: 1,0,0,0 + testRunFinished: 1,1,0,0 """) // CHECKSTYLE:ON public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass { @@ -434,7 +463,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testSuiteStarted: classes testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest) - testFailure: RavenwoodConfiguration and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfiguration. + testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it. testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest) testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest testSuiteFinished: classes diff --git a/services/appfunctions/TEST_MAPPING b/services/appfunctions/TEST_MAPPING index 91e82ec0e95b930f469d80057b1770397ae1758d..91cfa064d9fc89f891faad67e6488e09e3db73ab 100644 --- a/services/appfunctions/TEST_MAPPING +++ b/services/appfunctions/TEST_MAPPING @@ -1,4 +1,9 @@ { + "presubmit": [ + { + "name": "FrameworksAppFunctionsTests" + } + ], "postsubmit": [ { "name": "FrameworksAppFunctionsTests" diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java index c3b7087a44c3d1823c79588c613eb8e9a3fc9787..1f98334bb8ce55126909d228c46d3364a81bf45b 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java @@ -16,7 +16,15 @@ package com.android.server.appfunctions; +import android.annotation.NonNull; +import android.os.UserHandle; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -33,5 +41,50 @@ public final class AppFunctionExecutors { /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>()); + /** A map of per-user executors for queued work. */ + @GuardedBy("sLock") + private static final SparseArray mPerUserExecutorsLocked = new SparseArray<>(); + + private static final Object sLock = new Object(); + + /** + * Returns a per-user executor for queued metadata sync request. + * + *

The work submitted to these executor (Sync request) needs to be synchronous per user hence + * the use of a single thread. + * + *

Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code + * MetadataSyncAdapter}. + */ + // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself. + public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) { + synchronized (sLock) { + ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null); + if (executor == null) { + executor = Executors.newSingleThreadExecutor(); + mPerUserExecutorsLocked.put(user.getIdentifier(), executor); + } + return executor; + } + } + + /** + * Shuts down and removes the per-user executor for queued work. + * + *

This should be called when the user is removed. + */ + public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user) + throws InterruptedException { + ExecutorService executor; + synchronized (sLock) { + executor = mPerUserExecutorsLocked.get(user.getIdentifier()); + mPerUserExecutorsLocked.remove(user.getIdentifier()); + } + if (executor != null) { + executor.shutdown(); + var unused = executor.awaitTermination(30, TimeUnit.SECONDS); + } + } + private AppFunctionExecutors() {} } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java index 02800cbf4f3dbd284388ab737078ca53e53eda5e..c293087defb6ce6fef89402d5aa4d2cb6a3c62a9 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java @@ -16,6 +16,7 @@ package com.android.server.appfunctions; +import android.annotation.NonNull; import android.app.appfunctions.AppFunctionManagerConfiguration; import android.content.Context; @@ -36,4 +37,14 @@ public class AppFunctionManagerService extends SystemService { publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl); } } + + @Override + public void onUserUnlocked(@NonNull TargetUser user) { + mServiceImpl.onUserUnlocked(user); + } + + @Override + public void onUserStopping(@NonNull TargetUser user) { + mServiceImpl.onUserStopping(user); + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 269419f70b2f1edbf6df681ad99d3187e0ee13fb..b4713d9f11afd188923c2f49d25864e7dd41304d 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -19,28 +19,35 @@ package com.android.server.appfunctions; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; import android.app.appfunctions.IExecuteAppFunctionCallback; import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback; +import android.app.appsearch.AppSearchManager; +import android.app.appsearch.AppSearchResult; +import android.app.appsearch.observer.DocumentChangeInfo; +import android.app.appsearch.observer.ObserverCallback; +import android.app.appsearch.observer.ObserverSpec; +import android.app.appsearch.observer.SchemaChangeInfo; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; import android.util.Slog; -import android.app.appsearch.AppSearchResult; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemService.TargetUser; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; import java.util.Objects; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.CompletionException; /** Implementation of the AppFunctionManagerService. */ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @@ -50,9 +57,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final CallerValidator mCallerValidator; private final ServiceHelper mInternalServiceHelper; private final ServiceConfig mServiceConfig; + private final Context mContext; public AppFunctionManagerServiceImpl(@NonNull Context context) { this( + context, new RemoteServiceCallerImpl<>( context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR), new CallerValidatorImpl(context), @@ -62,16 +71,38 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @VisibleForTesting AppFunctionManagerServiceImpl( + Context context, RemoteServiceCaller remoteServiceCaller, CallerValidator callerValidator, ServiceHelper appFunctionInternalServiceHelper, ServiceConfig serviceConfig) { + mContext = Objects.requireNonNull(context); mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller); mCallerValidator = Objects.requireNonNull(callerValidator); mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper); mServiceConfig = serviceConfig; } + /** Called when the user is unlocked. */ + public void onUserUnlocked(TargetUser user) { + Objects.requireNonNull(user); + + registerAppSearchObserver(user); + trySyncRuntimeMetadata(user); + } + + /** Called when the user is stopping. */ + public void onUserStopping(@NonNull TargetUser user) { + Objects.requireNonNull(user); + + try { + AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle()); + MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); + } catch (InterruptedException e) { + Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e); + } + } + @Override public void executeAppFunction( @NonNull ExecuteAppFunctionAidlRequest requestInternal, @@ -82,25 +113,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback = new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback); - try { - executeAppFunctionInternal(requestInternal, safeExecuteAppFunctionCallback); - } catch (Exception e) { - safeExecuteAppFunctionCallback.onResult(mapExceptionToExecuteAppFunctionResponse(e)); - } - } - - private void executeAppFunctionInternal( - ExecuteAppFunctionAidlRequest requestInternal, - SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { - String validatedCallingPackage; - UserHandle targetUser; try { validatedCallingPackage = mCallerValidator.validateCallingPackage(requestInternal.getCallingPackage()); - targetUser = - mCallerValidator.verifyTargetUserHandle( - requestInternal.getUserHandle(), validatedCallingPackage); + mCallerValidator.verifyTargetUserHandle( + requestInternal.getUserHandle(), validatedCallingPackage); } catch (SecurityException exception) { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( @@ -110,6 +128,30 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } + int callingUid = Binder.getCallingUid(); + int callingPid = Binder.getCallingUid(); + THREAD_POOL_EXECUTOR.execute( + () -> { + try { + executeAppFunctionInternal( + requestInternal, + callingUid, + callingPid, + safeExecuteAppFunctionCallback); + } catch (Exception e) { + safeExecuteAppFunctionCallback.onResult( + mapExceptionToExecuteAppFunctionResponse(e)); + } + }); + } + + @WorkerThread + private void executeAppFunctionInternal( + ExecuteAppFunctionAidlRequest requestInternal, + int callingUid, + int callingPid, + SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { + UserHandle targetUser = requestInternal.getUserHandle(); // TODO(b/354956319): Add and honor the new enterprise policies. if (mCallerValidator.isUserOrganizationManaged(targetUser)) { safeExecuteAppFunctionCallback.onResult( @@ -131,53 +173,50 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } - var unused = mCallerValidator - .verifyCallerCanExecuteAppFunction( - validatedCallingPackage, - targetPackageName, - requestInternal.getClientRequest().getFunctionIdentifier()) - .thenAccept( - canExecute -> { - if (!canExecute) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_DENIED, - "Caller does not have permission to execute the" - + " appfunction", - /* extras= */ null)); - return; - } - Intent serviceIntent = - mInternalServiceHelper.resolveAppFunctionService( - targetPackageName, targetUser); - if (serviceIntent == null) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, - "Cannot find the target service.", - /* extras= */ null)); - return; - } - final long token = Binder.clearCallingIdentity(); - try { - bindAppFunctionServiceUnchecked( - requestInternal, - serviceIntent, - targetUser, - safeExecuteAppFunctionCallback, - /* bindFlags= */ Context.BIND_AUTO_CREATE, - /* timeoutInMillis= */ mServiceConfig - .getExecuteAppFunctionTimeoutMillis()); - } finally { - Binder.restoreCallingIdentity(token); - } - }) - .exceptionally( - ex -> { - safeExecuteAppFunctionCallback.onResult( - mapExceptionToExecuteAppFunctionResponse(ex)); - return null; - }); + var unused = + mCallerValidator + .verifyCallerCanExecuteAppFunction( + callingUid, + callingPid, + requestInternal.getCallingPackage(), + targetPackageName, + requestInternal.getClientRequest().getFunctionIdentifier()) + .thenAccept( + canExecute -> { + if (!canExecute) { + safeExecuteAppFunctionCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse.RESULT_DENIED, + "Caller does not have permission to execute" + + " the appfunction", + /* extras= */ null)); + return; + } + Intent serviceIntent = + mInternalServiceHelper.resolveAppFunctionService( + targetPackageName, targetUser); + if (serviceIntent == null) { + safeExecuteAppFunctionCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse + .RESULT_INTERNAL_ERROR, + "Cannot find the target service.", + /* extras= */ null)); + return; + } + bindAppFunctionServiceUnchecked( + requestInternal, + serviceIntent, + targetUser, + safeExecuteAppFunctionCallback, + /* bindFlags= */ Context.BIND_AUTO_CREATE); + }) + .exceptionally( + ex -> { + safeExecuteAppFunctionCallback.onResult( + mapExceptionToExecuteAppFunctionResponse(ex)); + return null; + }); } private void bindAppFunctionServiceUnchecked( @@ -185,13 +224,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @NonNull Intent serviceIntent, @NonNull UserHandle targetUser, @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback, - int bindFlags, - long timeoutInMillis) { + int bindFlags) { boolean bindServiceResult = mRemoteServiceCaller.runServiceCall( serviceIntent, bindFlags, - timeoutInMillis, targetUser, new RunServiceCallCallback() { @Override @@ -232,16 +269,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { "Failed to connect to AppFunctionService", /* extras= */ null)); } - - @Override - public void onTimedOut() { - Slog.e(TAG, "Timed out"); - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_TIMED_OUT, - "Binding to AppFunctionService timed out.", - /* extras= */ null)); - } }); if (!bindServiceResult) { @@ -255,6 +282,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) { + if (e instanceof CompletionException) { + e = e.getCause(); + } + if (e instanceof AppSearchException) { AppSearchException appSearchException = (AppSearchException) e; return ExecuteAppFunctionResponse.newFailure( @@ -286,4 +317,100 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR; } + + private void registerAppSearchObserver(@NonNull TargetUser user) { + AppSearchManager perUserAppSearchManager = + mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0) + .getSystemService(AppSearchManager.class); + if (perUserAppSearchManager == null) { + Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier()); + return; + } + FutureGlobalSearchSession futureGlobalSearchSession = + new FutureGlobalSearchSession( + perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR); + AppFunctionMetadataObserver appFunctionMetadataObserver = + new AppFunctionMetadataObserver( + user.getUserHandle(), + mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); + var unused = + futureGlobalSearchSession + .registerObserverCallbackAsync( + "android", + new ObserverSpec.Builder().build(), + THREAD_POOL_EXECUTOR, + appFunctionMetadataObserver) + .whenComplete( + (voidResult, ex) -> { + if (ex != null) { + Slog.e(TAG, "Failed to register observer: ", ex); + } + futureGlobalSearchSession.close(); + }); + } + + private void trySyncRuntimeMetadata(@NonNull TargetUser user) { + MetadataSyncAdapter metadataSyncAdapter = + MetadataSyncPerUser.getPerUserMetadataSyncAdapter( + user.getUserHandle(), + mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); + if (metadataSyncAdapter != null) { + var unused = + metadataSyncAdapter + .submitSyncRequest() + .whenComplete( + (isSuccess, ex) -> { + if (ex != null || !isSuccess) { + Slog.e(TAG, "Sync was not successful"); + } + }); + } + } + + private static class AppFunctionMetadataObserver implements ObserverCallback { + @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter; + + AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) { + mPerUserMetadataSyncAdapter = + MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext); + } + + @Override + public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) { + if (mPerUserMetadataSyncAdapter == null) { + return; + } + if (documentChangeInfo + .getDatabaseName() + .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) + && documentChangeInfo + .getNamespace() + .equals( + AppFunctionStaticMetadataHelper + .APP_FUNCTION_STATIC_NAMESPACE)) { + var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); + } + } + + @Override + public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) { + if (mPerUserMetadataSyncAdapter == null) { + return; + } + if (schemaChangeInfo + .getDatabaseName() + .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) { + boolean shouldInitiateSync = false; + for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) { + if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) { + shouldInitiateSync = true; + break; + } + } + if (shouldInitiateSync) { + var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); + } + } + } + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java index e7a861e0a0dc5b017ca797713762626ef618e4fd..3592ed587ab0edb01e99e2f61a875939749981b9 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java @@ -60,9 +60,9 @@ public interface CallerValidator { * Validates that the caller can execute the specified app function. * *

The caller can execute if the app function's package name is the same as the caller's - * package or the caller has either {@link Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or - * {@link Manifest.permission.EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can - * still opt-out of caller having {@link Manifest.permission.EXECUTE_APP_FUNCTIONS}. + * package or the caller has either {@link Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} or + * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can + * still opt-out of caller having {@link Manifest.permission#EXECUTE_APP_FUNCTIONS}. * * @param callerPackageName The calling package (as previously validated). * @param targetPackageName The package that owns the app function to execute. @@ -70,6 +70,8 @@ public interface CallerValidator { * @return Whether the caller can execute the specified app function. */ AndroidFuture verifyCallerCanExecuteAppFunction( + int callingUid, + int callingPid, @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull String functionId); diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java index 94a63b43dd173c97ad545d007689b4e4355c06f5..8b6251a59e3a6c708f81b03593b7e217fc835d29 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java @@ -20,6 +20,7 @@ import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCT import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction; + import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.Manifest; @@ -41,6 +42,7 @@ import android.os.UserHandle; import android.os.UserManager; import com.android.internal.infra.AndroidFuture; + import java.util.Objects; /* Validates that caller has the correct privilege to call an AppFunctionManager Api. */ @@ -82,7 +84,6 @@ class CallerValidatorImpl implements CallerValidator { } @Override - @BinderThread @RequiresPermission( anyOf = { Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, @@ -90,6 +91,8 @@ class CallerValidatorImpl implements CallerValidator { }, conditional = true) public AndroidFuture verifyCallerCanExecuteAppFunction( + int callingUid, + int callingPid, @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull String functionId) { @@ -97,11 +100,11 @@ class CallerValidatorImpl implements CallerValidator { return AndroidFuture.completedFuture(true); } - int pid = Binder.getCallingPid(); - int uid = Binder.getCallingUid(); boolean hasTrustedExecutionPermission = mContext.checkPermission( - Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, pid, uid) + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + callingPid, + callingUid) == PackageManager.PERMISSION_GRANTED; if (hasTrustedExecutionPermission) { @@ -109,40 +112,34 @@ class CallerValidatorImpl implements CallerValidator { } boolean hasExecutionPermission = - mContext.checkPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid) + mContext.checkPermission( + Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; if (!hasExecutionPermission) { return AndroidFuture.completedFuture(false); } - final long token = Binder.clearCallingIdentity(); - try { - FutureAppSearchSession futureAppSearchSession = - new FutureAppSearchSessionImpl( - mContext.getSystemService(AppSearchManager.class), - THREAD_POOL_EXECUTOR, - new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build()); + FutureAppSearchSession futureAppSearchSession = + new FutureAppSearchSessionImpl( + mContext.getSystemService(AppSearchManager.class), + THREAD_POOL_EXECUTOR, + new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build()); - String documentId = getDocumentIdForAppFunction(targetPackageName, functionId); + String documentId = getDocumentIdForAppFunction(targetPackageName, functionId); - return futureAppSearchSession - .getByDocumentId( - new GetByDocumentIdRequest.Builder(APP_FUNCTION_STATIC_NAMESPACE) - .addIds(documentId) - .build()) - .thenApply( - batchResult -> - getGenericDocumentFromBatchResult(batchResult, documentId)) - .thenApply( - CallerValidatorImpl::getRestrictCallersWithExecuteAppFunctionsProperty) - .thenApply( - restrictCallersWithExecuteAppFunctions -> - !restrictCallersWithExecuteAppFunctions - && hasExecutionPermission); - } finally { - Binder.restoreCallingIdentity(token); - } + return futureAppSearchSession + .getByDocumentId( + new GetByDocumentIdRequest.Builder(APP_FUNCTION_STATIC_NAMESPACE) + .addIds(documentId) + .build()) + .thenApply( + batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId)) + .thenApply(document -> !getRestrictCallersWithExecuteAppFunctionsProperty(document)) + .whenComplete( + (result, throwable) -> { + futureAppSearchSession.close(); + }); } private static GenericDocument getGenericDocumentFromBatchResult( @@ -167,19 +164,13 @@ class CallerValidatorImpl implements CallerValidator { } @Override - @BinderThread public boolean isUserOrganizationManaged(@NonNull UserHandle targetUser) { - final long callingIdentityToken = Binder.clearCallingIdentity(); - try { - if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class)) - .isDeviceManaged()) { - return true; - } - return Objects.requireNonNull(mContext.getSystemService(UserManager.class)) - .isManagedProfile(targetUser.getIdentifier()); - } finally { - Binder.restoreCallingIdentity(callingIdentityToken); + if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class)) + .isDeviceManaged()) { + return true; } + return Objects.requireNonNull(mContext.getSystemService(UserManager.class)) + .isManagedProfile(targetUser.getIdentifier()); } /** diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java index 0044b4b958cb392ef524e76a30f60aacef3c095c..de2034b5be2f6b197ee80bfafcab36a6b5c73b21 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java @@ -84,6 +84,9 @@ public interface FutureAppSearchSession extends Closeable { AndroidFuture search( @NonNull String queryExpression, @NonNull SearchSpec searchSpec); + @Override + void close(); + /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */ interface FutureSearchResults { diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java index 3079d9f51bf3a0994dbc9eeb30bf6dbf6be15e4b..d24bb871c3931ed4ffbcf76e5e04e53db0b89959 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java @@ -38,7 +38,6 @@ import android.app.appsearch.SetSchemaResponse; import com.android.internal.infra.AndroidFuture; -import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -183,7 +182,15 @@ public class FutureAppSearchSessionImpl implements FutureAppSearchSession { } @Override - public void close() throws IOException {} + public void close() { + getSessionAsync() + .whenComplete( + (appSearchSession, throwable) -> { + if (appSearchSession != null) { + appSearchSession.close(); + } + }); + } private static final class FutureSearchResultsImpl implements FutureSearchResults { private final SearchResults mSearchResults; diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java index 0c2262456032979c9fb5886ca58e24395fca2477..874c5daa16629191fab0f65cd0835d3194e620bd 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java @@ -23,12 +23,10 @@ import android.app.appsearch.GlobalSearchSession; import android.app.appsearch.exceptions.AppSearchException; import android.app.appsearch.observer.ObserverCallback; import android.app.appsearch.observer.ObserverSpec; -import android.util.Slog; import com.android.internal.infra.AndroidFuture; import java.io.Closeable; -import java.io.IOException; import java.util.concurrent.Executor; /** A wrapper around {@link GlobalSearchSession} that provides a future-based API. */ @@ -84,11 +82,13 @@ public class FutureGlobalSearchSession implements Closeable { } @Override - public void close() throws IOException { - try { - getSessionAsync().get().close(); - } catch (Exception ex) { - Slog.e(TAG, "Failed to close global search session", ex); - } + public void close() { + getSessionAsync() + .whenComplete( + (appSearchSession, throwable) -> { + if (appSearchSession != null) { + appSearchSession.close(); + } + }); } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index d140258107dcc1fe62a31921576716c2e18df1e4..e29b6e403f2aa82023742179d2b3bc38cd7efa00 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -24,6 +24,8 @@ import android.annotation.WorkerThread; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appsearch.AppSearchBatchResult; +import android.app.appsearch.AppSearchManager; +import android.app.appsearch.AppSearchManager.SearchContext; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.PackageIdentifier; @@ -61,9 +63,8 @@ import java.util.concurrent.Executor; */ public class MetadataSyncAdapter { private static final String TAG = MetadataSyncAdapter.class.getSimpleName(); - private final FutureAppSearchSession mRuntimeMetadataSearchSession; - private final FutureAppSearchSession mStaticMetadataSearchSession; private final Executor mSyncExecutor; + private final AppSearchManager mAppSearchManager; private final PackageManager mPackageManager; // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility @@ -73,13 +74,11 @@ public class MetadataSyncAdapter { public MetadataSyncAdapter( @NonNull Executor syncExecutor, - @NonNull FutureAppSearchSession runtimeMetadataSearchSession, - @NonNull FutureAppSearchSession staticMetadataSearchSession, - @NonNull PackageManager packageManager) { + @NonNull PackageManager packageManager, + @NonNull AppSearchManager appSearchManager) { mSyncExecutor = Objects.requireNonNull(syncExecutor); - mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession); - mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession); mPackageManager = Objects.requireNonNull(packageManager); + mAppSearchManager = Objects.requireNonNull(appSearchManager); } /** @@ -89,31 +88,54 @@ public class MetadataSyncAdapter { * synchronization was successful. */ public AndroidFuture submitSyncRequest() { + SearchContext staticMetadataSearchContext = + new SearchContext.Builder( + AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) + .build(); + SearchContext runtimeMetadataSearchContext = + new SearchContext.Builder( + AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB) + .build(); AndroidFuture settableSyncStatus = new AndroidFuture<>(); mSyncExecutor.execute( () -> { - try { - trySyncAppFunctionMetadataBlocking(); + try (FutureAppSearchSession staticMetadataSearchSession = + new FutureAppSearchSessionImpl( + mAppSearchManager, + AppFunctionExecutors.THREAD_POOL_EXECUTOR, + staticMetadataSearchContext); + FutureAppSearchSession runtimeMetadataSearchSession = + new FutureAppSearchSessionImpl( + mAppSearchManager, + AppFunctionExecutors.THREAD_POOL_EXECUTOR, + runtimeMetadataSearchContext)) { + + trySyncAppFunctionMetadataBlocking( + staticMetadataSearchSession, runtimeMetadataSearchSession); settableSyncStatus.complete(true); - } catch (Exception e) { - settableSyncStatus.completeExceptionally(e); + + } catch (Exception ex) { + settableSyncStatus.completeExceptionally(ex); } }); return settableSyncStatus; } @WorkerThread - private void trySyncAppFunctionMetadataBlocking() + @VisibleForTesting + void trySyncAppFunctionMetadataBlocking( + @NonNull FutureAppSearchSession staticMetadataSearchSession, + @NonNull FutureAppSearchSession runtimeMetadataSearchSession) throws ExecutionException, InterruptedException { ArrayMap> staticPackageToFunctionMap = getPackageToFunctionIdMap( - mStaticMetadataSearchSession, + staticMetadataSearchSession, AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE, AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID, AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME); ArrayMap> runtimePackageToFunctionMap = getPackageToFunctionIdMap( - mRuntimeMetadataSearchSession, + runtimeMetadataSearchSession, RUNTIME_SCHEMA_TYPE, AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME); @@ -123,18 +145,30 @@ public class MetadataSyncAdapter { ArrayMap> removedFunctionsDiffMap = getRemovedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap); - Set appRuntimeMetadataSchemas = - getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet()); - appRuntimeMetadataSchemas.add( - AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema()); + if (!staticPackageToFunctionMap.keySet().equals(runtimePackageToFunctionMap.keySet())) { + // Drop removed packages from removedFunctionsDiffMap, as setSchema() deletes them + ArraySet removedPackages = + getRemovedPackages( + staticPackageToFunctionMap.keySet(), removedFunctionsDiffMap.keySet()); + for (String packageName : removedPackages) { + removedFunctionsDiffMap.remove(packageName); + } + Set appRuntimeMetadataSchemas = + getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet()); + appRuntimeMetadataSchemas.add( + AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema()); + SetSchemaRequest addSetSchemaRequest = + buildSetSchemaRequestForRuntimeMetadataSchemas( + mPackageManager, appRuntimeMetadataSchemas); + Objects.requireNonNull( + runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); + } - // Operation order matters here. i.e. remove -> setSchema -> add. Otherwise we would - // encounter an error trying to delete a document with no existing schema. if (!removedFunctionsDiffMap.isEmpty()) { RemoveByDocumentIdRequest removeByDocumentIdRequest = buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap); AppSearchBatchResult removeDocumentBatchResult = - mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); + runtimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); if (!removeDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( removeDocumentBatchResult.getFailures().values()); @@ -142,15 +176,10 @@ public class MetadataSyncAdapter { } if (!addedFunctionsDiffMap.isEmpty()) { - // TODO(b/357551503): only set schema on package diff - SetSchemaRequest addSetSchemaRequest = - buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas); - Objects.requireNonNull( - mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); PutDocumentsRequest putDocumentsRequest = buildPutRuntimeMetadataRequest(addedFunctionsDiffMap); AppSearchBatchResult putDocumentBatchResult = - mRuntimeMetadataSearchSession.put(putDocumentsRequest).get(); + runtimeMetadataSearchSession.put(putDocumentsRequest).get(); if (!putDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( putDocumentBatchResult.getFailures().values()); @@ -180,11 +209,7 @@ public class MetadataSyncAdapter { ArraySet addedFunctionIds = addedFunctionsDiffMap.valueAt(i); for (String addedFunctionId : addedFunctionIds) { putDocumentRequestBuilder.addGenericDocuments( - new AppFunctionRuntimeMetadata.Builder( - packageName, - addedFunctionId, - AppFunctionRuntimeMetadata - .PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID) + new AppFunctionRuntimeMetadata.Builder(packageName, addedFunctionId) .build()); } } @@ -215,6 +240,7 @@ public class MetadataSyncAdapter { @NonNull private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas( + @NonNull PackageManager packageManager, @NonNull Set metadataSchemaSet) { Objects.requireNonNull(metadataSchemaSet); SetSchemaRequest.Builder setSchemaRequestBuilder = @@ -224,7 +250,7 @@ public class MetadataSyncAdapter { String packageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema( runtimeMetadataSchema.getSchemaType()); - byte[] packageCert = getCertificate(packageName); + byte[] packageCert = getCertificate(packageManager, packageName); if (packageCert == null) { continue; } @@ -232,12 +258,11 @@ public class MetadataSyncAdapter { runtimeMetadataSchema.getSchemaType(), true, new PackageIdentifier(packageName, packageCert)); + setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility( + runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS)); + setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility( + runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS_TRUSTED)); } - - setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility( - RUNTIME_SCHEMA_TYPE, Set.of(EXECUTE_APP_FUNCTIONS)); - setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility( - RUNTIME_SCHEMA_TYPE, Set.of(EXECUTE_APP_FUNCTIONS_TRUSTED)); return setSchemaRequestBuilder.build(); } @@ -256,6 +281,30 @@ public class MetadataSyncAdapter { return appRuntimeMetadataSchemas; } + /** + * This method returns a set of packages that are in the removed function packages but not in + * the all existing static packages. + * + * @param allExistingStaticPackages A set of all existing static metadata packages. + * @param removedFunctionPackages A set of all removed function packages. + * @return A set of packages that are in the removed function packages but not in the all + * existing static packages. + */ + @NonNull + private static ArraySet getRemovedPackages( + @NonNull Set allExistingStaticPackages, + @NonNull Set removedFunctionPackages) { + ArraySet removedPackages = new ArraySet<>(); + + for (String packageName : removedFunctionPackages) { + if (!allExistingStaticPackages.contains(packageName)) { + removedPackages.add(packageName); + } + } + + return removedPackages; + } + /** * This method returns a map of package names to a set of function ids that are in the static * metadata but not in the runtime metadata. @@ -404,13 +453,15 @@ public class MetadataSyncAdapter { /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */ @Nullable - private byte[] getCertificate(@NonNull String packageName) { + private byte[] getCertificate( + @NonNull PackageManager packageManager, @NonNull String packageName) { + Objects.requireNonNull(packageManager); Objects.requireNonNull(packageName); PackageInfo packageInfo; try { packageInfo = Objects.requireNonNull( - mPackageManager.getPackageInfo( + packageManager.getPackageInfo( packageName, PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES)); diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java new file mode 100644 index 0000000000000000000000000000000000000000..f421527e72d03b3b5a94a8b3af65defd9331b25b --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java @@ -0,0 +1,80 @@ +/* + * 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.server.appfunctions; + +import android.annotation.Nullable; +import android.app.appsearch.AppSearchManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +/** A Singleton class that manages per-user metadata sync adapters. */ +public final class MetadataSyncPerUser { + private static final String TAG = MetadataSyncPerUser.class.getSimpleName(); + + /** A map of per-user adapter for synchronizing appFunction metadata. */ + @GuardedBy("sLock") + private static final SparseArray sPerUserMetadataSyncAdapter = + new SparseArray<>(); + + private static final Object sLock = new Object(); + + /** + * Returns the per-user metadata sync adapter for the given user. + * + * @param user The user for which to get the metadata sync adapter. + * @param userContext The user context for the given user. + * @return The metadata sync adapter for the given user. + */ + @Nullable + public static MetadataSyncAdapter getPerUserMetadataSyncAdapter( + UserHandle user, Context userContext) { + synchronized (sLock) { + MetadataSyncAdapter metadataSyncAdapter = + sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null); + if (metadataSyncAdapter == null) { + AppSearchManager perUserAppSearchManager = + userContext.getSystemService(AppSearchManager.class); + PackageManager perUserPackageManager = userContext.getPackageManager(); + if (perUserAppSearchManager != null) { + metadataSyncAdapter = + new MetadataSyncAdapter( + AppFunctionExecutors.getPerUserSyncExecutor(user), + perUserPackageManager, + perUserAppSearchManager); + sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter); + return metadataSyncAdapter; + } + } + return metadataSyncAdapter; + } + } + + /** + * Removes the per-user metadata sync adapter for the given user. + * + * @param user The user for which to remove the metadata sync adapter. + */ + public static void removeUserSyncAdapter(UserHandle user) { + synchronized (sLock) { + sPerUserMetadataSyncAdapter.remove(user.getIdentifier()); + } + } +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java index 58597c38bb94d8a6082d1d521f411370641a9336..cd5c3831bc0dbe91ec0ccbafe84a6ce503f6ac79 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java @@ -43,7 +43,6 @@ public interface RemoteServiceCaller { * @param intent An Intent object that describes the service that should be bound. * @param bindFlags Flags used to control the binding process See {@link * android.content.Context#bindService}. - * @param timeoutInMillis The maximum time in milliseconds to wait for the service connection. * @param userHandle The UserHandle of the user for which the service should be bound. * @param callback A callback to be invoked for various events. See {@link * RunServiceCallCallback}. @@ -51,7 +50,6 @@ public interface RemoteServiceCaller { boolean runServiceCall( @NonNull Intent intent, int bindFlags, - long timeoutInMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback callback); @@ -75,11 +73,5 @@ public interface RemoteServiceCaller { /** Called when the service connection was failed to establish. */ void onFailedToConnect(); - - /** - * Called when the whole operation(i.e. binding and the service call) takes longer than - * allowed. - */ - void onTimedOut(); } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java index eea17eeca371a3ff239a5dfbcf94ae6bfcd33a5d..070a99d5bb28f41a2ae5646f23c8401c4f94afc8 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java @@ -62,12 +62,11 @@ public class RemoteServiceCallerImpl implements RemoteServiceCaller { public boolean runServiceCall( @NonNull Intent intent, int bindFlags, - long timeoutInMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback callback) { OneOffServiceConnection serviceConnection = new OneOffServiceConnection( - intent, bindFlags, timeoutInMillis, userHandle, callback); + intent, bindFlags, userHandle, callback); return serviceConnection.bindAndRun(); } @@ -76,28 +75,17 @@ public class RemoteServiceCallerImpl implements RemoteServiceCaller { implements ServiceConnection, ServiceUsageCompleteListener { private final Intent mIntent; private final int mFlags; - private final long mTimeoutMillis; private final UserHandle mUserHandle; private final RunServiceCallCallback mCallback; - private final Runnable mTimeoutCallback; OneOffServiceConnection( @NonNull Intent intent, int flags, - long timeoutMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback callback) { mIntent = intent; mFlags = flags; - mTimeoutMillis = timeoutMillis; mCallback = callback; - mTimeoutCallback = - () -> - mExecutor.execute( - () -> { - safeUnbind(); - mCallback.onTimedOut(); - }); mUserHandle = userHandle; } @@ -105,9 +93,7 @@ public class RemoteServiceCallerImpl implements RemoteServiceCaller { boolean bindServiceResult = mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle); - if (bindServiceResult) { - mHandler.postDelayed(mTimeoutCallback, mTimeoutMillis); - } else { + if(!bindServiceResult) { safeUnbind(); } @@ -141,7 +127,6 @@ public class RemoteServiceCallerImpl implements RemoteServiceCaller { private void safeUnbind() { try { - mHandler.removeCallbacks(mTimeoutCallback); mContext.unbindService(this); } catch (Exception ex) { Log.w(TAG, "Failed to unbind", ex); diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 2c261fe668fbbc1039bfba33252101aa2b6c1016..bd1b0ea99e1766eeb104452726a41ee2bef930bc 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -732,6 +732,7 @@ public final class PresentationStatsEventLogger { // These autofill id's are being refilled, so they had failed previously. // Note that these autofillIds correspond to the new autofill ids after relayout. event.mFailedAutofillIds = new ArraySet<>(autofillIds); + setHasRelayoutLog(); }); } @@ -753,6 +754,7 @@ public final class PresentationStatsEventLogger { int failureCount = ids.size(); if (isRefill) { event.mViewFailedOnRefillCount = failureCount; + setHasRelayoutLog(); } else { event.mViewFillFailureCount = failureCount; event.mViewFailedPriorToRefillCount = failureCount; @@ -833,6 +835,19 @@ public final class PresentationStatsEventLogger { }); } + /** + * Set the log contains relayout metrics. + * This is being added as a temporary measure to add logging. + * In future, when we map Session's old view states to the new autofill id's as part of fixing + * save for relayout cases, we no longer would need this. But till then, this is needed to set + * autofill logs for relayout cases. + */ + private void setHasRelayoutLog() { + mEventInternal.ifPresent(event -> { + event.mHasRelayoutLog = true; + }); + } + /** * Finish and log the event. */ @@ -844,8 +859,10 @@ public final class PresentationStatsEventLogger { } PresentationStatsEventInternal event = mEventInternal.get(); - boolean ignoreLogging = !event.mIsDatasetAvailable; - + boolean ignoreLogging = !event.mIsDatasetAvailable + && !event.mHasRelayoutLog + && !(event.mFixExpireResponseDuringAuthCount > 0) + && !(event.mNotifyViewEnteredIgnoredDuringAuthCount > 0); if (sVerbose) { Slog.v(TAG, "(" + caller + ") " + (ignoreLogging ? "IGNORING - following event won't be logged: " : "") @@ -1032,8 +1049,9 @@ public final class PresentationStatsEventLogger { ArraySet mFailedAutofillIds = new ArraySet<>(); ArraySet mAlreadyFilledAutofillIds = new ArraySet<>(); - // Not logged - used for internal logic + // Following are not logged and used only for internal logic boolean shouldResetShownCount = false; + boolean mHasRelayoutLog = false; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b109472a2a1e46884e14a0e2afea1f5ebb5c4338..2fa0e0d0d946bb3f99cc687c13337c8c5bd77b5e 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -720,6 +720,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, ViewState viewState) { + if (sVerbose) { + Slog.v(TAG, "handleInlineSuggestionRequest(): inline suggestion request received"); + } synchronized (mLock) { if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) { return; @@ -734,15 +737,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void maybeRequestFillLocked() { if (mPendingFillRequest == null) { + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " + + "due to empty pending fill request"); + } return; } mFieldClassificationIdSnapshot = sIdCounterForPcc.get(); if (mWaitForInlineRequest) { if (mPendingInlineSuggestionsRequest == null) { + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " + + "due to waiting for inline request and pending inline request is " + + "currently empty"); + } return; } - + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): adding inline request to pending " + + "fill request"); + } mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), mPendingFillRequest.getFillContexts(), mPendingFillRequest.getHints(), @@ -750,8 +765,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest, mPendingFillRequest.getDelayedFillIntentSender()); + } else { + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): not adding inline request to pending " + + "fill request"); + } } + mLastFillRequest = mPendingFillRequest; + if (sVerbose) { + Slog.v(TAG, "maybeRequestFillLocked(): sending fill request"); + } if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags()) && mSecondaryProviderHandler != null) { Slog.v(TAG, "Requesting fill response to secondary provider."); diff --git a/services/autofill/java/com/android/server/autofill/TEST_MAPPING b/services/autofill/java/com/android/server/autofill/TEST_MAPPING index d8a69177387d7fc6841c9ee76f1dbb8385225757..1dbeebe959045f1e661aa10329b217694386ad57 100644 --- a/services/autofill/java/com/android/server/autofill/TEST_MAPPING +++ b/services/autofill/java/com/android/server/autofill/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit-large": [ { - "name": "CtsAutoFillServiceTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsAutoFillServiceTestCases_android_server_autofill_Presubmit" } ] } diff --git a/services/backup/TEST_MAPPING b/services/backup/TEST_MAPPING index e1532309b78e818fbb353c80b75a0bba76cc1149..0c14e56932dfc16d7f135860b90b30ed8b3f899f 100644 --- a/services/backup/TEST_MAPPING +++ b/services/backup/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksMockingServicesTests", - "options": [ - { - "include-filter": "com.android.server.backup" - } - ] + "name": "FrameworksMockingServicesTests_backup" }, { "name": "CtsBackupTestCases", diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java index 46d60f9c85043809fb1842185e486a8365bc16f0..0c54720c53e4f8b153ddfc7ab07240661d984fc2 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java @@ -454,10 +454,10 @@ public final class AssociationDiskStore { @NonNull Associations associations) throws IOException { final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS); + writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId()); for (AssociationInfo association : associations.getAssociations()) { writeAssociation(serializer, association); } - writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId()); serializer.endTag(null, XML_TAG_ASSOCIATIONS); } diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS index 4fe0592f9075607b8809843aef829f6780b586be..4b732ac8e5a9018840480a32a09bdbdfc6acdc9c 100644 --- a/services/companion/java/com/android/server/companion/virtual/OWNERS +++ b/services/companion/java/com/android/server/companion/virtual/OWNERS @@ -2,7 +2,9 @@ set noparent -marvinramin@google.com vladokom@google.com +marvinramin@google.com +caen@google.com +biswarupp@google.com ogunwale@google.com michaelwr@google.com \ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING index caa877c2b9644fa25a3bcd55769cab36dd92c94a..14579c6fa3ad3b75ac77cdd8ab44007e26c3e066 100644 --- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING +++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING @@ -1,81 +1,32 @@ { "presubmit": [ { - "name": "CtsVirtualDevicesTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVirtualDevicesTestCases" }, { - "name": "CtsVirtualDevicesAudioTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVirtualDevicesAudioTestCases" }, { - "name": "CtsVirtualDevicesSensorTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVirtualDevicesSensorTestCases" }, { - "name": "CtsVirtualDevicesAppLaunchTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVirtualDevicesAppLaunchTestCases" }, { "name": "CtsVirtualDevicesCameraTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ], "keywords": ["primary-device"] }, { - "name": "CtsHardwareTestCases", - "options": [ - { - "include-filter": "android.hardware.input.cts.tests" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ], + "name": "CtsHardwareTestCases_cts_tests", "file_patterns": ["Virtual[^/]*\\.java"] }, { - "name": "CtsAccessibilityServiceTestCases", - "options": [ - { - "include-filter": "android.accessibilityservice.cts.AccessibilityDisplayProxyTest" - }, - { - "exclude-annotation": "android.support.test.filters.FlakyTest" - } - ] + "name": "CtsAccessibilityServiceTestCases_cts_accessibilitydisplayproxytest" } ], "postsubmit": [ { - "name": "CtsMediaAudioTestCases", - "options": [ - { - "include-filter": "android.media.audio.cts.AudioFocusWithVdmTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsMediaAudioTestCases_cts_audiofocuswithvdmtest" }, { "name": "CtsPermissionTestCases", @@ -92,12 +43,7 @@ ] }, { - "name": "CtsPermissionMultiDeviceTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsPermissionMultiDeviceTestCases" } ] } diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 6657c1c1ba8982868caa72773b10f1bcae22568c..59dea099c2a14fa08f069f1af19c24dd4457be1a 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -160,6 +160,7 @@ public final class BatteryService extends SystemService { private int mLastChargeCounter; private int mLastBatteryCycleCount; private int mLastChargingState; + private int mLastBatteryCapacityLevel; /** * The last seen charging policy. This requires the * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be @@ -609,7 +610,8 @@ public final class BatteryService extends SystemService { || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter || mInvalidCharger != mLastInvalidCharger || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount - || mHealthInfo.chargingState != mLastChargingState)) { + || mHealthInfo.chargingState != mLastChargingState + || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) { if (mPlugType != mLastPlugType) { if (mLastPlugType == BATTERY_PLUGGED_NONE) { @@ -829,6 +831,7 @@ public final class BatteryService extends SystemService { mLastInvalidCharger = mInvalidCharger; mLastBatteryCycleCount = mHealthInfo.batteryCycleCount; mLastChargingState = mHealthInfo.chargingState; + mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel; } } @@ -862,6 +865,7 @@ public final class BatteryService extends SystemService { intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah); intent.putExtra(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount); intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState); + intent.putExtra(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel); if (DEBUG) { Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE + ", info:" + mHealthInfo.toString()); @@ -964,6 +968,7 @@ public final class BatteryService extends SystemService { event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now); event.putInt(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount); event.putInt(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState); + event.putInt(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel); boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty(); mBatteryLevelsEventQueue.add(event); @@ -1401,6 +1406,7 @@ public final class BatteryService extends SystemService { pw.println(" technology: " + mHealthInfo.batteryTechnology); pw.println(" Charging state: " + mHealthInfo.chargingState); pw.println(" Charging policy: " + mHealthInfo.chargingPolicy); + pw.println(" Capacity level: " + mHealthInfo.batteryCapacityLevel); } else { Shell shell = new Shell(); shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null)); diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 30f3fd2f83a51d3b75740527387d092d04305d64..a459ea944008153f806355416011f79a556746f2 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -1,13 +1,7 @@ { "presubmit": [ { - "name": "CtsLocationFineTestCases", - "options": [ - { - // TODO: Wait for test to deflake - b/293934372 - "exclude-filter":"android.location.cts.fine.ScanningSettingsTest" - } - ] + "name": "CtsLocationFineTestCases_android_server_location" }, { "name": "CtsLocationCoarseTestCases" @@ -20,12 +14,7 @@ "file_patterns": ["NotificationManagerService\\.java"] }, { - "name": "CtsWindowManagerDeviceWindow", - "options": [ - { - "include-filter": "android.server.wm.window.ToastWindowTest" - } - ], + "name": "CtsWindowManagerDeviceWindow_window_toastwindowtest", "file_patterns": ["NotificationManagerService\\.java"] }, { @@ -103,12 +92,7 @@ "file_patterns": ["VcnManagementService\\.java"] }, { - "name": "FrameworksVpnTests", - "options": [ - { - "exclude-annotation": "com.android.testutils.SkipPresubmit" - } - ], + "name": "FrameworksVpnTests_android_server_connectivity", "file_patterns": ["VpnManagerService\\.java"] }, { @@ -123,6 +107,15 @@ "Background.*\\.java", "Activity.*\\.java" ] + }, + { + "name": "CtsOsTestCases", + "file_patterns": ["StorageManagerService\\.java"], + "options": [ + { + "include-filter": "android.os.storage.cts.StorageManagerTest" + } + ] } ], "presubmit-large": [ @@ -186,7 +179,7 @@ "file_patterns": ["StorageManagerService\\.java"], "options": [ { - "include-filter": "android.os.storage.cts.StorageManagerTest" + "include-filter": "android.os.storage.cts.StorageStatsManagerTest" } ] } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 1c13ad5b3ceb83c3567747a21355dccec41130f9..f32031dec43c263e8e0333a3ea2594a107dfded5 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -32,11 +32,11 @@ import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; import static android.app.UiModeManager.PROJECTION_TYPE_NONE; import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserHandle.getCallingUserId; -import static android.os.UserManager.isVisibleBackgroundUsersEnabled; import static android.provider.Settings.Secure.CONTRAST_LEVEL; import static android.util.TimeUtils.isTimeBetween; import static com.android.internal.util.FunctionalUtils.ignoreRemoteException; +import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled; import android.annotation.IntRange; import android.annotation.NonNull; @@ -100,7 +100,6 @@ import com.android.internal.app.DisableCarModeActivity; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.DumpUtils; -import com.android.server.pm.UserManagerService; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; @@ -850,7 +849,7 @@ final class UiModeManagerService extends SystemService { } final int user = UserHandle.getCallingUserId(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); final long ident = Binder.clearCallingIdentity(); try { @@ -914,7 +913,7 @@ final class UiModeManagerService extends SystemService { @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) { setAttentionModeThemeOverlay_enforcePermission(); - enforceValidCallingUser(UserHandle.getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(UserHandle.getCallingUserId()); synchronized (mLock) { if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) { @@ -1005,7 +1004,7 @@ final class UiModeManagerService extends SystemService { return false; } final int user = Binder.getCallingUserHandle().getIdentifier(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); if (user != mCurrentUser && getContext().checkCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS) @@ -1064,7 +1063,7 @@ final class UiModeManagerService extends SystemService { return; } final int user = UserHandle.getCallingUserId(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); final long ident = Binder.clearCallingIdentity(); try { @@ -1094,7 +1093,7 @@ final class UiModeManagerService extends SystemService { return; } final int user = UserHandle.getCallingUserId(); - enforceValidCallingUser(user); + enforceCurrentUserIfVisibleBackgroundEnabled(user); final long ident = Binder.clearCallingIdentity(); try { @@ -1116,7 +1115,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceValidCallingUser(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); synchronized (mLock) { if (mProjectionHolders == null) { @@ -1162,7 +1161,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceValidCallingUser(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); return releaseProjectionUnchecked(projectionType, callingPackage); } @@ -1204,7 +1203,7 @@ final class UiModeManagerService extends SystemService { return; } - enforceValidCallingUser(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); synchronized (mLock) { if (mProjectionListeners == null) { @@ -1253,32 +1252,6 @@ final class UiModeManagerService extends SystemService { } }; - // This method validates whether calling user is valid in visible background users - // feature. Valid user is the current user or the system or in the same profile group as - // the current user. - private void enforceValidCallingUser(int userId) { - if (!isVisibleBackgroundUsersEnabled()) { - return; - } - if (LOG) { - Slog.d(TAG, "enforceValidCallingUser: userId=" + userId - + " isSystemUser=" + (userId == USER_SYSTEM) + " current user=" + mCurrentUser - + " callingPid=" + Binder.getCallingPid() - + " callingUid=" + mInjector.getCallingUid()); - } - long ident = Binder.clearCallingIdentity(); - try { - if (userId != USER_SYSTEM && userId != mCurrentUser - && !UserManagerService.getInstance().isSameProfileGroup(userId, mCurrentUser)) { - throw new SecurityException( - "Calling user is not valid for level-1 compatibility in MUMD. " - + "callingUserId=" + userId + " currentUserId=" + mCurrentUser); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) { if ((p & PROJECTION_TYPE_AUTOMOTIVE) != 0) { getContext().enforceCallingPermission( diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index e2b6bd6ae360edee5fec52915d6fa5e9c413eb0a..d19899f03d716a327d4e558745bcc1e12799e0d6 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -489,7 +489,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { // Check subscription is active first; much cheaper/faster check, and an app (currently) // cannot be carrier privileged for inactive subscriptions. - if (subMgr.isValidSlotIndex(info.getSimSlotIndex()) + final int simSlotIndex = info.getSimSlotIndex(); + final boolean isValidSlotIndex = + simSlotIndex >= 0 && simSlotIndex < telMgr.getActiveModemCount(); + if (isValidSlotIndex && telMgr.checkCarrierPrivilegesForPackage(pkgName) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { // TODO (b/173717728): Allow configuration for inactive, but manageable diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 765afef4857d83d6ddb32d94b6e13fe667c638e0..88edb121c0c89694cd7ad36104b858aa353ad766 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -2169,6 +2169,9 @@ public class AccountManagerService Log.i(TAG, "callingUid=" + callingUid + ", userId=" + accounts.userId + " performing rename account"); Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName); + if (resultingAccount == null) { + resultingAccount = accountToRename; + } Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, resultingAccount.type); diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 34c3d7ec843370291345ac76b7079f84fcfb47ed..a73a991bc6a64dbeb5a2e23a25e28bf6a4911b03 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -74,6 +74,7 @@ import android.util.Slog; import android.util.Xml; import com.android.internal.R; +import com.android.internal.annotations.Keep; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.util.FrameworkStatsLog; @@ -214,7 +215,7 @@ public class AdbDebuggingManager { class PairingThread extends Thread implements NsdManager.RegistrationListener { private NsdManager mNsdManager; - private String mPublicKey; + @Keep private String mPublicKey; private String mPairingCode; private String mGuid; private String mServiceName; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3c574769eaec08c3f0073dc857279fd21261eb21..414a4e66591bc8c5b375b3c4dc97e56af470e4e9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -714,12 +714,14 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Map userId to its companion app uids. */ + @GuardedBy("mCompanionAppUidsMap") private final Map> mCompanionAppUidsMap = new ArrayMap<>(); /** * The profile owner UIDs. */ - private ArraySet mProfileOwnerUids = null; + @GuardedBy("mProfileOwnerUids") + private final ArraySet mProfileOwnerUids = new ArraySet<>(); final UserController mUserController; @VisibleForTesting @@ -4772,7 +4774,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!mConstants.mEnableWaitForFinishAttachApplication) { finishAttachApplicationInner(startSeq, callingUid, pid); } - maybeSendBootCompletedLocked(app); + maybeSendBootCompletedLocked(app, isRestrictedBackupMode); } catch (Exception e) { // We need kill the process group here. (b/148588589) Slog.wtf(TAG, "Exception thrown during bind of " + app, e); @@ -5017,7 +5019,7 @@ public class ActivityManagerService extends IActivityManager.Stub * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped, * or when the package first starts in private space */ - private void maybeSendBootCompletedLocked(ProcessRecord app) { + private void maybeSendBootCompletedLocked(ProcessRecord app, boolean isRestrictedBackupMode) { boolean sendBroadcast = false; if (android.os.Flags.allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures()) { @@ -5043,6 +5045,9 @@ public class ActivityManagerService extends IActivityManager.Stub RESTRICTION_REASON_USAGE, "unknown", RESTRICTION_SOURCE_USER, 0L); } + // Don't send BOOT_COMPLETED if currently in restricted backup mode + if (isRestrictedBackupMode) return; + if (!sendBroadcast) { if (!android.content.pm.Flags.stayStopped()) return; // Nothing to do if it wasn't previously stopped @@ -15299,10 +15304,8 @@ public class ActivityManagerService extends IActivityManager.Stub } psr.setReportedForegroundServiceTypes(fgServiceTypes); - ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked( - proc.getPid(), proc.info.uid); - item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES; - item.foregroundServiceTypes = fgServiceTypes; + mProcessList.enqueueProcessChangeItemLocked(proc.getPid(), proc.info.uid, + ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes); } if (oomAdj) { updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY); @@ -17532,32 +17535,35 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setProfileOwnerUid(ArraySet profileOwnerUids) { - synchronized (ActivityManagerService.this) { - mProfileOwnerUids = profileOwnerUids; + synchronized (mProfileOwnerUids) { + mProfileOwnerUids.clear(); + mProfileOwnerUids.addAll(profileOwnerUids); } } @Override public boolean isProfileOwner(int uid) { - synchronized (ActivityManagerService.this) { - return mProfileOwnerUids != null && mProfileOwnerUids.indexOf(uid) >= 0; + synchronized (mProfileOwnerUids) { + return mProfileOwnerUids.indexOf(uid) >= 0; } } @Override public void setCompanionAppUids(int userId, Set companionAppUids) { - synchronized (ActivityManagerService.this) { + synchronized (mCompanionAppUidsMap) { mCompanionAppUidsMap.put(userId, companionAppUids); } } @Override public boolean isAssociatedCompanionApp(int userId, int uid) { - final Set allUids = mCompanionAppUidsMap.get(userId); - if (allUids == null) { - return false; + synchronized (mCompanionAppUidsMap) { + final Set allUids = mCompanionAppUidsMap.get(userId); + if (allUids == null) { + return false; + } + return allUids.contains(uid); } - return allUids.contains(uid); } @Override diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 6bb56c9edcab8aaaff74fc20a83b159f81f24b94..e885c14afddbc0fc1c56683ed24d7a8c18b781db 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -79,6 +79,7 @@ final class CoreSettingsObserver extends ContentObserver { sSecureSettingToTypeMap.put(Settings.Secure.MULTI_PRESS_TIMEOUT, int.class); sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_TIMEOUT_MS, int.class); sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_DELAY_MS, int.class); + sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_ENABLED, int.class); sSecureSettingToTypeMap.put(Settings.Secure.STYLUS_POINTER_ICON_ENABLED, int.class); // add other secure settings here... diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index d6f04db5af55976c5cee09b3f6601e89aae0c5fe..1bcf8259afe829105b0d7c0c8fa8f51d6d9db1cc 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -1,16 +1,24 @@ # Applications & Processes -yamasani@google.com -jsharkey@google.com -hackbod@google.com -omakoto@google.com -ctate@google.com -huiyu@google.com -mwachens@google.com -sudheersai@google.com -suprabh@google.com -varunshah@google.com -bookatz@google.com -jji@google.com +per-file ActivityManager* = file:/ACTIVITY_MANAGER_OWNERS +per-file ActiveServices.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessList.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ActivityThread.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file SystemServer.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ServiceRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file AppProfiler.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessStateRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessServiceRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ForegroundServiceTypeLoggerModule.java = file:/ACTIVITY_MANAGER_OWNERS +per-file AppRestrictionController.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessErrorStateRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessProfileRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ConnectionRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file UidRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file IntentBindRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file AppFGSTracker.java = file:/ACTIVITY_MANAGER_OWNERS +per-file FgsTempAllowList.java = file:/ACTIVITY_MANAGER_OWNERS +per-file HostingRecord.java = file:/ACTIVITY_MANAGER_OWNERS # Windows & Activities ogunwale@google.com @@ -20,24 +28,19 @@ patb@google.com per-file AccessCheckDelegateHelper.java = file:/core/java/android/permission/OWNERS # Battery Stats -joeo@google.com +per-file AppBatteryTracker.java = file:/BATTERY_STATS_OWNERS per-file BatteryStats* = file:/BATTERY_STATS_OWNERS per-file BatteryExternalStats* = file:/BATTERY_STATS_OWNERS -# Londoners -michaelwr@google.com -narayan@google.com - # Voice Interaction per-file *Assist* = file:/core/java/android/service/voice/OWNERS per-file *Voice* = file:/core/java/android/service/voice/OWNERS -per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com - -per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS - -per-file ContentProviderHelper.java = varunshah@google.com, omakoto@google.com, jsharkey@google.com, yamasani@google.com +# Content Provider +per-file ContentProvider* = varunshah@google.com, yamasani@google.com +# Cached App Freezer +per-file ProcessCachedOptimizerRecord.java = file:/PERFORMANCE_OWNERS per-file CachedAppOptimizer.java = file:/PERFORMANCE_OWNERS per-file Freezer.java = file:/PERFORMANCE_OWNERS @@ -46,3 +49,23 @@ per-file User* = file:/MULTIUSER_OWNERS # Broadcasts per-file Broadcast* = file:/BROADCASTS_OWNERS + +# Permissions & Packages +per-file *Permission* = patb@google.com +per-file *Package* = patb@google.com + +# OOM Adjuster +per-file *Oom* = file:/OOM_ADJUSTER_OWNERS + +# Miscellaneous +per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com +per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS + +# Londoners +michaelwr@google.com #{LAST_RESORT_SUGGESTION} +narayan@google.com #{LAST_RESORT_SUGGESTION} + +# Default +hackbod@google.com #{LAST_RESORT_SUGGESTION} +omakoto@google.com #{LAST_RESORT_SUGGESTION} +yamasani@google.com #{LAST_RESORT_SUGGESTION} \ No newline at end of file diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index f0cc09f7c2326a8931d255c1fd892845dc21a161..78a0a117fe6f23d16ac42e50211edf1bd9a52593 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -73,6 +73,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; import static android.media.audio.Flags.roForegroundAudioControl; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; +import static android.os.Process.THREAD_GROUP_FOREGROUND_WINDOW; import static android.os.Process.THREAD_GROUP_RESTRICTED; import static android.os.Process.THREAD_GROUP_TOP_APP; import static android.os.Process.THREAD_PRIORITY_DISPLAY; @@ -116,6 +117,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; +import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND; @@ -1731,6 +1733,11 @@ public class OomAdjuster { // The recently used non-top visible freeform app. schedGroup = SCHED_GROUP_TOP_APP; mAdjType = "perceptible-freeform-activity"; + } else if ((flags + & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0) { + // Currently the only case is from freeform apps which are not close to top. + schedGroup = SCHED_GROUP_FOREGROUND_WINDOW; + mAdjType = "vis-multi-window-activity"; } foregroundActivities = true; mHasVisibleActivities = true; @@ -3438,6 +3445,9 @@ public class OomAdjuster { case SCHED_GROUP_RESTRICTED: processGroup = THREAD_GROUP_RESTRICTED; break; + case SCHED_GROUP_FOREGROUND_WINDOW: + processGroup = THREAD_GROUP_FOREGROUND_WINDOW; + break; default: processGroup = THREAD_GROUP_DEFAULT; break; @@ -3607,14 +3617,12 @@ public class OomAdjuster { if (changes != 0) { if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, "Changes in " + app + ": " + changes); - ActivityManagerService.ProcessChangeItem item = - mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid); - item.changes |= changes; - item.foregroundActivities = state.hasRepForegroundActivities(); + mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid, + changes, state.hasRepForegroundActivities()); if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "Item " + Integer.toHexString(System.identityHashCode(item)) - + " " + app.toShortString() + ": changes=" + item.changes - + " foreground=" + item.foregroundActivities + "Enqueued process change item for " + + app.toShortString() + ": changes=" + changes + + " foreground=" + state.hasRepForegroundActivities() + " type=" + state.getAdjType() + " source=" + state.getAdjSource() + " target=" + state.getAdjTarget()); } diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 21842db590b04124297ed1137bed65ec3e1664aa..fb1c2e9a1f9dfa4871fbb96f536da987c95b39d0 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -331,7 +331,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { void forEachNewNode(int slot, @NonNull Consumer callback) { ProcessRecordNode node = mLastNode[slot].mNext; final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL; - while (node != tail) { + while (node != null && node != tail) { mTmpOomAdjusterArgs.mApp = node.mApp; if (node.mApp == null) { // TODO(b/336178916) - Temporary logging for root causing b/336178916. @@ -365,7 +365,9 @@ public class OomAdjusterModernImpl extends OomAdjuster { } // Save the next before calling callback, since that may change the node.mNext. final ProcessRecordNode next = node.mNext; - callback.accept(mTmpOomAdjusterArgs); + if (mTmpOomAdjusterArgs.mApp != null) { + callback.accept(mTmpOomAdjusterArgs); + } // There are couple of cases: // a) The current node is moved to another slot // - for this case, we'd need to keep using the "next" node. diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index cb918a045ec6826649b0f2c632c76b7e0d9e0072..a93ae72fcfeac82884685f3fb993b2c845d3b961 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -303,6 +303,9 @@ public final class ProcessList { // Activity manager's version of Process.THREAD_GROUP_TOP_APP // Disambiguate between actual top app and processes bound to the top app static final int SCHED_GROUP_TOP_APP_BOUND = 4; + // Activity manager's version of Process.THREAD_GROUP_FOREGROUND_WINDOW + // The priority is like between default and top-app. + static final int SCHED_GROUP_FOREGROUND_WINDOW = 5; // The minimum number of cached apps we want to be able to keep around, // without empty apps being able to push them out of memory. @@ -4995,50 +4998,67 @@ public final class ProcessList { } @GuardedBy("mService") - ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) { + void enqueueProcessChangeItemLocked(int pid, int uid, int changes, int foregroundServicetypes) { synchronized (mProcessChangeLock) { - int i = mPendingProcessChanges.size() - 1; - ActivityManagerService.ProcessChangeItem item = null; - while (i >= 0) { - item = mPendingProcessChanges.get(i); - if (item.pid == pid) { - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item); - } - break; + final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid); + item.changes |= changes; + item.foregroundServiceTypes = foregroundServicetypes; + } + } + + @GuardedBy("mService") + void enqueueProcessChangeItemLocked(int pid, int uid, int changes, + boolean hasForegroundActivities) { + synchronized (mProcessChangeLock) { + final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid); + item.changes |= changes; + item.foregroundActivities = hasForegroundActivities; + } + } + + @GuardedBy({"mService", "mProcessChangeLock"}) + private ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) { + int i = mPendingProcessChanges.size() - 1; + ActivityManagerService.ProcessChangeItem item = null; + while (i >= 0) { + item = mPendingProcessChanges.get(i); + if (item.pid == pid) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item); } - i--; + break; } + i--; + } - if (i < 0) { - // No existing item in pending changes; need a new one. - final int num = mAvailProcessChanges.size(); - if (num > 0) { - item = mAvailProcessChanges.remove(num - 1); - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item); - } - } else { - item = new ActivityManagerService.ProcessChangeItem(); - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item); - } + if (i < 0) { + // No existing item in pending changes; need a new one. + final int num = mAvailProcessChanges.size(); + if (num > 0) { + item = mAvailProcessChanges.remove(num - 1); + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item); } - item.changes = 0; - item.pid = pid; - item.uid = uid; - if (mPendingProcessChanges.size() == 0) { - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!"); - } - mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG) - .sendToTarget(); + } else { + item = new ActivityManagerService.ProcessChangeItem(); + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item); } - mPendingProcessChanges.add(item); } - - return item; + item.changes = 0; + item.pid = pid; + item.uid = uid; + if (mPendingProcessChanges.size() == 0) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!"); + } + mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG) + .sendToTarget(); + } + mPendingProcessChanges.add(item); } + + return item; } @GuardedBy("mService") diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index ab5e2d04cecd46c7d8ba0af40fcff6c8b8b826a1..6383dcb000ab6b494c16e891dca90ad7c24d63ea 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -22,32 +22,10 @@ ] }, { - "name": "CtsAppFgsTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsAppFgsTestCases_pm_Presubmit" }, { - "name": "CtsShortFgsTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsShortFgsTestCases_pm_Presubmit" }, { "name": "FrameworksServicesTests_android_server_am_Presubmit" @@ -73,23 +51,14 @@ }, { "file_patterns": ["Broadcast.*"], - "name": "CtsBroadcastTestCases", - "options": [ - { "exclude-annotation": "androidx.test.filters.LargeTest" }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, - { "exclude-annotation": "org.junit.Ignore" } - ] + "name": "CtsBroadcastTestCases_android_server_am" }, { - "name": "CtsBRSTestCases", "file_patterns": [ "ActivityManagerService\\.java", "BroadcastQueue\\.java" ], - "options": [ - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, - { "exclude-annotation": "org.junit.Ignore" } - ] + "name": "CtsBRSTestCases" } ], "postsubmit": [ @@ -110,13 +79,7 @@ ] }, { - "name": "CtsStatsdAtomHostTestCases", - "options": [ - { "include-filter": "android.cts.statsdatom.appexit.AppExitHostTest" }, - { "exclude-annotation": "androidx.test.filters.LargeTest" }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, - { "exclude-annotation": "org.junit.Ignore" } - ] + "name": "CtsStatsdAtomHostTestCases_appexit_appexithosttest" }, { "name": "CtsContentTestCases", diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index b186eaacab74f114bd5451b1a6912b08bca4535f..262c76e4a4d70911dd8c7a7ab19d919e1f9fc3e7 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -156,6 +156,9 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -222,19 +225,10 @@ class UserController implements Handler.Callback { */ private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000; - /** - * Amount of time waited for - * {@link ActivityTaskManagerInternal.ScreenObserver#onKeyguardStateChanged} callbacks to be - * called after calling {@link WindowManagerService#lockDeviceNow}. - * Otherwise, we should throw a {@link RuntimeException} and never dismiss the - * {@link UserSwitchingDialog}. - */ - static final int SHOW_KEYGUARD_TIMEOUT_MS = 20 * 1000; - /** * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be * called after dismissing the keyguard. - * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog}} + * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()} * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}. */ private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000; @@ -1986,10 +1980,18 @@ class UserController implements Handler.Callback { // it should be moved outside, but for now it's not as there are many calls to // external components here afterwards updateProfileRelatedCaches(); + dispatchOnBeforeUserSwitching(userId); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); + // Once the internal notion of the active user has switched, we lock the device + // with the option to show the user switcher on the keyguard. if (userSwitchUiEnabled) { mInjector.getWindowManager().setSwitchingUser(true); + // Only lock if the user has a secure keyguard PIN/Pattern/Pwd + if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { + // Make sure the device is locked before moving on with the user switch + mInjector.lockDeviceNowAndWaitForKeyguardShown(); + } } } else { @@ -2284,6 +2286,25 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } + private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId) { + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId); + final int observerCount = mUserSwitchObservers.beginBroadcast(); + for (int i = 0; i < observerCount; i++) { + final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); + t.traceBegin("onBeforeUserSwitching-" + name); + try { + mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId); + } catch (RemoteException e) { + // Ignore + } finally { + t.traceEnd(); + } + } + mUserSwitchObservers.finishBroadcast(); + t.traceEnd(); + } + /** Called on handler thread */ @VisibleForTesting void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) { @@ -2499,17 +2520,6 @@ class UserController implements Handler.Callback { final int observerCount = mUserSwitchObservers.beginBroadcast(); if (observerCount > 0) { - for (int i = 0; i < observerCount; i++) { - final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); - t.traceBegin("onBeforeUserSwitching-" + name); - try { - mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId); - } catch (RemoteException e) { - // Ignore - } finally { - t.traceEnd(); - } - } final ArraySet curWaitingUserSwitchCallbacks = new ArraySet<>(); synchronized (mLock) { uss.switching = true; @@ -2606,54 +2616,32 @@ class UserController implements Handler.Callback { @VisibleForTesting void completeUserSwitch(int oldUserId, int newUserId) { - final Runnable sendUserSwitchCompleteMessage = () -> { - mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); - mHandler.sendMessage(mHandler.obtainMessage( - REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); - }; - if (isUserSwitchUiEnabled()) { - if (mInjector.getKeyguardManager().isDeviceSecure(newUserId)) { - this.showKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); - } else { - this.dismissKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); - } - } else { - sendUserSwitchCompleteMessage.run(); - } - } - - protected void showKeyguard(Runnable runnable) { - runWithTimeout(mInjector::showKeyguard, SHOW_KEYGUARD_TIMEOUT_MS, runnable, () -> { - throw new RuntimeException( - "Keyguard is not shown in " + SHOW_KEYGUARD_TIMEOUT_MS + " ms."); - }, "showKeyguard"); - } - - protected void dismissKeyguard(Runnable runnable) { - runWithTimeout(mInjector::dismissKeyguard, DISMISS_KEYGUARD_TIMEOUT_MS, runnable, runnable, - "dismissKeyguard"); + final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled(); + // serialize each conditional step + await( + // STEP 1 - If there is no challenge set, dismiss the keyguard right away + isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId), + mInjector::dismissKeyguard, + () -> await( + // STEP 2 - If user switch ui was enabled, dismiss user switch dialog + isUserSwitchUiEnabled, + this::dismissUserSwitchDialog, + () -> { + // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast + // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete + mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); + mHandler.sendMessage(mHandler.obtainMessage( + REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); + } + )); } - private void runWithTimeout(Consumer task, int timeoutMs, Runnable onSuccess, - Runnable onTimeout, String traceMsg) { - final AtomicInteger state = new AtomicInteger(0); // state = 0 (RUNNING) - - asyncTraceBegin(traceMsg, 0); - - mHandler.postDelayed(() -> { - if (state.compareAndSet(0, 1)) { // state = 1 (TIMEOUT) - asyncTraceEnd(traceMsg, 0); - Slogf.w(TAG, "Timeout: %s did not finish in %d ms", traceMsg, timeoutMs); - onTimeout.run(); - } - }, timeoutMs); - - task.accept(() -> { - if (state.compareAndSet(0, 2)) { // state = 2 (SUCCESS) - asyncTraceEnd(traceMsg, 0); - onSuccess.run(); - } - }); + private void await(boolean condition, Consumer conditionalStep, Runnable nextStep) { + if (condition) { + conditionalStep.accept(nextStep); + } else { + nextStep.run(); + } } private void moveUserToForeground(UserState uss, int newUserId) { @@ -4100,45 +4088,29 @@ class UserController implements Handler.Callback { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } - protected void showKeyguard(Runnable runnable) { - if (getWindowManager().isKeyguardLocked()) { - runnable.run(); - return; - } - getActivityTaskManagerInternal().registerScreenObserver( - new ActivityTaskManagerInternal.ScreenObserver() { - @Override - public void onAwakeStateChanged(boolean isAwake) { - - } - - @Override - public void onKeyguardStateChanged(boolean isShowing) { - if (isShowing) { - getActivityTaskManagerInternal().unregisterScreenObserver(this); - runnable.run(); - } - } - } - ); - getWindowManager().lockDeviceNow(); - } - protected void dismissKeyguard(Runnable runnable) { + final AtomicBoolean isFirst = new AtomicBoolean(true); + final Runnable runOnce = () -> { + if (isFirst.getAndSet(false)) { + runnable.run(); + } + }; + + mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS); getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() { @Override public void onDismissError() throws RemoteException { - runnable.run(); + mHandler.post(runOnce); } @Override public void onDismissSucceeded() throws RemoteException { - runnable.run(); + mHandler.post(runOnce); } @Override public void onDismissCancelled() throws RemoteException { - runnable.run(); + mHandler.post(runOnce); } }, /* message= */ null); } @@ -4164,5 +4136,43 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } + + void lockDeviceNowAndWaitForKeyguardShown() { + if (getWindowManager().isKeyguardLocked()) { + return; + } + + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("lockDeviceNowAndWaitForKeyguardShown"); + + final CountDownLatch latch = new CountDownLatch(1); + ActivityTaskManagerInternal.ScreenObserver screenObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + latch.countDown(); + } + } + }; + + getActivityTaskManagerInternal().registerScreenObserver(screenObserver); + getWindowManager().lockDeviceNow(); + try { + if (!latch.await(20, TimeUnit.SECONDS)) { + throw new RuntimeException("Keyguard is not shown in 20 seconds"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); + t.traceEnd(); + } + } } } diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING index b718ce62c1184e260685b89333ca882f6557898f..9e76175ae866cd7111568c0d43c09360599b810a 100644 --- a/services/core/java/com/android/server/app/TEST_MAPPING +++ b/services/core/java/com/android/server/app/TEST_MAPPING @@ -1,26 +1,10 @@ { "presubmit": [ { - "name": "CtsGameManagerTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsGameManagerTestCases" }, { - "name": "CtsStatsdAtomHostTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.cts.statsdatom.gamemanager" - } - ], + "name": "CtsStatsdAtomHostTestCases_statsdatom_gamemanager", "file_patterns": [ "(/|^)GameManagerService.java" ] @@ -29,18 +13,7 @@ "name": "FrameworksMockingServicesTests_android_server_app" }, { - "name": "FrameworksCoreGameManagerTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.app" - } - ], + "name": "FrameworksCoreGameManagerTests_android_app", "file_patterns": [ "(/|^)GameManagerService.java", "(/|^)GameManagerSettings.java" ] diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING index 9317c1eda088678383e22de4d66d8432ec076aee..25dd30b0226ac53328bcbd292b38d4b979e135ff 100644 --- a/services/core/java/com/android/server/appop/TEST_MAPPING +++ b/services/core/java/com/android/server/appop/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsAppOpsTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsAppOpsTestCases" }, { "name": "CtsAppOps2TestCases" @@ -21,12 +16,7 @@ "name": "CtsPermissionTestCases_Platform" }, { - "name": "CtsAppTestCases", - "options": [ - { - "include-filter": "android.app.cts.ActivityManagerApi29Test" - } - ] + "name": "CtsAppTestCases_cts_activitymanagerapi29test" }, { "name": "CtsStatsdAtomHostTestCases", diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING index e5b034415824cea9959b98894c15b004f8a2cbf8..519ed071830d7427bfb275c7297e172b1f240b7e 100644 --- a/services/core/java/com/android/server/attention/TEST_MAPPING +++ b/services/core/java/com/android/server/attention/TEST_MAPPING @@ -1,24 +1,7 @@ { "presubmit": [ { - "name": "CtsVoiceInteractionTestCases", - "options": [ - { - "include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest" - }, - { - "include-filter": "android.voiceinteraction.cts.unittests.HotwordDetectedResultTest" - }, - { - "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest" - }, - { - "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceProximityTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVoiceInteractionTestCases_android_server_attention" } ] } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 1cf9935217138b691753dc5aa36318eca51008a9..55d9c6eac87aa782a0b520d1fb84c08c8bd4c7b2 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -533,7 +533,8 @@ public class AudioDeviceBroker { AudioDeviceInfo.TYPE_BLE_SPEAKER, AudioDeviceInfo.TYPE_LINE_ANALOG, AudioDeviceInfo.TYPE_HDMI, - AudioDeviceInfo.TYPE_AUX_LINE + AudioDeviceInfo.TYPE_AUX_LINE, + AudioDeviceInfo.TYPE_BUS }; /*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) { @@ -541,7 +542,8 @@ public class AudioDeviceBroker { return device.isSink() && isValidCommunicationDeviceType(device.getType()); } - private static boolean isValidCommunicationDeviceType(int deviceType) { + private static boolean isValidCommunicationDeviceType( + @AudioDeviceInfo.AudioDeviceType int deviceType) { for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { if (deviceType == type) { return true; @@ -740,7 +742,8 @@ public class AudioDeviceBroker { * @param deviceType the device type the query applies to. * @return true if this device type is requested for communication. */ - private boolean isDeviceRequestedForCommunication(int deviceType) { + private boolean isDeviceRequestedForCommunication( + @AudioDeviceInfo.AudioDeviceType int deviceType) { synchronized (mDeviceStateLock) { AudioDeviceAttributes device = requestedCommunicationDevice(); return device != null && device.getType() == deviceType; @@ -754,7 +757,8 @@ public class AudioDeviceBroker { * @param deviceType the device type the query applies to. * @return true if this device type is requested for communication. */ - private boolean isDeviceOnForCommunication(int deviceType) { + private boolean isDeviceOnForCommunication( + @AudioDeviceInfo.AudioDeviceType int deviceType) { synchronized (mDeviceStateLock) { AudioDeviceAttributes device = preferredCommunicationDevice(); return device != null && device.getType() == deviceType; @@ -768,7 +772,8 @@ public class AudioDeviceBroker { * @param deviceType the device type the query applies to. * @return true if this device type is requested for communication. */ - private boolean isDeviceActiveForCommunication(int deviceType) { + private boolean isDeviceActiveForCommunication( + @AudioDeviceInfo.AudioDeviceType int deviceType) { return mActiveCommunicationDevice != null && mActiveCommunicationDevice.getType() == deviceType && mPreferredCommunicationDevice != null diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c3d09bb674527d1648d222b47d42206261976fd7..df69afe6ed796b02928c6cd3a6ce308776963cb0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4006,6 +4006,7 @@ public class AudioService extends IAudioService.Stub && isFullVolumeDevice(device); boolean tvConditions = mHdmiTvClient != null && mHdmiSystemAudioSupported + && isFullVolumeDevice(device) && !isAbsoluteVolumeDevice(device) && !isA2dpAbsoluteVolumeDevice(device); @@ -10808,7 +10809,8 @@ public class AudioService extends IAudioService.Stub //TODO move inside HardeningEnforcer after refactor that moves permission checks // in the blockFocusMethod if (permissionOverridesCheck) { - mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, focusReqType, uid); + mHardeningEnforcer.metricsLogFocusReq(/*blocked*/ false, focusReqType, uid, + /*unblockedBySdk*/ false); } if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid, HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS, @@ -13382,19 +13384,39 @@ public class AudioService extends IAudioService.Stub } @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) - /** @see AudioPolicy#getFocusStack() */ + /* @see AudioPolicy#getFocusStack() */ public List getFocusStack() { super.getFocusStack_enforcePermission(); return mMediaFocusControl.getFocusStack(); } - /** @see AudioPolicy#sendFocusLoss */ + /** + * @param focusLoser non-null entry that may be in the stack + * @see AudioPolicy#sendFocusLossAndUpdate(AudioFocusInfo) + */ + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) + public void sendFocusLossAndUpdate(@NonNull AudioFocusInfo focusLoser, + @NonNull IAudioPolicyCallback apcb) { + super.sendFocusLossAndUpdate_enforcePermission(); + Objects.requireNonNull(apcb); + if (!mAudioPolicies.containsKey(apcb.asBinder())) { + throw new IllegalStateException("Only registered AudioPolicy can change focus"); + } + if (!mAudioPolicies.get(apcb.asBinder()).mHasFocusListener) { + throw new IllegalStateException("AudioPolicy must have focus listener to change focus"); + } + + mMediaFocusControl.sendFocusLossAndUpdate(Objects.requireNonNull(focusLoser)); + } + + /* @see AudioPolicy#sendFocusLoss(AudioFocusInfo) */ + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser, @NonNull IAudioPolicyCallback apcb) { + super.sendFocusLoss_enforcePermission(); Objects.requireNonNull(focusLoser); Objects.requireNonNull(apcb); - enforceModifyAudioRoutingPermission(); if (!mAudioPolicies.containsKey(apcb.asBinder())) { throw new IllegalStateException("Only registered AudioPolicy can change focus"); } diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java index faeba5d068fc1588801f5e75cf83256d735e2739..661111070aaefd039d1f47460b4c03ef85c82759 100644 --- a/services/core/java/com/android/server/audio/HardeningEnforcer.java +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -168,6 +168,8 @@ public class HardeningEnforcer { } boolean blocked = true; + // indicates the focus request was not blocked because of the SDK version + boolean unblockedBySdk = false; if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) { if (DEBUG) { Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking"); @@ -179,9 +181,10 @@ public class HardeningEnforcer { + targetSdk); } blocked = false; + unblockedBySdk = true; } - metricsLogFocusReq(blocked, focusReqType, callingUid); + metricsLogFocusReq(blocked, focusReqType, callingUid, unblockedBySdk); if (!blocked) { return false; @@ -195,7 +198,16 @@ public class HardeningEnforcer { return true; } - /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) { + /** + * Log metrics for the focus request + * @param blocked true if the call blocked + * @param focusReq the type of focus request + * @param callingUid the UID of the caller + * @param unblockedBySdk if blocked is false, + * true indicates it was unblocked thanks to an older SDK + */ + /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid, + boolean unblockedBySdk) { final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq) : METRIC_COUNTERS_FOCUS_GRANT.get(focusReq); if (TextUtils.isEmpty(metricId)) { @@ -204,6 +216,12 @@ public class HardeningEnforcer { } try { Counter.logIncrementWithUid(metricId, callingUid); + if (!blocked && unblockedBySdk) { + // additional metric to capture focus requests that are currently granted + // because the app is on an older SDK, but would have been blocked otherwise + Counter.logIncrementWithUid( + "media_audio.value_audio_focus_grant_hardening_waived_by_sdk", callingUid); + } } catch (Exception e) { Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq + " from uid:" + callingUid, e); diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 7e263560b8a1036ac18545bbd34c2b246197b310..b4af46efcb38d7ab71007b9b6af6e2e5c18e7fd3 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -279,6 +279,37 @@ public class MediaFocusControl implements PlayerFocusEnforcer { return true; } + /** + * Like {@link #sendFocusLoss(AudioFocusInfo)} but if the loser was at the top of stack, + * make the next entry gain focus with {@link AudioManager#AUDIOFOCUS_GAIN}. + * @param focusInfo the focus owner to discard + * @see AudioPolicy#sendFocusLossAndUpdate(AudioFocusInfo) + */ + protected void sendFocusLossAndUpdate(@NonNull AudioFocusInfo focusInfo) { + synchronized (mAudioFocusLock) { + if (mFocusStack.isEmpty()) { + return; + } + final FocusRequester currentFocusOwner = mFocusStack.peek(); + if (currentFocusOwner.toAudioFocusInfo().equals(focusInfo)) { + // focus loss is for the top of the stack + currentFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, + false /*forceDuck*/); + currentFocusOwner.release(); + + mFocusStack.pop(); + // is there a new focus owner? + if (!mFocusStack.isEmpty()) { + mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); + } + } else { + // focus loss if for another entry that's not at the top of the stack, + // just remove it from the stack and make it lose focus + sendFocusLoss(focusInfo); + } + } + } + /** * Return a copy of the focus stack for external consumption (composed of AudioFocusInfo * instead of FocusRequester instances) diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java index 88ff7e4103f98f157ea9583f6de4125cf58fd433..2cadbc58d0f2270589ac19cce5a012f39c994a9f 100644 --- a/services/core/java/com/android/server/cpu/CpuMonitorService.java +++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java @@ -216,7 +216,7 @@ public final class CpuMonitorService extends SystemService { @Override public void onBootPhase(int phase) { - if (phase != PHASE_BOOT_COMPLETED) { + if (phase != PHASE_BOOT_COMPLETED || mHandler == null) { return; } Slogf.i(TAG, "Stopping periodic cpuset reading on boot complete"); diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index 38eb416ffdd8a4096cbeafd50663c73889903e0b..ddea285d3564cb3745842b8ff43a4c552a243f77 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -109,7 +109,7 @@ public class DisplayControl { /** * Sets the HDR conversion mode for the device. * - * Returns the system preferred Hdr output type nn case when HDR conversion mode is + * Returns the system preferred HDR output type in case when HDR conversion mode is * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM}. * Returns Hdr::INVALID in other cases. * @hide diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 93bd926144033d41b95838e7d59dd31b1341bfc9..acf4db30ba939878f5c8c61b05b6f29ad68e6a67 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -318,13 +318,16 @@ final class DisplayDeviceInfo { */ public Display.HdrCapabilities hdrCapabilities; + /** When true, all HDR capabilities are hidden from public APIs */ + public boolean isForceSdr; + /** * Indicates whether this display supports Auto Low Latency Mode. */ public boolean allmSupported; /** - * Indicates whether this display suppors Game content type. + * Indicates whether this display supports Game content type. */ public boolean gameContentTypeSupported; @@ -516,6 +519,7 @@ final class DisplayDeviceInfo { || !Arrays.equals(supportedModes, other.supportedModes) || !Arrays.equals(supportedColorModes, other.supportedColorModes) || !Objects.equals(hdrCapabilities, other.hdrCapabilities) + || isForceSdr != other.isForceSdr || allmSupported != other.allmSupported || gameContentTypeSupported != other.gameContentTypeSupported || densityDpi != other.densityDpi @@ -560,6 +564,7 @@ final class DisplayDeviceInfo { colorMode = other.colorMode; supportedColorModes = other.supportedColorModes; hdrCapabilities = other.hdrCapabilities; + isForceSdr = other.isForceSdr; allmSupported = other.allmSupported; gameContentTypeSupported = other.gameContentTypeSupported; densityDpi = other.densityDpi; @@ -603,6 +608,7 @@ final class DisplayDeviceInfo { sb.append(", colorMode ").append(colorMode); sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes)); sb.append(", hdrCapabilities ").append(hdrCapabilities); + sb.append(", isForceSdr ").append(isForceSdr); sb.append(", allmSupported ").append(allmSupported); sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported); sb.append(", density ").append(densityDpi); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 3c2167e7a4ef03066685e1a74c9610c1ff317078..e7fd8f7db182d26b2ba4fd8b64458798a7dfa3ee 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -48,6 +48,7 @@ import static android.os.Process.ROOT_UID; import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL; import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH; import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN; +import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.server.display.layout.Layout.Display.POSITION_REAR; @@ -284,7 +285,7 @@ public final class DisplayManagerService extends SystemService { @GuardedBy("mSyncRoot") private int[] mUserDisabledHdrTypes = {}; @Display.HdrCapabilities.HdrType - private int[] mSupportedHdrOutputType; + private int[] mSupportedHdrOutputTypes; @GuardedBy("mSyncRoot") private boolean mAreUserDisabledHdrTypesAllowed = true; @@ -299,10 +300,10 @@ public final class DisplayManagerService extends SystemService { // HDR conversion mode chosen by user @GuardedBy("mSyncRoot") private HdrConversionMode mHdrConversionMode = null; - // Actual HDR conversion mode, which takes app overrides into account. - private HdrConversionMode mOverrideHdrConversionMode = null; + // Whether app has disabled HDR conversion + private boolean mShouldDisableHdrConversion = false; @GuardedBy("mSyncRoot") - private int mSystemPreferredHdrOutputType = Display.HdrCapabilities.HDR_TYPE_INVALID; + private int mSystemPreferredHdrOutputType = HDR_TYPE_INVALID; // The synchronization root for the display manager. @@ -1419,7 +1420,8 @@ public final class DisplayManagerService extends SystemService { } } - private void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) { + @VisibleForTesting + void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) { synchronized (mSyncRoot) { if (userDisabledHdrTypes == null) { Slog.e(TAG, "Null is not an expected argument to " @@ -1437,6 +1439,7 @@ public final class DisplayManagerService extends SystemService { if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) { return; } + String userDisabledFormatsString = ""; if (userDisabledHdrTypes.length != 0) { userDisabledFormatsString = TextUtils.join(",", @@ -1452,6 +1455,15 @@ public final class DisplayManagerService extends SystemService { handleLogicalDisplayChangedLocked(display); }); } + /* Note: it may be expected to reset the Conversion Mode when an HDR type is enabled + and the Conversion Mode is set to System Preferred. This is handled in the Settings + code because in the special case where HDR is indirectly disabled by Force SDR + Conversion, manually enabling HDR is not recognized as an action that reduces the + disabled HDR count. Thus, this case needs to be checked in the Settings code when we + know we're enabling an HDR mode. If we split checking for SystemConversion and + isForceSdr in two places, we may have duplicate calls to resetting to System Conversion + and get two black screens. + */ } } @@ -1464,19 +1476,20 @@ public final class DisplayManagerService extends SystemService { return true; } - private void setAreUserDisabledHdrTypesAllowedInternal( + @VisibleForTesting + void setAreUserDisabledHdrTypesAllowedInternal( boolean areUserDisabledHdrTypesAllowed) { synchronized (mSyncRoot) { if (mAreUserDisabledHdrTypesAllowed == areUserDisabledHdrTypesAllowed) { return; } mAreUserDisabledHdrTypesAllowed = areUserDisabledHdrTypesAllowed; - if (mUserDisabledHdrTypes.length == 0) { - return; - } Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED, areUserDisabledHdrTypesAllowed ? 1 : 0); + if (mUserDisabledHdrTypes.length == 0) { + return; + } int userDisabledHdrTypes[] = {}; if (!mAreUserDisabledHdrTypesAllowed) { userDisabledHdrTypes = mUserDisabledHdrTypes; @@ -1487,6 +1500,14 @@ public final class DisplayManagerService extends SystemService { display.setUserDisabledHdrTypes(finalUserDisabledHdrTypes); handleLogicalDisplayChangedLocked(display); }); + // When HDR conversion mode is set to SYSTEM, modification to + // areUserDisabledHdrTypesAllowed requires refreshing the HDR conversion mode to tell + // the system which HDR types it is not allowed to use. + if (getHdrConversionModeInternal().getConversionMode() + == HdrConversionMode.HDR_CONVERSION_SYSTEM) { + setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + } } } @@ -2357,7 +2378,7 @@ public final class DisplayManagerService extends SystemService { final int preferredHdrOutputType = hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE ? hdrConversionMode.getPreferredHdrOutputType() - : Display.HdrCapabilities.HDR_TYPE_INVALID; + : HDR_TYPE_INVALID; Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType); } @@ -2370,7 +2391,7 @@ public final class DisplayManagerService extends SystemService { ? Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.HDR_FORCE_CONVERSION_TYPE, Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION) - : Display.HdrCapabilities.HDR_TYPE_INVALID; + : HDR_TYPE_INVALID; mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType); setHdrConversionModeInternal(mHdrConversionMode); } @@ -2507,22 +2528,38 @@ public final class DisplayManagerService extends SystemService { }); } + /** + * Returns the HDR output types that are supported by the device's HDR conversion capabilities, + * stripping out any user-disabled HDR types if mAreUserDisabledHdrTypesAllowed is false. + */ @GuardedBy("mSyncRoot") - private int[] getEnabledAutoHdrTypesLocked() { - IntArray autoHdrOutputTypesArray = new IntArray(); + @VisibleForTesting + int[] getEnabledHdrOutputTypesLocked() { + if (mAreUserDisabledHdrTypesAllowed) { + return getSupportedHdrOutputTypesInternal(); + } + // Strip out all HDR formats that are currently user-disabled + IntArray enabledHdrOutputTypesArray = new IntArray(); for (int type : getSupportedHdrOutputTypesInternal()) { - boolean isDisabled = false; + boolean isEnabled = true; for (int disabledType : mUserDisabledHdrTypes) { if (type == disabledType) { - isDisabled = true; + isEnabled = false; break; } } - if (!isDisabled) { - autoHdrOutputTypesArray.add(type); + if (isEnabled) { + enabledHdrOutputTypesArray.add(type); } } - return autoHdrOutputTypesArray.toArray(); + return enabledHdrOutputTypesArray.toArray(); + } + + @VisibleForTesting + int[] getEnabledHdrOutputTypes() { + synchronized (mSyncRoot) { + return getEnabledHdrOutputTypesLocked(); + } } @GuardedBy("mSyncRoot") @@ -2531,7 +2568,7 @@ public final class DisplayManagerService extends SystemService { final int preferredHdrOutputType = mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType(); - if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) { + if (preferredHdrOutputType != HDR_TYPE_INVALID) { int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency(); return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType); } @@ -2565,41 +2602,57 @@ public final class DisplayManagerService extends SystemService { if (!mInjector.getHdrOutputConversionSupport()) { return; } - int[] autoHdrOutputTypes = null; + synchronized (mSyncRoot) { if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM && hdrConversionMode.getPreferredHdrOutputType() - != Display.HdrCapabilities.HDR_TYPE_INVALID) { + != HDR_TYPE_INVALID) { throw new IllegalArgumentException("preferredHdrOutputType must not be set if" + " the conversion mode is HDR_CONVERSION_SYSTEM"); } mHdrConversionMode = hdrConversionMode; storeHdrConversionModeLocked(mHdrConversionMode); - // For auto mode, all supported HDR types are allowed except the ones specifically - // disabled by the user. + // If the HDR conversion is HDR_CONVERSION_SYSTEM, all supported HDR types are allowed + // except the ones specifically disabled by the user. + int[] enabledHdrOutputTypes = null; if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) { - autoHdrOutputTypes = getEnabledAutoHdrTypesLocked(); + enabledHdrOutputTypes = getEnabledHdrOutputTypesLocked(); } int conversionMode = hdrConversionMode.getConversionMode(); int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType(); + // If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then // set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH. - if (mOverrideHdrConversionMode == null) { + if (mShouldDisableHdrConversion) { + conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; + preferredHdrType = -1; + enabledHdrOutputTypes = null; + } else { // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type. - // But, internally SDR is selected by using passthrough mode. + // But, internally SDR is forced by using passthrough mode and not reporting any + // HDR capabilities to apps. if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE - && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) { + && preferredHdrType == HDR_TYPE_INVALID) { conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; + mLogicalDisplayMapper.forEachLocked( + logicalDisplay -> { + if (logicalDisplay.setIsForceSdr(true)) { + handleLogicalDisplayChangedLocked(logicalDisplay); + } + }); + } else { + mLogicalDisplayMapper.forEachLocked( + logicalDisplay -> { + if (logicalDisplay.setIsForceSdr(false)) { + handleLogicalDisplayChangedLocked(logicalDisplay); + } + }); } - } else { - conversionMode = mOverrideHdrConversionMode.getConversionMode(); - preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType(); - autoHdrOutputTypes = null; } mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode( - conversionMode, preferredHdrType, autoHdrOutputTypes); + conversionMode, preferredHdrType, enabledHdrOutputTypes); } } @@ -2621,8 +2674,8 @@ public final class DisplayManagerService extends SystemService { } HdrConversionMode mode; synchronized (mSyncRoot) { - mode = mOverrideHdrConversionMode != null - ? mOverrideHdrConversionMode + mode = mShouldDisableHdrConversion + ? new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH) : mHdrConversionMode; // Handle default: PASSTHROUGH. Don't include the system-preferred type. if (mode == null @@ -2630,8 +2683,6 @@ public final class DisplayManagerService extends SystemService { return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH); } // Handle default or current mode: SYSTEM. Include the system preferred type. - // mOverrideHdrConversionMode and mHdrConversionMode do not include the system - // preferred type, it is kept separately in mSystemPreferredHdrOutputType. if (mode == null || mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) { return new HdrConversionMode( @@ -2642,10 +2693,10 @@ public final class DisplayManagerService extends SystemService { } private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() { - if (mSupportedHdrOutputType == null) { - mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes(); + if (mSupportedHdrOutputTypes == null) { + mSupportedHdrOutputTypes = mInjector.getSupportedHdrOutputTypes(); } - return mSupportedHdrOutputType; + return mSupportedHdrOutputTypes; } void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) { @@ -2831,15 +2882,9 @@ public final class DisplayManagerService extends SystemService { // HDR conversion is disabled in two cases: // - HDR conversion introduces latency and minimal post-processing is requested // - app requests to disable HDR conversion - if (mOverrideHdrConversionMode == null && (disableHdrConversion - || disableHdrConversionForLatency)) { - mOverrideHdrConversionMode = - new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH); - setHdrConversionModeInternal(mHdrConversionMode); - handleLogicalDisplayChangedLocked(display); - } else if (mOverrideHdrConversionMode != null && !disableHdrConversion - && !disableHdrConversionForLatency) { - mOverrideHdrConversionMode = null; + boolean previousShouldDisableHdrConversion = mShouldDisableHdrConversion; + mShouldDisableHdrConversion = disableHdrConversion || disableHdrConversionForLatency; + if (previousShouldDisableHdrConversion != mShouldDisableHdrConversion) { setHdrConversionModeInternal(mHdrConversionMode); handleLogicalDisplayChangedLocked(display); } @@ -3530,9 +3575,9 @@ public final class DisplayManagerService extends SystemService { } int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, - int[] autoHdrTypes) { + int[] allowedHdrOutputTypes) { return DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType, - autoHdrTypes); + allowedHdrOutputTypes); } @Display.HdrCapabilities.HdrType diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index e8be8a4496528fd946a41d20736303dc6d22d6cc..007e3a8fde2fd2ba7e78ca8bae93a4dfe32dd121 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -518,6 +518,7 @@ final class LogicalDisplay { deviceInfo.supportedColorModes, deviceInfo.supportedColorModes.length); mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities; + mBaseDisplayInfo.isForceSdr = deviceInfo.isForceSdr; mBaseDisplayInfo.userDisabledHdrTypes = mUserDisabledHdrTypes; mBaseDisplayInfo.minimalPostProcessingSupported = deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported; @@ -898,6 +899,29 @@ final class LogicalDisplay { } } + /** + * Checks whether display is of the type where HDR settings are relevant, and then sets + * whether Force SDR conversion mode is active. isForceSdr is checked by the Display when + * returning HDR capabilities. + * + * @param isForceSdr Whether Force SDR conversion mode is active + * @return Whether Display Manager should call handleLogicalDisplayChangedLocked() + */ + public boolean setIsForceSdr(boolean isForceSdr) { + int displayType = getDisplayInfoLocked().type; + boolean isTargetDisplayType = displayType == Display.TYPE_INTERNAL + || displayType == Display.TYPE_EXTERNAL + || displayType == Display.TYPE_OVERLAY; + + boolean handleLogicalDisplayChangedLocked = false; + if (isTargetDisplayType && mBaseDisplayInfo.isForceSdr != isForceSdr) { + mBaseDisplayInfo.isForceSdr = isForceSdr; + mInfo.set(null); + handleLogicalDisplayChangedLocked = true; + } + return handleLogicalDisplayChangedLocked; + } + /** * Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay. * diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING index 049b2fd032db4742957c99a881da261186527b7b..4d7962f467fd33a149aeff83f2945b3dcda2086d 100644 --- a/services/core/java/com/android/server/display/TEST_MAPPING +++ b/services/core/java/com/android/server/display/TEST_MAPPING @@ -1,21 +1,12 @@ { "presubmit": [ { - "name": "DisplayServiceTests", - "options": [ - {"include-filter": "com.android.server.display"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "DisplayServiceTests_server_display" } ], "postsubmit": [ { - "name": "DisplayServiceTests", - "options": [ - {"include-filter": "com.android.server.display"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "DisplayServiceTests_server_display" } ] } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index cf44ac029c82c900cfc281a7635bd9952d777fc3..a1fd16476706ea5398f44f33bf4a0ec2676a681c 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -74,6 +74,5 @@ abstract class BrightnessClamper { protected enum Type { POWER, - WEAR_BEDTIME_MODE, } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 9404034cdd34bc8e49788e9aca287a9d12059eb4..a10094fdfbb893a22d2249a44088c9fd3209f2c0 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -218,9 +218,7 @@ public class BrightnessClamperController { return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; } else if (mClamperType == Type.POWER) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC; - } else if (mClamperType == Type.WEAR_BEDTIME_MODE) { - return BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE; - } else { + } else { Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType); return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; } @@ -350,10 +348,6 @@ public class BrightnessClamperController { data, currentBrightness)); } } - if (flags.isBrightnessWearBedtimeModeClamperEnabled()) { - clampers.add(new BrightnessWearBedtimeModeClamper(handler, context, - clamperChangeListener, data)); - } return clampers; } @@ -362,6 +356,10 @@ public class BrightnessClamperController { DisplayDeviceData data) { List modifiers = new ArrayList<>(); modifiers.add(new BrightnessThermalModifier(handler, listener, data)); + if (flags.isBrightnessWearBedtimeModeClamperEnabled()) { + modifiers.add(new BrightnessWearBedtimeModeModifier(handler, context, + listener, data)); + } modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); @@ -395,7 +393,7 @@ public class BrightnessClamperController { */ public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData, BrightnessPowerClamper.PowerData, - BrightnessWearBedtimeModeClamper.WearBedtimeModeData { + BrightnessWearBedtimeModeModifier.WearBedtimeModeData { @NonNull private final String mUniqueDisplayId; @NonNull diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java deleted file mode 100644 index 1902e35ed3973e7261b9fe9d64ff626d80abecb2..0000000000000000000000000000000000000000 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java +++ /dev/null @@ -1,99 +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.server.display.brightness.clamper; - -import android.annotation.NonNull; -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings; - -import com.android.internal.annotations.VisibleForTesting; - -public class BrightnessWearBedtimeModeClamper extends - BrightnessClamper { - - public static final int BEDTIME_MODE_OFF = 0; - public static final int BEDTIME_MODE_ON = 1; - - private final Context mContext; - - private final ContentObserver mSettingsObserver; - - BrightnessWearBedtimeModeClamper(Handler handler, Context context, - BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { - this(new Injector(), handler, context, listener, data); - } - - @VisibleForTesting - BrightnessWearBedtimeModeClamper(Injector injector, Handler handler, Context context, - BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { - super(handler, listener); - mContext = context; - mBrightnessCap = data.getBrightnessWearBedtimeModeCap(); - mSettingsObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - final int bedtimeModeSetting = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.Wearable.BEDTIME_MODE, - BEDTIME_MODE_OFF); - mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON; - mChangeListener.onChanged(); - } - }; - injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver); - } - - @NonNull - @Override - Type getType() { - return Type.WEAR_BEDTIME_MODE; - } - - @Override - void onDeviceConfigChanged() {} - - @Override - void onDisplayChanged(WearBedtimeModeData displayData) { - mHandler.post(() -> { - mBrightnessCap = displayData.getBrightnessWearBedtimeModeCap(); - mChangeListener.onChanged(); - }); - } - - @Override - void stop() { - mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); - } - - interface WearBedtimeModeData { - float getBrightnessWearBedtimeModeCap(); - } - - @VisibleForTesting - static class Injector { - void registerBedtimeModeObserver(@NonNull ContentResolver cr, - @NonNull ContentObserver observer) { - cr.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE), - /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL); - } - } -} diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java new file mode 100644 index 0000000000000000000000000000000000000000..c9c8c33764a6f3a48ebb415fa903b167b61350e8 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java @@ -0,0 +1,158 @@ +/* + * 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.server.display.brightness.clamper; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import java.io.PrintWriter; + +public class BrightnessWearBedtimeModeModifier implements BrightnessStateModifier, + BrightnessClamperController.DisplayDeviceDataListener, + BrightnessClamperController.StatefulModifier { + + public static final int BEDTIME_MODE_OFF = 0; + public static final int BEDTIME_MODE_ON = 1; + + private final Context mContext; + + private final ContentObserver mSettingsObserver; + protected final Handler mHandler; + protected final BrightnessClamperController.ClamperChangeListener mChangeListener; + + private float mBrightnessCap; + private boolean mIsActive = false; + private boolean mApplied = false; + + BrightnessWearBedtimeModeModifier(Handler handler, Context context, + BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { + this(new Injector(), handler, context, listener, data); + } + + @VisibleForTesting + BrightnessWearBedtimeModeModifier(Injector injector, Handler handler, Context context, + BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { + mHandler = handler; + mChangeListener = listener; + mContext = context; + mBrightnessCap = data.getBrightnessWearBedtimeModeCap(); + mSettingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + final int bedtimeModeSetting = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.Wearable.BEDTIME_MODE, + BEDTIME_MODE_OFF); + mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON; + mChangeListener.onChanged(); + } + }; + injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver); + } + + //region BrightnessStateModifier + @Override + public void apply(DisplayManagerInternal.DisplayPowerRequest request, + DisplayBrightnessState.Builder stateBuilder) { + if (mIsActive && stateBuilder.getMaxBrightness() > mBrightnessCap) { + stateBuilder.setMaxBrightness(mBrightnessCap); + stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap)); + stateBuilder.setBrightnessMaxReason( + BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE); + stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); + // set fast change only when modifier is activated. + // this will allow auto brightness to apply slow change even when modifier is active + if (!mApplied) { + stateBuilder.setIsSlowChange(false); + } + mApplied = true; + } else { + mApplied = false; + } + } + + @Override + public void stop() { + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } + + @Override + public void dump(PrintWriter writer) { + writer.println("BrightnessWearBedtimeModeModifier:"); + writer.println(" mBrightnessCap: " + mBrightnessCap); + writer.println(" mIsActive: " + mIsActive); + writer.println(" mApplied: " + mApplied); + } + + @Override + public boolean shouldListenToLightSensor() { + return false; + } + + @Override + public void setAmbientLux(float lux) { + // noop + } + //endregion + + //region DisplayDeviceDataListener + @Override + public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) { + mHandler.post(() -> { + mBrightnessCap = data.getBrightnessWearBedtimeModeCap(); + mChangeListener.onChanged(); + }); + } + //endregion + + //region StatefulModifier + @Override + public void applyStateChange( + BrightnessClamperController.ModifiersAggregatedState aggregatedState) { + if (mIsActive && aggregatedState.mMaxBrightness > mBrightnessCap) { + aggregatedState.mMaxBrightness = mBrightnessCap; + aggregatedState.mMaxBrightnessReason = + BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE; + } + } + //endregion + + interface WearBedtimeModeData { + float getBrightnessWearBedtimeModeCap(); + } + + @VisibleForTesting + static class Injector { + void registerBedtimeModeObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + cr.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE), + /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL); + } + } +} diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index ea240c75452d210a5ec497c1c46b718f4305421e..9d04682f2374531c3613794138f95fe6c140bbb3 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -196,12 +196,7 @@ final class UpdatableFontDir { File signatureFile = new File(dir, FONT_SIGNATURE_FILE); if (!signatureFile.exists()) { Slog.i(TAG, "The signature file is missing."); - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - return; - } else { - FileUtils.deleteContentsAndDir(dir); - continue; - } + return; } byte[] signature; try { @@ -226,39 +221,33 @@ final class UpdatableFontDir { FontFileInfo fontFileInfo = validateFontFile(fontFile, signature); if (fontConfig == null) { - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - // Use preinstalled font config for checking revision number. - fontConfig = mConfigSupplier.apply(Collections.emptyMap()); - } else { - fontConfig = getSystemFontConfig(); - } + // Use preinstalled font config for checking revision number. + fontConfig = mConfigSupplier.apply(Collections.emptyMap()); } addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */); } - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - // Treat as error if post script name of font family was not installed. - for (int i = 0; i < config.fontFamilies.size(); ++i) { - FontUpdateRequest.Family family = config.fontFamilies.get(i); - for (int j = 0; j < family.getFonts().size(); ++j) { - FontUpdateRequest.Font font = family.getFonts().get(j); - if (mFontFileInfoMap.containsKey(font.getPostScriptName())) { - continue; - } - - if (fontConfig == null) { - fontConfig = mConfigSupplier.apply(Collections.emptyMap()); - } - - if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) { - continue; - } - - Slog.e(TAG, "Unknown font that has PostScript name " - + font.getPostScriptName() + " is requested in FontFamily " - + family.getName()); - return; + // Treat as error if post script name of font family was not installed. + for (int i = 0; i < config.fontFamilies.size(); ++i) { + FontUpdateRequest.Family family = config.fontFamilies.get(i); + for (int j = 0; j < family.getFonts().size(); ++j) { + FontUpdateRequest.Font font = family.getFonts().get(j); + if (mFontFileInfoMap.containsKey(font.getPostScriptName())) { + continue; } + + if (fontConfig == null) { + fontConfig = mConfigSupplier.apply(Collections.emptyMap()); + } + + if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) { + continue; + } + + Slog.e(TAG, "Unknown font that has PostScript name " + + font.getPostScriptName() + " is requested in FontFamily " + + family.getName()); + return; } } @@ -273,9 +262,7 @@ final class UpdatableFontDir { mFontFileInfoMap.clear(); mLastModifiedMillis = 0; FileUtils.deleteContents(mFilesDir); - if (com.android.text.flags.Flags.fixFontUpdateFailure()) { - mConfigFile.delete(); - } + mConfigFile.delete(); } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 5696fbaf3679adb3009b7d1d78b0d3edc9864d62..f2e2f653f929ce8f5b943a7991d000218d6b50f5 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -1207,9 +1207,11 @@ final class HdmiCecController { @Override public void onValues(int result, short addr) { - if (result == Result.SUCCESS) { - synchronized (mLock) { - mPhysicalAddress = new Short(addr).intValue(); + synchronized (mLock) { + if (result == Result.SUCCESS) { + mPhysicalAddress = Short.toUnsignedInt(addr); + } else { + mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; } } } @@ -1605,9 +1607,11 @@ final class HdmiCecController { @Override public void onValues(int result, short addr) { - if (result == Result.SUCCESS) { - synchronized (mLock) { - mPhysicalAddress = new Short(addr).intValue(); + synchronized (mLock) { + if (result == Result.SUCCESS) { + mPhysicalAddress = Short.toUnsignedInt(addr); + } else { + mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 5db17bb906378d643aac9434156b72d0bc5f1f37..d0ad6fc0854f16035f5badadb3b5681edb6cacb8 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -171,6 +171,17 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { addAndStartAction( new HotplugDetectionAction(HdmiCecLocalDevicePlayback.this)); } + + if (mService.isHdmiControlEnhancedBehaviorFlagEnabled()) { + List + powerStatusMonitorActionsFromPlayback = + getActions(PowerStatusMonitorActionFromPlayback.class); + if (powerStatusMonitorActionsFromPlayback.isEmpty()) { + addAndStartAction( + new PowerStatusMonitorActionFromPlayback( + HdmiCecLocalDevicePlayback.this)); + } + } } }); addAndStartAction(action); @@ -686,6 +697,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { removeAction(DeviceDiscoveryAction.class); removeAction(HotplugDetectionAction.class); removeAction(NewDeviceAction.class); + removeAction(PowerStatusMonitorActionFromPlayback.class); super.disableDevice(initiatedByCec, callback); clearDeviceInfoList(); checkIfPendingActionsCleared(); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 271836ac7d295df4b10190ab4436eca5fc458476..8e41d18f0953f57c48815eca6858f30da4fea156 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import static android.media.tv.flags.Flags.hdmiControlEnhancedBehavior; + import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE; import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED; @@ -1378,7 +1380,8 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private List getCecLocalDeviceTypes() { ArrayList allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices); - if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) + if (!isTvDevice() && isDsmEnabled() + && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) && isArcSupported() && mSoundbarModeFeatureFlagEnabled) { allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); } @@ -1685,7 +1688,11 @@ public class HdmiControlService extends SystemService { private void sendCecCommandWithRetries(HdmiCecMessage command, @Nullable SendMessageCallback callback) { assertRunOnServiceThread(); - HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0); + List devices = getAllCecLocalDevices(); + if (devices.isEmpty()) { + return; + } + HdmiCecLocalDevice localDevice = devices.get(0); if (localDevice != null) { sendCecCommandWithoutRetries(command, new SendMessageCallback() { @Override @@ -5137,4 +5144,8 @@ public class HdmiControlService extends SystemService { tv().startArcAction(enabled, callback); } } + + protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() { + return hdmiControlEnhancedBehavior(); + } } diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java new file mode 100644 index 0000000000000000000000000000000000000000..9a3cde15630031d92691cbb572f7ab62346a13c1 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY; + +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * This action is used by playback devices to query TV's power status such that they can go to + * standby when the TV reports power off. + */ +public class PowerStatusMonitorActionFromPlayback extends HdmiCecFeatureAction { + private static final String TAG = "PowerStatusMonitorActionFromPlayback"; + + // State that waits for once sending + // to all external devices. + private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1; + // State that waits for next monitoring. + private static final int STATE_WAIT_FOR_NEXT_MONITORING = 2; + // Monitoring interval (60s) + @VisibleForTesting + protected static final int MONITORING_INTERVAL_MS = 60000; + // Timeout once sending + private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000; + // Maximum number of retries in case the failed being sent or times + // out. + private static final int GIVE_POWER_STATUS_FOR_SOURCE_RETRIES = 5; + private int mPowerStatusRetries = 0; + + PowerStatusMonitorActionFromPlayback(HdmiCecLocalDevice source) { + super(source); + } + + @Override + boolean start() { + // Start after timeout since the device just finished allocation. + mState = STATE_WAIT_FOR_NEXT_MONITORING; + addTimer(mState, MONITORING_INTERVAL_MS); + return true; + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS + && cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS + && cmd.getSource() == Constants.ADDR_TV) { + return handleReportPowerStatusFromTv(cmd); + } + return false; + } + + private boolean handleReportPowerStatusFromTv(HdmiCecMessage cmd) { + int powerStatus = cmd.getParams()[0] & 0xFF; + if (powerStatus == POWER_STATUS_STANDBY) { + Slog.d(TAG, "TV reported it turned off, going to sleep."); + source().getService().standby(); + return true; + } + return false; + } + + @Override + void handleTimerEvent(int state) { + switch (mState) { + case STATE_WAIT_FOR_NEXT_MONITORING: + mPowerStatusRetries = 0; + queryPowerStatus(); + break; + case STATE_WAIT_FOR_REPORT_POWER_STATUS: + handleTimeout(); + break; + } + } + + private void queryPowerStatus() { + sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), + Constants.ADDR_TV)); + + mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; + addTimer(mState, REPORT_POWER_STATUS_TIMEOUT_MS); + } + + private void handleTimeout() { + if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS) { + if (mPowerStatusRetries++ < GIVE_POWER_STATUS_FOR_SOURCE_RETRIES) { + queryPowerStatus(); + } else { + mPowerStatusRetries = 0; + mState = STATE_WAIT_FOR_NEXT_MONITORING; + addTimer(mState, MONITORING_INTERVAL_MS); + } + } + } +} diff --git a/services/core/java/com/android/server/hdmi/TEST_MAPPING b/services/core/java/com/android/server/hdmi/TEST_MAPPING index d116087d5e1c3c1a7a3ba77f95c28982c2cf4639..bacacafb11536e46e27644116dc3de21f1082663 100644 --- a/services/core/java/com/android/server/hdmi/TEST_MAPPING +++ b/services/core/java/com/android/server/hdmi/TEST_MAPPING @@ -12,18 +12,7 @@ // Postsubmit tests for TV devices "tv-postsubmit": [ { - "name": "HdmiCecTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "include-filter": "android.hardware.hdmi" - } - ], + "name": "HdmiCecTests_hardware_hdmi", "file_patterns": [ "(/|^)DeviceFeature[^/]*", "(/|^)Hdmi[^/]*" ] diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 73f18d17d058343c27a08ba5c9fa1a07eb9d573c..92812670057afcfec312eea8c7dd3de8b9a8f30e 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -232,6 +232,9 @@ public abstract class InputManagerInternal { /** * Notify key gesture was completed by the user. * + * NOTE: This is a temporary API added to assist in a long-term refactor, and is not meant for + * general use by system services. + * * @param deviceId the device ID of the keyboard using which the event was completed * @param keycodes the keys pressed for the event * @param modifierState the modifier state @@ -240,4 +243,20 @@ public abstract class InputManagerInternal { */ public abstract void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int event); + + /** + * Notify that a key gesture was detected by another system component, and it should be handled + * appropriately by KeyGestureController. + * + * NOTE: This is a temporary API added to assist in a long-term refactor, and is not meant for + * general use by system services. + * + * @param deviceId the device ID of the keyboard using which the event was completed + * @param keycodes the keys pressed for the event + * @param modifierState the modifier state + * @param event the gesture event that was completed + * + */ + public abstract void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes, + int modifierState, @KeyGestureEvent.KeyGestureType int event); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index ca8ae6e2e68d1e030a084e949b5d3ef2e2bda3e3..fd7479eb8d48d6841ed01cb23c8df9d846a7c081 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -16,11 +16,15 @@ package com.android.server.input; +import static android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW; +import static android.content.PermissionChecker.PERMISSION_GRANTED; +import static android.content.PermissionChecker.PID_UNKNOWN; import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT; import static android.view.KeyEvent.KEYCODE_UNKNOWN; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.hardware.input.Flags.touchpadVisualizer; +import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import android.Manifest; import android.annotation.EnforcePermission; @@ -36,6 +40,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.graphics.PixelFormat; @@ -120,6 +125,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodSubtypeHandle; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.KeyInterceptionInfo; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.DisplayThread; @@ -129,6 +135,7 @@ import com.android.server.input.InputManagerInternal.LidSwitchCallback; import com.android.server.input.debug.FocusEventDebugView; import com.android.server.input.debug.TouchpadDebugViewController; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.WindowManagerInternal; import libcore.io.IoUtils; @@ -176,6 +183,7 @@ public class InputManagerService extends IInputManager.Stub private final InputManagerHandler mHandler; private DisplayManagerInternal mDisplayManagerInternal; + private WindowManagerInternal mWindowManagerInternal; private PackageManagerInternal mPackageManagerInternal; private final File mDoubleTouchGestureEnableFile; @@ -448,6 +456,14 @@ public class InputManagerService extends IInputManager.Stub void registerLocalService(InputManagerInternal localService) { LocalServices.addService(InputManagerInternal.class, localService); } + + KeyboardBacklightControllerInterface getKeyboardBacklightController( + NativeInputManagerService nativeService, PersistentDataStore dataStore) { + return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled() + ? new KeyboardBacklightController(mContext, nativeService, dataStore, + mLooper, mUEventManager) + : new KeyboardBacklightControllerInterface() {}; + } } public InputManagerService(Context context) { @@ -471,10 +487,7 @@ public class InputManagerService extends IInputManager.Stub injector.getLooper(), this) : null; mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(), injector.getUEventManager()); - mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled() - ? new KeyboardBacklightController(mContext, mNative, mDataStore, - injector.getLooper(), injector.getUEventManager()) - : new KeyboardBacklightControllerInterface() {}; + mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore); mStickyModifierStateController = new StickyModifierStateController(); mKeyGestureController = new KeyGestureController(mContext, injector.getLooper()); mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(), @@ -547,6 +560,7 @@ public class InputManagerService extends IInputManager.Stub } mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mSettingsObserver.registerAndUpdate(); @@ -597,6 +611,8 @@ public class InputManagerService extends IInputManager.Stub mKeyRemapper.systemRunning(); mPointerIconCache.systemRunning(); mKeyboardGlyphManager.systemRunning(); + + initKeyGestures(); } private void reloadDeviceAliases() { @@ -2116,6 +2132,7 @@ public class InputManagerService extends IInputManager.Stub mKeyboardBacklightController.dump(ipw); mKeyboardLedController.dump(ipw); mKeyboardGlyphManager.dump(ipw); + mKeyGestureController.dump(ipw); } private void dumpAssociations(IndentingPrintWriter pw) { @@ -2274,6 +2291,14 @@ public class InputManagerService extends IInputManager.Stub } } + // Native callback. + @SuppressWarnings("unused") + private void notifyTouchpadGestureInfo(int type, int deviceId) { + if (mTouchpadDebugViewController != null) { + mTouchpadDebugViewController.updateTouchpadGestureInfo(type, deviceId); + } + } + // Native callback. @SuppressWarnings("unused") private void notifySwitch(long whenNanos, int switchValues, int switchMask) { @@ -2457,13 +2482,86 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") - private long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) { - // TODO(b/358569822): Move shortcut trigger logic from PWM to KeyGestureController - long value = mKeyGestureController.interceptKeyBeforeDispatching(focus, event, policyFlags); - if (value != 0) { // If key is consumed (i.e. non-zero value) - return value; + @VisibleForTesting + long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) { + final long keyNotConsumed = 0; + long value = keyNotConsumed; + // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts + if ((event.isMetaPressed() || KeyEvent.isMetaKey(event.getKeyCode())) + && shouldInterceptShortcuts(focus)) { + return keyNotConsumed; } - return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags); + if (useKeyGestureEventHandler()) { + value = mKeyGestureController.interceptKeyBeforeDispatching(focus, event, policyFlags); + } + if (value == keyNotConsumed) { + value = mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, + policyFlags); + } + return value; + } + + private boolean shouldInterceptShortcuts(IBinder focusedToken) { + KeyInterceptionInfo info = + mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken); + boolean hasInterceptWindowFlag = info != null && (info.layoutParamsPrivateFlags + & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0; + return hasInterceptWindowFlag && PermissionChecker.checkPermissionForDataDelivery(mContext, + OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW, PID_UNKNOWN, info.windowOwnerUid, + null, null, null) == PERMISSION_GRANTED; + } + + private void initKeyGestures() { + if (!useKeyGestureEventHandler()) { + return; + } + InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); + im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() { + @Override + public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, + @Nullable IBinder focussedToken) { + int deviceId = event.getDeviceId(); + boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE + && !event.isCancelled(); + switch (event.getKeyGestureType()) { + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: + if (complete) { + mKeyboardBacklightController.incrementKeyboardBacklight(deviceId); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: + if (complete) { + mKeyboardBacklightController.decrementKeyboardBacklight(deviceId); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: + // TODO(b/367748270): Add functionality to turn keyboard backlight on/off. + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: + if (complete) { + mNative.toggleCapsLock(deviceId); + } + return true; + default: + return false; + + } + } + + @Override + public boolean isKeyGestureSupported(int gestureType) { + switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: + case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: + return true; + default: + return false; + + } + } + }); } // Native callback. @@ -2748,12 +2846,14 @@ public class InputManagerService extends IInputManager.Stub private void enforceManageKeyGesturePermission() { // TODO(b/361567988): Use @EnforcePermission to enforce permission once flag guarding the // permission is rolled out - String systemUIPackage = mContext.getString(R.string.config_systemUi); - int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal - .getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY, - UserHandle.USER_SYSTEM)); - if (UserHandle.getCallingAppId() == systemUIAppId) { - return; + if (mSystemReady) { + String systemUIPackage = mContext.getString(R.string.config_systemUi); + int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal + .getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY, + UserHandle.USER_SYSTEM)); + if (UserHandle.getCallingAppId() == systemUIAppId) { + return; + } } if (mContext.checkCallingOrSelfPermission( Manifest.permission.MANAGE_KEY_GESTURES) == PackageManager.PERMISSION_GRANTED) { @@ -3167,6 +3267,8 @@ public class InputManagerService extends IInputManager.Stub mKeyboardBacklightController.onInteractiveChanged(interactive); } + // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture + // handler refactoring complete @Override public void toggleCapsLock(int deviceId) { mNative.toggleCapsLock(deviceId); @@ -3254,11 +3356,15 @@ public class InputManagerService extends IInputManager.Stub mKeyboardBacklightController.notifyUserActivity(); } + // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture + // handler refactoring complete @Override public void incrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.incrementKeyboardBacklight(deviceId); } + // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture + // handler refactoring complete @Override public void decrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.decrementKeyboardBacklight(deviceId); @@ -3302,6 +3408,12 @@ public class InputManagerService extends IInputManager.Stub mKeyGestureController.notifyKeyGestureCompleted(deviceId, keycodes, modifierState, gestureType); } + + @Override + public void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes, + int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) { + mKeyGestureController.handleKeyGesture(deviceId, keycodes, modifierState, gestureType); + } } @Override diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 835fb72e524ab70e721f3b2dcb817769dc2401b3..d70bd8b17ddf4431bb8542c3cf47e6e884b27501 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -94,6 +94,8 @@ class InputSettingsObserver extends ContentObserver { (reason) -> updateKeyRepeatInfo()), Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS), (reason) -> updateKeyRepeatInfo()), + Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_ENABLED), + (reason) -> updateKeyRepeatInfo()), Map.entry(Settings.System.getUriFor(Settings.System.SHOW_ROTARY_INPUT), (reason) -> updateShowRotaryInput()), Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS), @@ -230,6 +232,11 @@ class InputSettingsObserver extends ContentObserver { } private void updateKeyRepeatInfo() { + // Key repeat is enabled by default + final boolean keyRepeatEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_ENABLED, 1, + UserHandle.USER_CURRENT) != 0; + // Use ViewConfiguration getters only as fallbacks because they may return stale values. final int timeoutMs = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_TIMEOUT_MS, ViewConfiguration.getKeyRepeatTimeout(), @@ -237,7 +244,7 @@ class InputSettingsObserver extends ContentObserver { final int delayMs = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(), UserHandle.USER_CURRENT); - mNative.setKeyRepeatConfiguration(timeoutMs, delayMs); + mNative.setKeyRepeatConfiguration(timeoutMs, delayMs, keyRepeatEnabled); } private void updateMaximumObscuringOpacityForTouch() { diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index bfdb1c1f4ea99b5fefbda4499d53b36f386bf09e..4538b49b73c514a1c3c984da8a431ae8b8adf5e6 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -16,10 +16,14 @@ package com.android.server.input; +import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; + import android.annotation.BinderThread; import android.annotation.MainThread; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.res.Resources; import android.hardware.input.AidlKeyGestureEvent; import android.hardware.input.IKeyGestureEventListener; import android.hardware.input.IKeyGestureHandler; @@ -31,6 +35,8 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -38,10 +44,14 @@ import android.view.Display; import android.view.InputDevice; import android.view.KeyEvent; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.ArrayDeque; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.TreeMap; /** @@ -56,12 +66,36 @@ final class KeyGestureController { // 'adb shell setprop log.tag.KeyGestureController DEBUG' (requires restart) private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + // Maximum key gesture events that are tracked and will be available in input dump. + private static final int MAX_TRACKED_EVENTS = 10; + private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1; + // must match: config_settingsKeyBehavior in config.xml + private static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0; + private static final int SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1; + private static final int SETTINGS_KEY_BEHAVIOR_NOTHING = 2; + private static final int LAST_SETTINGS_KEY_BEHAVIOR = SETTINGS_KEY_BEHAVIOR_NOTHING; + + // Must match: config_searchKeyBehavior in config.xml + private static final int SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH = 0; + private static final int SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1; + private static final int LAST_SEARCH_KEY_BEHAVIOR = SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY; + private final Context mContext; private final Handler mHandler; private final int mSystemPid; + // Pending actions + private boolean mPendingMetaAction; + private boolean mPendingCapsLockToggle; + private boolean mPendingHideRecentSwitcher; + + // Key behaviors + private boolean mEnableBugReportKeyboardShortcut; + private int mSearchKeyBehavior; + private int mSettingsKeyBehavior; + // List of currently registered key gesture event listeners keyed by process pid @GuardedBy("mKeyGestureEventListenerRecords") private final SparseArray @@ -73,6 +107,11 @@ final class KeyGestureController { @GuardedBy("mKeyGestureHandlerRecords") private final TreeMap mKeyGestureHandlerRecords; + private final ArrayDeque mLastHandledEvents = new ArrayDeque<>(); + + /** Currently fully consumed key codes per device */ + private final SparseArray> mConsumedKeysForDevice = new SparseArray<>(); + KeyGestureController(Context context, Looper looper) { mContext = context; mHandler = new Handler(looper, this::handleMessage); @@ -89,20 +128,464 @@ final class KeyGestureController { return Integer.compare(p1, p2); } }); + initBehaviors(); + } + + private void initBehaviors() { + mEnableBugReportKeyboardShortcut = "1".equals(SystemProperties.get("ro.debuggable")); + + Resources res = mContext.getResources(); + mSearchKeyBehavior = res.getInteger(R.integer.config_searchKeyBehavior); + if (mSearchKeyBehavior < SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH + || mSearchKeyBehavior > LAST_SEARCH_KEY_BEHAVIOR) { + mSearchKeyBehavior = SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH; + } + mSettingsKeyBehavior = res.getInteger(R.integer.config_settingsKeyBehavior); + if (mSettingsKeyBehavior < SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY + || mSettingsKeyBehavior > LAST_SETTINGS_KEY_BEHAVIOR) { + mSettingsKeyBehavior = SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY; + } } - public int interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) { + public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, + int policyFlags) { // TODO(b/358569822): Handle shortcuts trigger logic here and pass it to appropriate // KeyGestureHandler (PWM is one of the handlers) - return 0; + final int keyCode = event.getKeyCode(); + final int deviceId = event.getDeviceId(); + final long keyConsumed = -1; + final long keyNotConsumed = 0; + + Set consumedKeys = mConsumedKeysForDevice.get(deviceId); + if (consumedKeys == null) { + consumedKeys = new HashSet<>(); + mConsumedKeysForDevice.put(deviceId, consumedKeys); + } + + if (interceptSystemKeysAndShortcuts(focusedToken, event) + && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + consumedKeys.add(keyCode); + return keyConsumed; + } + + boolean needToConsumeKey = consumedKeys.contains(keyCode); + if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) { + consumedKeys.remove(keyCode); + if (consumedKeys.isEmpty()) { + mConsumedKeysForDevice.remove(deviceId); + } + } + + return needToConsumeKey ? keyConsumed : keyNotConsumed; + } + + @SuppressLint("MissingPermission") + private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) { + final int keyCode = event.getKeyCode(); + final int repeatCount = event.getRepeatCount(); + final int metaState = event.getMetaState(); + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final boolean canceled = event.isCanceled(); + final int displayId = event.getDisplayId(); + final int deviceId = event.getDeviceId(); + final boolean firstDown = down && repeatCount == 0; + + // Cancel any pending meta actions if we see any other keys being pressed between the + // down of the meta key and its corresponding up. + if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) { + mPendingMetaAction = false; + } + // Any key that is not Alt or Meta cancels Caps Lock combo tracking. + if (mPendingCapsLockToggle && !KeyEvent.isMetaKey(keyCode) && !KeyEvent.isAltKey(keyCode)) { + mPendingCapsLockToggle = false; + } + + switch (keyCode) { + case KeyEvent.KEYCODE_A: + if (firstDown && event.isMetaPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_RECENT_APPS: + if (firstDown) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_APP_SWITCH: + if (firstDown) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, + KeyGestureEvent.ACTION_GESTURE_START, displayId, + focusedToken, /* flags = */0); + } else if (!down) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0); + } + return true; + case KeyEvent.KEYCODE_H: + case KeyEvent.KEYCODE_ENTER: + if (firstDown && event.isMetaPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_I: + if (firstDown && event.isMetaPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_L: + if (firstDown && event.isMetaPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_N: + if (firstDown && event.isMetaPressed()) { + if (event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } else { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + } + break; + case KeyEvent.KEYCODE_S: + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_DEL: + if (newBugreportKeyboardShortcut()) { + if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed() + && event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + } + // fall through + case KeyEvent.KEYCODE_ESCAPE: + if (firstDown && event.isMetaPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + if (firstDown && event.isMetaPressed()) { + if (event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } else if (event.isAltPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } else { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + } + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (firstDown && event.isMetaPressed()) { + if (event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } else if (event.isAltPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + } + break; + case KeyEvent.KEYCODE_SLASH: + if (firstDown && event.isMetaPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + break; + case KeyEvent.KEYCODE_BRIGHTNESS_UP: + case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: + if (down) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP + ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP + : KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: + if (down) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP: + if (down) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE: + // TODO: Add logic + if (!down) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_ALL_APPS: + if (firstDown) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_NOTIFICATION: + if (!down) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_SEARCH: + if (firstDown && mSearchKeyBehavior == SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY) { + return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + + } + break; + case KeyEvent.KEYCODE_SETTINGS: + if (firstDown) { + if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) { + handleKeyGesture(deviceId, + new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) { + handleKeyGesture(deviceId, + new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + } + return true; + case KeyEvent.KEYCODE_LANGUAGE_SWITCH: + if (firstDown) { + handleKeyGesture(deviceId, new int[]{keyCode}, + event.isShiftPressed() ? KeyEvent.META_SHIFT_ON : 0, + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_CAPS_LOCK: + // Just logging/notifying purposes + // Caps lock is already handled in inputflinger native + if (!down) { + AidlKeyGestureEvent eventToNotify = createKeyGestureEvent(deviceId, + new int[]{keyCode}, metaState, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0); + Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT, + eventToNotify); + mHandler.sendMessage(msg); + } + break; + case KeyEvent.KEYCODE_SCREENSHOT: + if (firstDown) { + handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + return true; + case KeyEvent.KEYCODE_META_LEFT: + case KeyEvent.KEYCODE_META_RIGHT: + if (down) { + if (event.isAltPressed()) { + mPendingCapsLockToggle = true; + mPendingMetaAction = false; + } else { + mPendingCapsLockToggle = false; + mPendingMetaAction = true; + } + } else { + // Toggle Caps Lock on META-ALT. + if (mPendingCapsLockToggle) { + mPendingCapsLockToggle = false; + handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + + } else if (mPendingMetaAction) { + mPendingMetaAction = false; + if (!canceled) { + handleKeyGesture(deviceId, new int[]{keyCode}, + /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + } + } + return true; + case KeyEvent.KEYCODE_TAB: + if (firstDown) { + if (event.isMetaPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } else if (!mPendingHideRecentSwitcher) { + final int shiftlessModifiers = + event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; + if (KeyEvent.metaStateHasModifiers( + shiftlessModifiers, KeyEvent.META_ALT_ON)) { + mPendingHideRecentSwitcher = true; + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, + KeyGestureEvent.ACTION_GESTURE_START, displayId, + focusedToken, /* flags = */0); + } + } + } + break; + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + if (down) { + if (event.isMetaPressed()) { + mPendingCapsLockToggle = true; + mPendingMetaAction = false; + } else { + mPendingCapsLockToggle = false; + } + } else { + if (mPendingHideRecentSwitcher) { + mPendingHideRecentSwitcher = false; + return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_TAB}, + KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + + // Toggle Caps Lock on META-ALT. + if (mPendingCapsLockToggle) { + mPendingCapsLockToggle = false; + return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0); + } + } + break; + case KeyEvent.KEYCODE_ASSIST: + Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); + return true; + case KeyEvent.KEYCODE_VOICE_ASSIST: + Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in" + + " interceptKeyBeforeQueueing"); + return true; + case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY: + case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: + Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in" + + " interceptKeyBeforeQueueing"); + return true; + } + return false; } @VisibleForTesting boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId, IBinder focusedToken, int flags) { - AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, - modifierState, gestureType, action, displayId, flags); + return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes, + modifierState, gestureType, action, displayId, flags), focusedToken); + } + + private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) { synchronized (mKeyGestureHandlerRecords) { for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) { if (handler.handleKeyGesture(event, focusedToken)) { @@ -135,6 +618,13 @@ final class KeyGestureController { mHandler.obtainMessage(MSG_NOTIFY_KEY_GESTURE_EVENT, event).sendToTarget(); } + public void handleKeyGesture(int deviceId, int[] keycodes, int modifierState, + @KeyGestureEvent.KeyGestureType int gestureType) { + AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState, + gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0); + handleKeyGesture(event, null /*focusedToken*/); + } + @MainThread private void notifyKeyGestureEvent(AidlKeyGestureEvent event) { InputDevice device = getInputDevice(event.deviceId); @@ -147,6 +637,10 @@ final class KeyGestureController { KeyGestureEvent.keyGestureTypeToLogEvent(event.gestureType)); } notifyAllListeners(event); + while (mLastHandledEvents.size() >= MAX_TRACKED_EVENTS) { + mLastHandledEvents.removeFirst(); + } + mLastHandledEvents.addLast(new KeyGestureEvent(event)); } @MainThread @@ -347,4 +841,45 @@ final class KeyGestureController { event.flags = flags; return event; } + + public void dump(IndentingPrintWriter ipw) { + ipw.println("KeyGestureController:"); + ipw.increaseIndent(); + ipw.println("mSystemPid = " + mSystemPid); + ipw.println("mPendingMetaAction = " + mPendingMetaAction); + ipw.println("mPendingCapsLockToggle = " + mPendingCapsLockToggle); + ipw.println("mPendingHideRecentSwitcher = " + mPendingHideRecentSwitcher); + ipw.println("mSearchKeyBehavior = " + mSearchKeyBehavior); + ipw.println("mSettingsKeyBehavior = " + mSettingsKeyBehavior); + ipw.print("mKeyGestureEventListenerRecords = {"); + synchronized (mKeyGestureEventListenerRecords) { + int size = mKeyGestureEventListenerRecords.size(); + for (int i = 0; i < size; i++) { + ipw.print(mKeyGestureEventListenerRecords.keyAt(i)); + if (i < size - 1) { + ipw.print(", "); + } + } + } + ipw.println("}"); + ipw.print("mKeyGestureHandlerRecords = {"); + synchronized (mKeyGestureHandlerRecords) { + int i = mKeyGestureHandlerRecords.size() - 1; + for (int processId : mKeyGestureHandlerRecords.keySet()) { + ipw.print(processId); + if (i > 0) { + ipw.print(", "); + } + i--; + } + } + ipw.println("}"); + ipw.decreaseIndent(); + ipw.println("Last handled KeyGestureEvents: "); + ipw.increaseIndent(); + for (KeyGestureEvent ev : mLastHandledEvents) { + ipw.println(ev); + } + ipw.decreaseIndent(); + } } diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java index 7ba77698cb5d613962412228eaf0438a8db238c9..82b36aff52734631fdf9e7d6e8cce435d287c6c4 100644 --- a/services/core/java/com/android/server/input/KeyRemapper.java +++ b/services/core/java/com/android/server/input/KeyRemapper.java @@ -17,27 +17,24 @@ package com.android.server.input; import android.content.Context; -import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.ArrayMap; import android.util.FeatureFlagUtils; -import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import java.util.Map; -import java.util.Objects; /** * A component of {@link InputManagerService} responsible for managing key remappings. * * @hide */ -final class KeyRemapper implements InputManager.InputDeviceListener { +final class KeyRemapper { - private static final int MSG_UPDATE_EXISTING_DEVICES = 1; + private static final int MSG_UPDATE_EXISTING_KEY_REMAPPING = 1; private static final int MSG_REMAP_KEY = 2; private static final int MSG_CLEAR_ALL_REMAPPING = 3; @@ -49,7 +46,7 @@ final class KeyRemapper implements InputManager.InputDeviceListener { private final Handler mHandler; KeyRemapper(Context context, NativeInputManagerService nativeService, - PersistentDataStore dataStore, Looper looper) { + PersistentDataStore dataStore, Looper looper) { mContext = context; mNative = nativeService; mDataStore = dataStore; @@ -57,13 +54,7 @@ final class KeyRemapper implements InputManager.InputDeviceListener { } public void systemRunning() { - InputManager inputManager = Objects.requireNonNull( - mContext.getSystemService(InputManager.class)); - inputManager.registerInputDeviceListener(this, mHandler); - - Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES, - inputManager.getInputDeviceIds()); - mHandler.sendMessage(msg); + Message.obtain(mHandler, MSG_UPDATE_EXISTING_KEY_REMAPPING).sendToTarget(); } public void remapKey(int fromKey, int toKey) { @@ -91,19 +82,19 @@ final class KeyRemapper implements InputManager.InputDeviceListener { } } - private void addKeyRemapping(int fromKey, int toKey) { - InputManager inputManager = Objects.requireNonNull( - mContext.getSystemService(InputManager.class)); - for (int deviceId : inputManager.getInputDeviceIds()) { - InputDevice inputDevice = inputManager.getInputDevice(deviceId); - if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) { - mNative.addKeyRemapping(deviceId, fromKey, toKey); - } + private void setKeyRemapping(Map keyRemapping) { + int index = 0; + int[] fromKeycodesArr = new int[keyRemapping.size()]; + int[] toKeycodesArr = new int[keyRemapping.size()]; + for (Map.Entry entry : keyRemapping.entrySet()) { + fromKeycodesArr[index] = entry.getKey(); + toKeycodesArr[index] = entry.getValue(); + index++; } + mNative.setKeyRemapping(fromKeycodesArr, toKeycodesArr); } private void remapKeyInternal(int fromKey, int toKey) { - addKeyRemapping(fromKey, toKey); synchronized (mDataStore) { try { if (fromKey == toKey) { @@ -114,6 +105,7 @@ final class KeyRemapper implements InputManager.InputDeviceListener { } finally { mDataStore.saveIfNeeded(); } + setKeyRemapping(mDataStore.getKeyRemapping()); } } @@ -123,45 +115,25 @@ final class KeyRemapper implements InputManager.InputDeviceListener { Map keyRemapping = mDataStore.getKeyRemapping(); for (int fromKey : keyRemapping.keySet()) { mDataStore.clearMappedKey(fromKey); - - // Remapping to itself will clear the remapping on native side - addKeyRemapping(fromKey, fromKey); } } finally { mDataStore.saveIfNeeded(); } + setKeyRemapping(mDataStore.getKeyRemapping()); } } - @Override - public void onInputDeviceAdded(int deviceId) { + public void updateExistingKeyMapping() { if (!supportRemapping()) { return; } - InputManager inputManager = Objects.requireNonNull( - mContext.getSystemService(InputManager.class)); - InputDevice inputDevice = inputManager.getInputDevice(deviceId); - if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) { - Map remapping = getKeyRemapping(); - remapping.forEach( - (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey)); - } - } - - @Override - public void onInputDeviceRemoved(int deviceId) { - } - - @Override - public void onInputDeviceChanged(int deviceId) { + setKeyRemapping(getKeyRemapping()); } private boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_UPDATE_EXISTING_DEVICES: - for (int deviceId : (int[]) msg.obj) { - onInputDeviceAdded(deviceId); - } + case MSG_UPDATE_EXISTING_KEY_REMAPPING: + updateExistingKeyMapping(); return true; case MSG_REMAP_KEY: remapKeyInternal(msg.arg1, msg.arg2); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 1e7c97f9e5514a81b038df07e496750b0492ab51..d17e256e34fc3bc01571158ccf466c21ff8c01ea 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -48,7 +48,7 @@ interface NativeInputManagerService { int getSwitchState(int deviceId, int sourceMask, int sw); - void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode); + void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes); boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); @@ -212,7 +212,7 @@ interface NativeInputManagerService { void setMotionClassifierEnabled(boolean enabled); - void setKeyRepeatConfiguration(int timeoutMs, int delayMs); + void setKeyRepeatConfiguration(int timeoutMs, int delayMs, boolean keyRepeatEnabled); InputSensorInfo[] getSensorList(int deviceId); @@ -311,7 +311,7 @@ interface NativeInputManagerService { public native int getSwitchState(int deviceId, int sourceMask, int sw); @Override - public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode); + public native void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes); @Override public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, @@ -509,7 +509,8 @@ interface NativeInputManagerService { public native void setMotionClassifierEnabled(boolean enabled); @Override - public native void setKeyRepeatConfiguration(int timeoutMs, int delayMs); + public native void setKeyRepeatConfiguration(int timeoutMs, int delayMs, + boolean keyRepeatEnabled); @Override public native InputSensorInfo[] getSensorList(int deviceId); diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java index cc13e8e5ccc7287e0d94b695a57c66b7a1182ce0..f3514653518bedcedc203ef58b4909599c25d63d 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java @@ -29,11 +29,14 @@ import android.util.Slog; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; +import android.view.SurfaceControl; import android.view.ViewConfiguration; +import android.view.ViewRootImpl; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.input.TouchpadFingerState; import com.android.server.input.TouchpadHardwareProperties; import com.android.server.input.TouchpadHardwareState; @@ -47,11 +50,16 @@ public class TouchpadDebugView extends LinearLayout { private static final float TEXT_SIZE_SP = 16.0f; private static final float DEFAULT_RES_X = 47f; private static final float DEFAULT_RES_Y = 45f; + private static final int TEXT_PADDING_DP = 12; + private static final int ROUNDED_CORNER_RADIUS_DP = 24; + private static final int BUTTON_PRESSED_BACKGROUND_COLOR = Color.rgb(118, 151, 99); + private static final int BUTTON_RELEASED_BACKGROUND_COLOR = Color.rgb(84, 85, 169); /** * Input device ID for the touchpad that this debug view is displaying. */ private final int mTouchpadId; + private static final String TAG = "TouchpadDebugView"; @NonNull private final WindowManager mWindowManager; @@ -67,6 +75,10 @@ public class TouchpadDebugView extends LinearLayout { private int mScreenHeight; private int mWindowLocationBeforeDragX; private int mWindowLocationBeforeDragY; + private int mLatestGestureType = 0; + private TextView mGestureInfoView; + private TextView mNameView; + @NonNull private TouchpadHardwareState mLastTouchpadState = new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, @@ -75,7 +87,7 @@ public class TouchpadDebugView extends LinearLayout { private final TouchpadHardwareProperties mTouchpadHardwareProperties; public TouchpadDebugView(Context context, int touchpadId, - TouchpadHardwareProperties touchpadHardwareProperties) { + TouchpadHardwareProperties touchpadHardwareProperties) { super(context); mTouchpadId = touchpadId; mWindowManager = @@ -111,40 +123,70 @@ public class TouchpadDebugView extends LinearLayout { LayoutParams.WRAP_CONTENT)); setBackgroundColor(Color.TRANSPARENT); - TextView nameView = new TextView(context); - nameView.setBackgroundColor(Color.RED); - nameView.setTextSize(TEXT_SIZE_SP); - nameView.setText(Objects.requireNonNull(Objects.requireNonNull( + mNameView = new TextView(context); + mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR); + mNameView.setTextSize(TEXT_SIZE_SP); + mNameView.setText(Objects.requireNonNull(Objects.requireNonNull( mContext.getSystemService(InputManager.class)) .getInputDevice(touchpadId)).getName()); - nameView.setGravity(Gravity.CENTER); - nameView.setTextColor(Color.WHITE); - nameView.setLayoutParams( + mNameView.setGravity(Gravity.CENTER); + mNameView.setTextColor(Color.WHITE); + int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP, + getResources().getDisplayMetrics()); + mNameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP); + mNameView.setLayoutParams( new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mTouchpadVisualizationView = new TouchpadVisualizationView(context, mTouchpadHardwareProperties); - mTouchpadVisualizationView.setBackgroundColor(Color.WHITE); - - //TODO(b/365562952): Add a display for recognized gesture info here - TextView gestureInfoView = new TextView(context); - gestureInfoView.setBackgroundColor(Color.GRAY); - gestureInfoView.setTextSize(TEXT_SIZE_SP); - gestureInfoView.setText("Touchpad Debug View 3"); - gestureInfoView.setGravity(Gravity.CENTER); - gestureInfoView.setTextColor(Color.BLACK); - gestureInfoView.setLayoutParams( + + mGestureInfoView = new TextView(context); + mGestureInfoView.setTextSize(TEXT_SIZE_SP); + mGestureInfoView.setText("Latest Gesture: "); + mGestureInfoView.setGravity(Gravity.CENTER); + mGestureInfoView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP); + mGestureInfoView.setLayoutParams( new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - addView(nameView); + updateTheme(getResources().getConfiguration().uiMode); + + addView(mNameView); addView(mTouchpadVisualizationView); - addView(gestureInfoView); + addView(mGestureInfoView); updateViewsDimensions(); } + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + postDelayed(() -> { + final ViewRootImpl viewRootImpl = getRootView().getViewRootImpl(); + if (viewRootImpl == null) { + Slog.d("TouchpadDebugView", "ViewRootImpl is null."); + return; + } + + SurfaceControl surfaceControl = viewRootImpl.getSurfaceControl(); + if (surfaceControl != null && surfaceControl.isValid()) { + try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { + transaction.setCornerRadius(surfaceControl, + TypedValue.applyDimension(COMPLEX_UNIT_DIP, + ROUNDED_CORNER_RADIUS_DP, + getResources().getDisplayMetrics())).apply(); + } + } else { + Slog.d("TouchpadDebugView", "SurfaceControl is invalid or has been released."); + } + }, 100); + } + @Override public boolean onTouchEvent(MotionEvent event) { + if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE) { + return false; + } + float deltaX; float deltaY; switch (event.getAction()) { @@ -193,13 +235,15 @@ public class TouchpadDebugView extends LinearLayout { @Override public boolean performClick() { super.performClick(); - Slog.d("TouchpadDebugView", "You tapped the window!"); + Slog.d(TAG, "You tapped the window!"); return true; } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + + updateTheme(newConfig.uiMode); updateScreenDimensions(); updateViewsDimensions(); @@ -211,6 +255,27 @@ public class TouchpadDebugView extends LinearLayout { mWindowManager.updateViewLayout(this, mWindowLayoutParams); } + private void updateTheme(int uiMode) { + int currentNightMode = uiMode & Configuration.UI_MODE_NIGHT_MASK; + if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) { + setNightModeTheme(); + } else { + setLightModeTheme(); + } + } + + private void setLightModeTheme() { + mTouchpadVisualizationView.setLightModeTheme(); + mGestureInfoView.setBackgroundColor(Color.WHITE); + mGestureInfoView.setTextColor(Color.BLACK); + } + + private void setNightModeTheme() { + mTouchpadVisualizationView.setNightModeTheme(); + mGestureInfoView.setBackgroundColor(Color.BLACK); + mGestureInfoView.setTextColor(Color.WHITE); + } + private boolean isSlopExceeded(float deltaX, float deltaY) { return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop; } @@ -265,12 +330,14 @@ public class TouchpadDebugView extends LinearLayout { return mWindowLayoutParams; } + @VisibleForTesting + TextView getGestureInfoView() { + return mGestureInfoView; + } + /** - * Notify the view of a change in the hardware state of a touchpad. The view should - * update its content to reflect the new state. - * - * @param touchpadHardwareState the hardware state of a touchpad - * @param deviceId the deviceId of the touchpad that is sending the hardware state + * Notify the view of a change in TouchpadHardwareState and changing the + * color of the view based on the status of the button click. */ public void updateHardwareState(TouchpadHardwareState touchpadHardwareState, int deviceId) { if (deviceId != mTouchpadId) { @@ -291,12 +358,43 @@ public class TouchpadDebugView extends LinearLayout { } private void onTouchpadButtonPress() { - Slog.d("TouchpadDebugView", "You clicked me!"); - getChildAt(0).setBackgroundColor(Color.BLUE); + Slog.d(TAG, "You clicked me!"); + mNameView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR); } private void onTouchpadButtonRelease() { - Slog.d("TouchpadDebugView", "You released the click"); - getChildAt(0).setBackgroundColor(Color.RED); + Slog.d(TAG, "You released the click"); + mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR); + } + + /** + * Notify the view of any new gesture on the touchpad and displaying its name + */ + public void updateGestureInfo(int newGestureType, int deviceId) { + if (deviceId == mTouchpadId && mLatestGestureType != newGestureType) { + mGestureInfoView.setText(getGestureText(newGestureType)); + mLatestGestureType = newGestureType; + } + } + + @NonNull + static String getGestureText(int gestureType) { + // These values are a representation of the GestureType enum in the + // external/libchrome-gestures/include/gestures.h library in the C++ code + String mGestureName = switch (gestureType) { + case 1 -> "Move, 1 Finger"; + case 2 -> "Scroll, 2 Fingers"; + case 3 -> "Buttons Change, 1 Fingers"; + case 4 -> "Fling"; + case 5 -> "Swipe, 3 Fingers"; + case 6 -> "Pinch, 2 Fingers"; + case 7 -> "Swipe Lift, 3 Fingers"; + case 8 -> "Metrics"; + case 9 -> "Four Finger Swipe, 4 Fingers"; + case 10 -> "Four Finger Swipe Lift, 4 Fingers"; + case 11 -> "Mouse Wheel"; + default -> "Unknown Gesture"; + }; + return "Latest Gesture: " + mGestureName; } } diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java index b4b357a29363c5ff16af6642434f0f64e79f4eca..cb43977d991151f7db996845246bd89518917373 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java @@ -155,4 +155,13 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList mTouchpadDebugView.updateHardwareState(touchpadHardwareState, deviceId); } } + + /** + * Notify the TouchpadDebugView of a new touchpad gesture. + */ + public void updateTouchpadGestureInfo(int gestureType, int deviceId) { + if (mTouchpadDebugView != null) { + mTouchpadDebugView.updateGestureInfo(gestureType, deviceId); + } + } } diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java index 67c3621b7c8c4bceef955131fe1b698899b66fb5..96426bbfe4f300a9060fc386141c3119cc8d6e92 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java @@ -18,6 +18,7 @@ package com.android.server.input.debug; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.Slog; @@ -27,20 +28,28 @@ import com.android.server.input.TouchpadFingerState; import com.android.server.input.TouchpadHardwareProperties; import com.android.server.input.TouchpadHardwareState; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; + public class TouchpadVisualizationView extends View { private static final String TAG = "TouchpadVizMain"; private static final boolean DEBUG = true; private static final float DEFAULT_RES_X = 47f; private static final float DEFAULT_RES_Y = 45f; + private static final float MAX_TRACE_HISTORY_DURATION_SECONDS = 1f; private final TouchpadHardwareProperties mTouchpadHardwareProperties; private float mScaleFactor; - TouchpadHardwareState mLatestHardwareState = new TouchpadHardwareState(0, 0, 0, 0, - new TouchpadFingerState[]{}); + private final ArrayDeque mHardwareStateHistory = + new ArrayDeque(); + private final Map mTempFingerStatesByTrackingId = new HashMap<>(); private final Paint mOvalStrokePaint; private final Paint mOvalFillPaint; + private final Paint mTracePaint; + private final Paint mCenterPointPaint; private final RectF mTempOvalRect = new RectF(); public TouchpadVisualizationView(Context context, @@ -50,11 +59,32 @@ public class TouchpadVisualizationView extends View { mScaleFactor = 1; mOvalStrokePaint = new Paint(); mOvalStrokePaint.setAntiAlias(true); - mOvalStrokePaint.setARGB(255, 0, 0, 0); mOvalStrokePaint.setStyle(Paint.Style.STROKE); mOvalFillPaint = new Paint(); mOvalFillPaint.setAntiAlias(true); - mOvalFillPaint.setARGB(255, 0, 0, 0); + mTracePaint = new Paint(); + mTracePaint.setAntiAlias(false); + mTracePaint.setARGB(255, 0, 0, 255); + mTracePaint.setStyle(Paint.Style.STROKE); + mTracePaint.setStrokeWidth(2); + mCenterPointPaint = new Paint(); + mCenterPointPaint.setAntiAlias(true); + mCenterPointPaint.setARGB(255, 255, 0, 0); + mCenterPointPaint.setStrokeWidth(2); + } + + private void removeOldPoints() { + float latestTimestamp = mHardwareStateHistory.getLast().getTimestamp(); + + while (!mHardwareStateHistory.isEmpty()) { + TouchpadHardwareState oldestPoint = mHardwareStateHistory.getFirst(); + float onScreenTime = latestTimestamp - oldestPoint.getTimestamp(); + if (onScreenTime >= MAX_TRACE_HISTORY_DURATION_SECONDS) { + mHardwareStateHistory.removeFirst(); + } else { + break; + } + } } private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle) { @@ -71,19 +101,22 @@ public class TouchpadVisualizationView extends View { @Override protected void onDraw(Canvas canvas) { + if (mHardwareStateHistory.isEmpty()) { + return; + } + + TouchpadHardwareState latestHardwareState = mHardwareStateHistory.getLast(); + float maximumPressure = 0; - for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) { + for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) { maximumPressure = Math.max(maximumPressure, touchpadFingerState.getPressure()); } - for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) { - float newX = translateRange(mTouchpadHardwareProperties.getLeft(), - mTouchpadHardwareProperties.getRight(), 0, getWidth(), - touchpadFingerState.getPositionX()); + // Visualizing fingers as ovals + for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) { + float newX = translateX(touchpadFingerState.getPositionX()); - float newY = translateRange(mTouchpadHardwareProperties.getTop(), - mTouchpadHardwareProperties.getBottom(), 0, getHeight(), - touchpadFingerState.getPositionY()); + float newY = translateY(touchpadFingerState.getPositionY()); float newAngle = translateRange(0, mTouchpadHardwareProperties.getOrientationMaximum(), 0, 90, touchpadFingerState.getOrientation()); @@ -102,6 +135,28 @@ public class TouchpadVisualizationView extends View { drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle); } + + mTempFingerStatesByTrackingId.clear(); + + // Drawing the trace + for (TouchpadHardwareState currentHardwareState : mHardwareStateHistory) { + for (TouchpadFingerState currentFingerState : currentHardwareState.getFingerStates()) { + TouchpadFingerState prevFingerState = mTempFingerStatesByTrackingId.put( + currentFingerState.getTrackingId(), currentFingerState); + + if (prevFingerState == null) { + continue; + } + + float currentX = translateX(currentFingerState.getPositionX()); + float currentY = translateY(currentFingerState.getPositionY()); + float prevX = translateX(prevFingerState.getPositionX()); + float prevY = translateY(prevFingerState.getPositionY()); + + canvas.drawLine(prevX, prevY, currentX, currentY, mTracePaint); + canvas.drawPoint(currentX, currentY, mCenterPointPaint); + } + } } /** @@ -114,7 +169,18 @@ public class TouchpadVisualizationView extends View { logHardwareState(schs); } - mLatestHardwareState = schs; + if (!mHardwareStateHistory.isEmpty() + && mHardwareStateHistory.getLast().getFingerCount() == 0 + && schs.getFingerCount() > 0) { + mHardwareStateHistory.clear(); + } + + mHardwareStateHistory.addLast(schs); + removeOldPoints(); + + if (DEBUG) { + logFingerTrace(); + } invalidate(); } @@ -128,6 +194,34 @@ public class TouchpadVisualizationView extends View { mScaleFactor = scaleFactor; } + /** + * Change the colors of the objects inside the view to light mode theme. + */ + public void setLightModeTheme() { + this.setBackgroundColor(Color.rgb(20, 20, 20)); + mOvalFillPaint.setARGB(255, 255, 255, 255); + mOvalStrokePaint.setARGB(255, 255, 255, 255); + } + + /** + * Change the colors of the objects inside the view to night mode theme. + */ + public void setNightModeTheme() { + this.setBackgroundColor(Color.rgb(240, 240, 240)); + mOvalFillPaint.setARGB(255, 0, 0, 0); + mOvalStrokePaint.setARGB(255, 0, 0, 0); + } + + private float translateX(float x) { + return translateRange(mTouchpadHardwareProperties.getLeft(), + mTouchpadHardwareProperties.getRight(), 0, getWidth(), x); + } + + private float translateY(float y) { + return translateRange(mTouchpadHardwareProperties.getTop(), + mTouchpadHardwareProperties.getBottom(), 0, getHeight(), y); + } + private float translateRange(float rangeBeforeMin, float rangeBeforeMax, float rangeAfterMin, float rangeAfterMax, float value) { return rangeAfterMin + (value - rangeBeforeMin) / (rangeBeforeMax - rangeBeforeMin) * ( @@ -154,4 +248,10 @@ public class TouchpadVisualizationView extends View { } } -} + private void logFingerTrace() { + Slog.d(TAG, "Trace size= " + mHardwareStateHistory.size()); + for (TouchpadFingerState tfs : mHardwareStateHistory.getLast().getFingerStates()) { + Slog.d(TAG, "ID= " + tfs.getTrackingId()); + } + } +} \ No newline at end of file diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index f7478799527cd2857ccece2c8ee082088f544ce3..2bb2b7b21cce8113e9d94fcab0a5f179f3cb4015 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -6107,7 +6107,22 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int argUserId = parseUserIdFromDumpArgs(args); final Printer p = new PrintWriterPrinter(pw); p.println("Current Input Method Manager state:"); - p.println(" concurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled); + p.println(" mSystemReady=" + mSystemReady); + p.println(" mInteractive=" + mIsInteractive); + p.println(" mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled); + p.println(" ENABLE_HIDE_IME_CAPTION_BAR=" + + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR); + synchronized (ImfLock.class) { + p.println(" mStylusIds=" + (mStylusIds != null + ? Arrays.toString(mStylusIds.toArray()) : "")); + } + if (Flags.imeSwitcherRevamp()) { + p.println(" mMenuControllerNew:"); + mMenuControllerNew.dump(p, " "); + } else { + p.println(" mMenuController:"); + mMenuController.dump(p, " "); + } if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) { mUserDataRepository.forAllUserData( u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical)); @@ -6116,6 +6131,22 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final var userData = getUserData(userId); dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical); } + + // TODO(b/365868861): Make StartInputHistory, SoftInputShowHideHistory and ImeTracker per + // user. + synchronized (ImfLock.class) { + p.println(" mStartInputHistory:"); + mStartInputHistory.dump(pw, " "); + + p.println(" mSoftInputShowHideHistory:"); + mSoftInputShowHideHistory.dump(pw, " "); + } + + p.println(" mImeTrackerService#History:"); + mImeTrackerService.dump(pw, " "); + + dumpUserRepository(p); + dumpClientStates(p); } @UserIdInt @@ -6140,104 +6171,33 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. userData.mUserId); final List methodList = settings.getMethodList(); int numImes = methodList.size(); - p.println(" Input Methods:"); + p.println(" Input Methods:"); for (int i = 0; i < numImes; i++) { InputMethodInfo info = methodList.get(i); - p.println(" InputMethod #" + i + ":"); - info.dump(p, " "); + p.println(" InputMethod #" + i + ":"); + info.dump(p, " "); } - // Dump ClientController#mClients - p.println(" ClientStates:"); - // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. - @SuppressWarnings("GuardedBy") Consumer clientControllerDump = c -> { - p.println(" " + c + ":"); - p.println(" client=" + c.mClient); - p.println(" fallbackInputConnection=" - + c.mFallbackInputConnection); - p.println(" sessionRequested=" - + c.mSessionRequested); - p.println(" sessionRequestedForAccessibility=" - + c.mSessionRequestedForAccessibility); - p.println(" curSession=" + c.mCurSession); - p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId); - p.println(" uid=" + c.mUid); - p.println(" pid=" + c.mPid); - }; - mClientController.forAllClients(clientControllerDump); final var bindingController = userData.mBindingController; - p.println(" mCurMethodId=" + bindingController.getSelectedMethodId()); + p.println(" mCurMethodId=" + bindingController.getSelectedMethodId()); client = userData.mCurClient; - p.println(" mCurClient=" + client + " mCurSeq=" + p.println(" mCurClient=" + client + " mCurSeq=" + bindingController.getSequenceNumber()); - p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); + p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); userData.mImeBindingState.dump(/* prefix= */ " ", p); - - p.println(" mCurId=" + bindingController.getCurId() - + " mHaveConnection=" + bindingController.hasMainConnection() - + " mBoundToMethod=" + userData.mBoundToMethod + " mVisibleBound=" - + bindingController.isVisibleBound()); - - p.println(" mUserDataRepository="); - // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. - @SuppressWarnings("GuardedBy") Consumer userDataDump = - u -> { - p.println(" mUserId=" + u.mUserId); - p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get()); - p.println(" hasMainConnection=" - + u.mBindingController.hasMainConnection()); - p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound()); - p.println(" boundToMethod=" + u.mBoundToMethod); - p.println(" curClient=" + u.mCurClient); - if (u.mCurEditorInfo != null) { - p.println(" curEditorInfo:"); - u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */); - } else { - p.println(" curEditorInfo: null"); - } - p.println(" imeBindingState:"); - u.mImeBindingState.dump(" ", p); - p.println(" enabledSession=" + u.mEnabledSession); - p.println(" inFullscreenMode=" + u.mInFullscreenMode); - p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get()); - p.println(" switchingController:"); - u.mSwitchingController.dump(p, " "); - p.println(" mLastEnabledInputMethodsStr=" - + u.mLastEnabledInputMethodsStr); - }; - mUserDataRepository.forAllUserData(userDataDump); - - if (Flags.imeSwitcherRevamp()) { - p.println(" menuControllerNew:"); - mMenuControllerNew.dump(p, " "); - } else { - p.println(" menuController:"); - mMenuController.dump(p, " "); - } - p.println(" mCurToken=" + bindingController.getCurToken()); - p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId()); - p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken()); - p.println(" mCurIntent=" + bindingController.getCurIntent()); + p.println(" mCurId=" + bindingController.getCurId()); + p.println(" mHaveConnection=" + bindingController.hasMainConnection()); + p.println(" mBoundToMethod=" + userData.mBoundToMethod); + p.println(" mVisibleBound=" + bindingController.isVisibleBound()); + p.println(" mCurToken=" + bindingController.getCurToken()); + p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId()); + p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken()); + p.println(" mCurIntent=" + bindingController.getCurIntent()); method = bindingController.getCurMethod(); - p.println(" mCurMethod=" + method); - p.println(" mEnabledSession=" + userData.mEnabledSession); + p.println(" mCurMethod=" + method); + p.println(" mEnabledSession=" + userData.mEnabledSession); final var visibilityStateComputer = userData.mVisibilityStateComputer; visibilityStateComputer.dump(pw, " "); - p.println(" mInFullscreenMode=" + userData.mInFullscreenMode); - p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive); - p.println(" mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled); - p.println(" ENABLE_HIDE_IME_CAPTION_BAR=" - + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR); - p.println(" mStylusIds=" + (mStylusIds != null - ? Arrays.toString(mStylusIds.toArray()) : "")); - - p.println(" mStartInputHistory:"); - mStartInputHistory.dump(pw, " "); - - p.println(" mSoftInputShowHideHistory:"); - mSoftInputShowHideHistory.dump(pw, " "); - - p.println(" mImeTrackerService#History:"); - mImeTrackerService.dump(pw, " "); + p.println(" mInFullscreenMode=" + userData.mInFullscreenMode); } // Exit here for critical dump, as remaining sections require IPCs to other processes. @@ -6288,6 +6248,61 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + private void dumpClientStates(Printer p) { + p.println(" ClientStates:"); + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer clientControllerDump = c -> { + p.println(" " + c + ":"); + p.println(" client=" + c.mClient); + p.println(" fallbackInputConnection=" + + c.mFallbackInputConnection); + p.println(" sessionRequested=" + + c.mSessionRequested); + p.println(" sessionRequestedForAccessibility=" + + c.mSessionRequestedForAccessibility); + p.println(" curSession=" + c.mCurSession); + p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId); + p.println(" uid=" + c.mUid); + p.println(" pid=" + c.mPid); + }; + synchronized (ImfLock.class) { + mClientController.forAllClients(clientControllerDump); + } + } + + private void dumpUserRepository(Printer p) { + p.println(" mUserDataRepository="); + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer userDataDump = + u -> { + p.println(" mUserId=" + u.mUserId); + p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get()); + p.println(" hasMainConnection=" + + u.mBindingController.hasMainConnection()); + p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound()); + p.println(" boundToMethod=" + u.mBoundToMethod); + p.println(" curClient=" + u.mCurClient); + if (u.mCurEditorInfo != null) { + p.println(" curEditorInfo:"); + u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */); + } else { + p.println(" curEditorInfo: null"); + } + p.println(" imeBindingState:"); + u.mImeBindingState.dump(" ", p); + p.println(" enabledSession=" + u.mEnabledSession); + p.println(" inFullscreenMode=" + u.mInFullscreenMode); + p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get()); + p.println(" switchingController:"); + u.mSwitchingController.dump(p, " "); + p.println(" mLastEnabledInputMethodsStr=" + + u.mLastEnabledInputMethodsStr); + }; + synchronized (ImfLock.class) { + mUserDataRepository.forAllUserData(userDataDump); + } + } + @BinderThread @Override public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING index 1d2cd3c6e217aae5415d5b69f87c94e10eb3bc21..8abdf0069e1f5a46bab654dec82994c26818050c 100644 --- a/services/core/java/com/android/server/lights/TEST_MAPPING +++ b/services/core/java/com/android/server/lights/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsHardwareTestCases", - "options": [ - {"include-filter": "com.android.hardware.lights"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"} - ] + "name": "CtsHardwareTestCases_hardware_lights" }, { "name": "FrameworksServicesTests_android_server_lights" diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING index 64b1ed20656e6e420b86215233e973ae31b49245..b2ac7d1ef7e7694426cd254e84d77d84e7412a40 100644 --- a/services/core/java/com/android/server/location/TEST_MAPPING +++ b/services/core/java/com/android/server/location/TEST_MAPPING @@ -1,13 +1,7 @@ { "presubmit": [ { - "name": "CtsLocationFineTestCases", - "options": [ - { - // TODO: Wait for test to deflake - b/293934372 - "exclude-filter":"android.location.cts.fine.ScanningSettingsTest" - } - ] + "name": "CtsLocationFineTestCases_android_server_location" }, { "name": "CtsLocationCoarseTestCases" diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 4b2c12abe5bbd6f544add5055a4ace5447971255..63bd9ab815b26d7b2d84a94ddfd92fcca1977d95 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -389,7 +389,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // Reload gnss config for no SIM case mGnssConfiguration.reloadGpsProperties(); } - if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { updateNiSuplMessageListenerRegistration( mGnssConfiguration.isNiSuplMessageInjectionEnabled()); } @@ -538,7 +538,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler); - if (!Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (!Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { updateNiSuplMessageListenerRegistration( mGnssConfiguration.isNiSuplMessageInjectionEnabled()); } @@ -1672,7 +1672,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements if (dumpAll) { mNetworkTimeHelper.dump(pw); pw.println("mSupportsPsds=" + mSupportsPsds); - if (Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { pw.println("mNiSuplMessageListenerRegistered=" + mNiSuplMessageListenerRegistered); } diff --git a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java index 01c108bd5067343f86890a3ba574137a79efbd3e..494ea7714ff91c2cc40b759b66146a7786d96975 100644 --- a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java +++ b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java @@ -19,6 +19,7 @@ import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.content.Context; +import android.location.flags.Flags; import android.os.Looper; import java.io.PrintWriter; @@ -55,7 +56,7 @@ abstract class NetworkTimeHelper { static NetworkTimeHelper create( @NonNull Context context, @NonNull Looper looper, @NonNull InjectTimeCallback injectTimeCallback) { - if (USE_TIME_DETECTOR_IMPL) { + if (!Flags.useLegacyNtpTime()) { TimeDetectorNetworkTimeHelper.Environment environment = new TimeDetectorNetworkTimeHelper.EnvironmentImpl(looper); return new TimeDetectorNetworkTimeHelper(environment, injectTimeCallback); diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java index df45a6ec985c512864b5a089fb88ed450d34105f..177eefb2ef2a3f26a7bdf86c1d5d1e79eda3d77b 100644 --- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java @@ -76,11 +76,12 @@ public class SystemEmergencyHelper extends EmergencyHelper { try { mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber( intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)); - dispatchEmergencyStateChanged(); } catch (IllegalStateException | UnsupportedOperationException e) { Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e); } } + + dispatchEmergencyStateChanged(); } }, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL)); @@ -140,9 +141,10 @@ public class SystemEmergencyHelper extends EmergencyHelper { if (mIsInEmergencyCall) { mEmergencyCallEndRealtimeMs = SystemClock.elapsedRealtime(); mIsInEmergencyCall = false; - dispatchEmergencyStateChanged(); } } + + dispatchEmergencyStateChanged(); } } } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 158d444bcff2bc2a2fbccbc3a0b9eda501d6d84c..1e25f1cf1d5e13e22cec51d743dec35c9c39deaa 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -140,7 +140,7 @@ class LockSettingsStorage { try { db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", new String[] {key, Integer.toString(userId)}); - db.insert(TABLE, null, cv); + db.insertOrThrow(TABLE, null, cv); db.setTransactionSuccessful(); mCache.putKeyValue(key, value, userId); } finally { diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING index ffbdf7f2bf8bb766ae52236bea2604c204342030..d338c50bb8c155661eea337de4fbb210d895b667 100644 --- a/services/core/java/com/android/server/locksettings/TEST_MAPPING +++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit-large": [ { - "name": "CtsDevicePolicyManagerTestCases", - "options": [ - { - "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsDevicePolicyManagerTestCases_LockSettingsTest" } ], "presubmit": [ diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java index f27ade42a738f1e9bdd8a32db4919e60e44245a2..c79f41d32b295bdf393a6aa9cad9a7741628eada 100644 --- a/services/core/java/com/android/server/media/AudioManagerRouteController.java +++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java @@ -442,7 +442,7 @@ import java.util.Objects; @Nullable private MediaRoute2Info createMediaRoute2Info( @Nullable String routeId, - int audioDeviceInfoType, + @AudioDeviceInfo.AudioDeviceType int audioDeviceInfoType, @Nullable CharSequence deviceName, @Nullable String address) { SystemRouteInfo systemRouteInfo = @@ -490,7 +490,8 @@ import java.util.Objects; public final boolean mCorrespondsToInactiveBluetoothRoute; public static MediaRoute2InfoHolder createForAudioManagerRoute( - MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType) { + MediaRoute2Info mediaRoute2Info, + @AudioDeviceInfo.AudioDeviceType int audioDeviceInfoType) { return new MediaRoute2InfoHolder( mediaRoute2Info, audioDeviceInfoType, @@ -509,7 +510,7 @@ import java.util.Objects; private MediaRoute2InfoHolder( MediaRoute2Info mediaRoute2Info, - int audioDeviceInfoType, + @AudioDeviceInfo.AudioDeviceType int audioDeviceInfoType, boolean correspondsToInactiveBluetoothRoute) { mMediaRoute2Info = mediaRoute2Info; mAudioDeviceInfoType = audioDeviceInfoType; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 621c090d37b8fcaeb3ac74f155fbed3934cac848..48d24f2e14dd52076a446f9fc21d75c277a92e20 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -179,7 +179,8 @@ public final class MediaProjectionManagerService extends SystemService /** * In order to record the keyguard, the MediaProjection package must be either: * - a holder of RECORD_SENSITIVE_CONTENT permission, or - * - be one of the bugreport whitelisted packages + * - be one of the bugreport allowlisted packages, or + * - hold the OP_PROJECT_MEDIA AppOp. */ private boolean canCaptureKeyguard() { if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { @@ -194,6 +195,14 @@ public final class MediaProjectionManagerService extends SystemService == PackageManager.PERMISSION_GRANTED) { return true; } + boolean operationActive = mAppOps.isOperationActive(AppOpsManager.OP_PROJECT_MEDIA, + mProjectionGrant.uid, + mProjectionGrant.packageName); + if (operationActive) { + // Some tools use media projection by granting the OP_PROJECT_MEDIA app + // op via a shell command. Those tools can be granted keyguard capture + return true; + } return SystemConfig.getInstance().getBugreportWhitelistedPackages() .contains(mProjectionGrant.packageName); } diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING index 7aa9118e45ee6cbcc7d51caae3e0441a9b30df5d..b33097c500021ea290ca46a82b071b9695550443 100644 --- a/services/core/java/com/android/server/media/projection/TEST_MAPPING +++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING @@ -1,15 +1,7 @@ { "presubmit": [ { - "name": "MediaProjectionTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "MediaProjectionTests" } ] } diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING index ad6b0ca715274bc7584936435f7ca227c8b67715..d95849ec6d6ab77d6031662ae645cf02f17411d5 100644 --- a/services/core/java/com/android/server/net/TEST_MAPPING +++ b/services/core/java/com/android/server/net/TEST_MAPPING @@ -1,40 +1,16 @@ { "presubmit-large": [ { - "name": "CtsHostsideNetworkPolicyTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - } - ] + "name": "CtsHostsideNetworkPolicyTests" } ], "presubmit": [ { - "name": "FrameworksServicesTests", - "file_patterns": ["(/|^)Network(Policy|Management)[^/]*\\.java"], - "options": [ - { - "include-filter": "com.android.server.net." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksServicesTests_android_server_net_Presubmit", + "file_patterns": ["(/|^)Network(Policy|Management)[^/]*\\.java"] }, { - "name": "FrameworksVpnTests", - "options": [ - { - "exclude-annotation": "com.android.testutils.SkipPresubmit" - } - ], + "name": "FrameworksVpnTests_android_server_connectivity", "file_patterns": ["VpnManagerService\\.java"] } ] diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index 82e00d9b4cbd707c2ae7eb4705ad87e66de32341..9d30c565bd969f5450b386e74a47dd20884c2b90 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -759,7 +759,7 @@ public class GroupHelper { if (Flags.notificationForceGroupSingletons()) { try { groupSparseGroups(record, notificationList, summaryByGroupKey, sectioner, - fullAggregateGroupKey); + fullAggregateGroupKey); } catch (Throwable e) { Slog.wtf(TAG, "Failed to group sparse groups", e); } @@ -1197,6 +1197,11 @@ public class GroupHelper { final ArrayMap aggregatedNotificationsAttrs = mAggregatedNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>()); final boolean hasSummary = !aggregatedNotificationsAttrs.isEmpty(); + String triggeringKey = null; + if (!record.getNotification().isGroupSummary()) { + // Use this record as triggeringKey only if not a group summary (will be removed) + triggeringKey = record.getKey(); + } for (NotificationRecord r : notificationList) { // Add notifications for detected sparse groups if (sparseGroupSummaries.containsKey(r.getGroupKey())) { @@ -1213,6 +1218,10 @@ public class GroupHelper { r.getNotification().getGroupAlertBehavior(), r.getChannel().getId())); + // Pick the first valid triggeringKey + if (triggeringKey == null) { + triggeringKey = r.getKey(); + } } else if (r.getNotification().isGroupSummary()) { // Remove summary notifications if (DEBUG) { @@ -1235,7 +1244,7 @@ public class GroupHelper { mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs); // add/update aggregate summary - updateAggregateAppGroup(fullAggregateGroupKey, record.getKey(), hasSummary, + updateAggregateAppGroup(fullAggregateGroupKey, triggeringKey, hasSummary, sectioner.mSummaryId); //cleanup mUngroupedAbuseNotifications diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING index 468c4518602eea4c1dd61b8dc7a08967007592fc..dc7129cde5e5b10820806a8b3024018ec3ec7a0b 100644 --- a/services/core/java/com/android/server/notification/TEST_MAPPING +++ b/services/core/java/com/android/server/notification/TEST_MAPPING @@ -1,32 +1,10 @@ { "presubmit": [ { - "name": "CtsNotificationTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsNotificationTestCases_notification" }, { - "name": "FrameworksUiServicesTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "FrameworksUiServicesTests_notification" } ], "postsubmit": [ diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index fbe77720b9fc36e6b88b73f4a75b1b58ac68a5a2..f54c1f7654dc19b4dd990070682a20781230dd25 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -218,10 +218,16 @@ public final class VibratorHelper { * @param uri {@code Uri} an uri including query parameter "vibraiton_uri" */ public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) { - if (uri == null) { + if (uri == null || uri.isOpaque()) { return null; } - return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri)); + + try { + return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri)); + } catch (Exception e) { + Slog.e(TAG, "Failed to get vibration effect: ", e); + } + return null; } /** Returns if a given vibration can be played by the vibrator that does notification buzz. */ diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index d495ef5ce108393926f323c1ea77774409d96730..50bfbc3530a90ce7828a7d8fdb06467ed202bd2d 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -157,7 +157,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { } // empty rule? disable and bail early if (rule.component == null && rule.enabler == null) { - if (!android.app.Flags.modesUi() || (android.app.Flags.modesUi() && !isManual)) { + if (!isManual) { Log.w(TAG, "No component found for automatic rule: " + rule.conditionId); rule.enabled = false; } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index e9db1b529a63dca1606a47af808f151372ebdabc..0eb4cbda72bfafab8e8b6a4dc0f76e8d7197de3e 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1358,7 +1358,8 @@ public class ZenModeHelper { if (isNew) { // Newly created rule with no provided policy; fill in with the default. zenRule.zenPolicy = - Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy(); + (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy()) + .copy(); return true; } // Otherwise, a null policy means no policy changes, so we can stop here. @@ -1773,7 +1774,7 @@ public class ZenModeHelper { // definition cannot have a rule with TYPE_BEDTIME (or any other type). config.automaticRules = new ArrayMap<>(); for (ZenRule rule : mDefaultConfig.automaticRules.values()) { - config.automaticRules.put(rule.id, rule); + config.automaticRules.put(rule.id, rule.copy()); } reason += ", reset to default rules"; } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index a41675a4aac5b54efa777eb400d6ed3b45f0cd06..6303ecd53dbbf2d1933e8abd723c8f809d35ffe7 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -298,13 +298,12 @@ public final class OverlayManagerService extends SystemService { restoreSettings(); - if (Build.IS_USER) { - // Wipe all shell overlays on boot, to recover from a potentially broken device - String shellPkgName = TextUtils.emptyIfNull( - getContext().getString(android.R.string.config_systemShell)); - mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated - && shellPkgName.equals(overlayInfo.packageName)); - } + // Wipe all shell overlays on boot, to recover from a potentially broken device + String shellPkgName = TextUtils.emptyIfNull( + getContext().getString(android.R.string.config_systemShell)); + mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated + && shellPkgName.equals(overlayInfo.packageName)); + initIfNeeded(); onStartUser(UserHandle.USER_SYSTEM); diff --git a/services/core/java/com/android/server/os/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING index 50c8964b2aa43659c1509467aaff56f98fc15dff..3ffcd186dc4b6d030463c7cdcc1882ca9cbe3f14 100644 --- a/services/core/java/com/android/server/os/TEST_MAPPING +++ b/services/core/java/com/android/server/os/TEST_MAPPING @@ -2,36 +2,18 @@ "presubmit": [ { "file_patterns": ["Bugreport[^/]*\\.java"], - "name": "BugreportManagerTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "BugreportManagerTestCases_android_server_os" }, { "file_patterns": ["Bugreport[^/]*\\.java"], - "name": "CtsBugreportTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } - ] + "name": "CtsBugreportTestCases_android_server_os" }, { "name": "CtsUsbTests" }, { "file_patterns": ["Bugreport[^/]*\\.java"], - "name": "ShellTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "ShellTests_android_server_os" } ], "postsubmit": [ diff --git a/services/core/java/com/android/server/pm/Android.bp b/services/core/java/com/android/server/pm/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..d625cf2e8e770f6a59ec73c3d923c04c45a89639 --- /dev/null +++ b/services/core/java/com/android/server/pm/Android.bp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package { + default_team: "trendy_team_framework_android_packages", + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "framework-pm-service-sources", + srcs: [ + "**/*.java", + "**/*.aidl", + ], + exclude_srcs: [ + "dex/**/*.java", + "User*.java", + "Shortcut*.java", + ], + visibility: ["//frameworks/base"], +} diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java index 41351613331dd0fbd3fc56957ba3aeabd2f3677d..49a6ffde6783e78fee91750b547941e792443cf2 100644 --- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java +++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.media.AudioAttributes.USAGE_ALARM; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Notification; @@ -42,6 +43,8 @@ import android.util.Log; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import java.util.List; + public class BackgroundUserSoundNotifier { private static final boolean DEBUG = false; @@ -49,11 +52,21 @@ public class BackgroundUserSoundNotifier { private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel"; private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound"; public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER"; - private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID"; - private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID"; private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER"; - /** ID of user with notification displayed, -1 if notification is not showing*/ - private int mUserWithNotification = -1; + private static final String ACTION_DISMISS_NOTIFICATION = + "com.android.server.ACTION_DISMISS_NOTIFICATION"; + /** + * The clientUid from the AudioFocusInfo of the background user, + * for which an active notification is currently displayed. + * Set to -1 if no notification is being shown. + * TODO: b/367615180 - add support for multiple simultaneous alarms + */ + @VisibleForTesting + int mNotificationClientUid = -1; + @VisibleForTesting + AudioPolicy mFocusControlAudioPolicy; + @VisibleForTesting + BackgroundUserListener mBgUserListener; private final Context mSystemUserContext; @VisibleForTesting final NotificationManager mNotificationManager; @@ -67,11 +80,18 @@ public class BackgroundUserSoundNotifier { mSystemUserContext = context; mNotificationManager = mSystemUserContext.getSystemService(NotificationManager.class); mUserManager = mSystemUserContext.getSystemService(UserManager.class); + createNotificationChannel(); + setupFocusControlAudioPolicy(); + } + + /** + * Creates a dedicated channel for background user related notifications. + */ + private void createNotificationChannel() { NotificationChannel channel = new NotificationChannel(BUSN_CHANNEL_ID, BUSN_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH); channel.setSound(null, null); mNotificationManager.createNotificationChannel(channel); - setupFocusControlAudioPolicy(); } private void setupFocusControlAudioPolicy() { @@ -81,15 +101,16 @@ public class BackgroundUserSoundNotifier { ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class); registerReceiver(am); - BackgroundUserListener bgUserListener = new BackgroundUserListener(mSystemUserContext); + mBgUserListener = new BackgroundUserListener(mSystemUserContext); AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext); focusControlPolicyBuilder.setLooper(Looper.getMainLooper()); - focusControlPolicyBuilder.setAudioPolicyFocusListener(bgUserListener); + focusControlPolicyBuilder.setAudioPolicyFocusListener(mBgUserListener); - AudioPolicy mFocusControlAudioPolicy = focusControlPolicyBuilder.build(); + mFocusControlAudioPolicy = focusControlPolicyBuilder.build(); int status = mSystemUserContext.getSystemService(AudioManager.class) .registerAudioPolicy(mFocusControlAudioPolicy); + if (status != AudioManager.SUCCESS) { Log.w(LOG_TAG , "Could not register the service's focus" + " control audio policy, error: " + status); @@ -117,123 +138,176 @@ public class BackgroundUserSoundNotifier { @SuppressLint("MissingPermission") public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { - BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi); + BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(); } } + @VisibleForTesting + BackgroundUserListener getAudioPolicyFocusListener() { + return mBgUserListener; + } + /** * Registers a BroadcastReceiver for actions related to background user sound notifications. * When ACTION_MUTE_SOUND is received, it mutes a background user's alarm sound. * When ACTION_SWITCH_USER is received, a switch to the background user with alarm is started. */ - private void registerReceiver(ActivityManager service) { + private void registerReceiver(ActivityManager activityManager) { BroadcastReceiver backgroundUserNotificationBroadcastReceiver = new BroadcastReceiver() { @SuppressLint("MissingPermission") @Override public void onReceive(Context context, Intent intent) { - if (!(intent.hasExtra(EXTRA_NOTIFICATION_ID) - && intent.hasExtra(EXTRA_CURRENT_USER_ID) - && intent.hasExtra(Intent.EXTRA_USER_ID))) { + if (mNotificationClientUid == -1) { return; } - final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); + dismissNotification(); if (DEBUG) { - Log.d(LOG_TAG, - "User with alarm id " + intent.getIntExtra(Intent.EXTRA_USER_ID, - -1) + " current user id " + intent.getIntExtra( - EXTRA_CURRENT_USER_ID, -1)); + final int actionIndex = intent.getAction().lastIndexOf(".") + 1; + final String action = intent.getAction().substring(actionIndex); + Log.d(LOG_TAG, "Action requested: " + action + ", by userId " + + ActivityManager.getCurrentUser() + " for alarm on user " + + UserHandle.getUserHandleForUid(mNotificationClientUid)); } - mUserWithNotification = -1; - mNotificationManager.cancelAsUser(LOG_TAG, notificationId, - UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1))); + if (ACTION_MUTE_SOUND.equals(intent.getAction())) { - final AudioManager audioManager = - mSystemUserContext.getSystemService(AudioManager.class); - if (audioManager != null) { - for (AudioPlaybackConfiguration apc : - audioManager.getActivePlaybackConfigurations()) { - if (apc.getAudioAttributes().getUsage() == USAGE_ALARM) { - if (apc.getPlayerProxy() != null) { - apc.getPlayerProxy().stop(); - } - } - } - } + muteAlarmSounds(mSystemUserContext); } else if (ACTION_SWITCH_USER.equals(intent.getAction())) { - service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1)); + activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid)); } + + mNotificationClientUid = -1; } }; IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_MUTE_SOUND); filter.addAction(ACTION_SWITCH_USER); + filter.addAction(ACTION_DISMISS_NOTIFICATION); mSystemUserContext.registerReceiver(backgroundUserNotificationBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); } + /** + * Stop player proxy for the ongoing alarm and drop focus for its AudioFocusInfo. + */ + @SuppressLint("MissingPermission") + @VisibleForTesting + void muteAlarmSounds(Context context) { + AudioManager audioManager = context.getSystemService(AudioManager.class); + if (audioManager != null) { + for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) { + if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) { + apc.getPlayerProxy().stop(); + } + } + } + + AudioFocusInfo currentAfi = getAudioFocusInfoForNotification(); + if (currentAfi != null) { + mFocusControlAudioPolicy.sendFocusLossAndUpdate(currentAfi); + } + } + /** * Check if sound is coming from background user and show notification is required. */ @VisibleForTesting - void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context - foregroundContext) throws RemoteException { + void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext) + throws RemoteException { final int userId = UserHandle.getUserId(afi.getClientUid()); final int usage = afi.getAttributes().getUsage(); UserInfo userInfo = mUserManager.getUserInfo(userId); - if (userInfo != null && userId != foregroundContext.getUserId()) { + // Only show notification if the sound is coming from background user and the notification + // is not already shown. + if (userInfo != null && userId != foregroundContext.getUserId() + && mNotificationClientUid == -1) { //TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE if (usage == USAGE_ALARM) { - Intent muteIntent = createIntent(ACTION_MUTE_SOUND, afi, foregroundContext, userId); - PendingIntent mutePI = PendingIntent.getBroadcast(mSystemUserContext, 0, - muteIntent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_IMMUTABLE); - Intent switchIntent = createIntent(ACTION_SWITCH_USER, afi, foregroundContext, - userId); - PendingIntent switchPI = PendingIntent.getBroadcast(mSystemUserContext, 0, - switchIntent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_IMMUTABLE); - - mUserWithNotification = foregroundContext.getUserId(); - mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(), - createNotification(userInfo.name, mutePI, switchPI, foregroundContext), + if (DEBUG) { + Log.d(LOG_TAG, "Alarm ringing on background user " + userId + + ", displaying notification for current user " + + foregroundContext.getUserId()); + } + + mNotificationClientUid = afi.getClientUid(); + + mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid, + createNotification(userInfo.name, foregroundContext), foregroundContext.getUser()); } } } /** - * If notification is present, dismisses it. To be called when the relevant sound loses focus. + * Dismisses notification if the associated focus has been removed from the focus stack. + * Notification remains if the focus is temporarily lost due to another client taking over the + * focus ownership. + */ + @VisibleForTesting + void dismissNotificationIfNecessary() { + if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) { + if (DEBUG) { + Log.d(LOG_TAG, "Alarm ringing on background user " + + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier() + + " left focus stack, dismissing notification"); + } + dismissNotification(); + mNotificationClientUid = -1; + } + } + + /** + * Dismisses notification for all users in case user switch occurred after notification was + * shown. + */ + @SuppressLint("MissingPermission") + private void dismissNotification() { + mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL); + } + + /** + * Returns AudioFocusInfo associated with the current notification. */ - private void dismissNotificationIfNecessary(AudioFocusInfo afi) { - if (mUserWithNotification >= 0) { - mNotificationManager.cancelAsUser(LOG_TAG, afi.getClientUid(), - UserHandle.of(mUserWithNotification)); + @SuppressLint("MissingPermission") + @VisibleForTesting + @Nullable + AudioFocusInfo getAudioFocusInfoForNotification() { + if (mNotificationClientUid >= 0) { + List stack = mFocusControlAudioPolicy.getFocusStack(); + for (int i = stack.size() - 1; i >= 0; i--) { + if (stack.get(i).getClientUid() == mNotificationClientUid) { + return stack.get(i); + } + } } - mUserWithNotification = -1; + return null; } - private Intent createIntent(String intentAction, AudioFocusInfo afi, Context fgUserContext, - int userId) { + private PendingIntent createPendingIntent(String intentAction) { final Intent intent = new Intent(intentAction); - intent.putExtra(EXTRA_CURRENT_USER_ID, fgUserContext.getUserId()); - intent.putExtra(EXTRA_NOTIFICATION_ID, afi.getClientUid()); - intent.putExtra(Intent.EXTRA_USER_ID, userId); - return intent; + PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + return resultPI; } - private Notification createNotification(String userName, PendingIntent muteIntent, - PendingIntent switchIntent, Context fgContext) { + @VisibleForTesting + Notification createNotification(String userName, Context fgContext) { final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm, userName); final int icon = R.drawable.ic_audio_alarm; + + PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND); + PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER); + PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION); + final Notification.Action mute = new Notification.Action.Builder(null, fgContext.getString(R.string.bg_user_sound_notification_button_mute), - muteIntent).build(); + mutePI).build(); final Notification.Action switchUser = new Notification.Action.Builder(null, fgContext.getString(R.string.bg_user_sound_notification_button_switch_user), - switchIntent).build(); + switchPI).build(); + Notification.Builder notificationBuilder = new Notification.Builder(mSystemUserContext, BUSN_CHANNEL_ID) .setSmallIcon(icon) @@ -243,16 +317,18 @@ public class BackgroundUserSoundNotifier { .setOngoing(true) .setColor(fgContext.getColor(R.color.system_notification_accent_color)) .setContentTitle(title) - .setContentIntent(muteIntent) + .setContentIntent(mutePI) .setAutoCancel(true) + .setDeleteIntent(dismissNotificationPI) .setVisibility(Notification.VISIBILITY_PUBLIC); + if (mUserManager.isUserSwitcherEnabled() && (mUserManager.getUserSwitchability( - UserHandle.of(fgContext.getUserId())) == UserManager.SWITCHABILITY_STATUS_OK)) { + fgContext.getUser()) == UserManager.SWITCHABILITY_STATUS_OK)) { notificationBuilder.setActions(mute, switchUser); } else { notificationBuilder.setActions(mute); } + return notificationBuilder.build(); } } - diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index ee15bec0d62b468942ef33548f61902da9c5f575..efd58ed6edcc140dae597fda2fa5f01174ae212c 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -92,7 +92,6 @@ import android.graphics.Rect; import android.multiuser.Flags; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IInterface; @@ -215,7 +214,7 @@ public class LauncherAppsService extends SystemService { @VisibleForTesting static class LauncherAppsImpl extends ILauncherApps.Stub { - private static final boolean DEBUG = Build.IS_DEBUGGABLE; + private static final boolean DEBUG = false; private static final String TAG = "LauncherAppsService"; private static final String NAMESPACE_MULTIUSER = "multiuser"; private static final String FLAG_NON_SYSTEM_ACCESS_TO_HIDDEN_PROFILES = @@ -496,28 +495,8 @@ public class LauncherAppsService extends SystemService { private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid, int targetUserId, String message) { - if (DEBUG) { - final AndroidPackage callingPackage = - mPackageManagerInternal.getPackage(callingUid); - final String callingPackageName = callingPackage == null - ? null : callingPackage.getPackageName(); - Slog.v(TAG, "canAccessProfile called by " + callingPackageName - + " for user " + callingUserId - + " requesting to access user " - + targetUserId + " when invoking " + message); - } - if (targetUserId == callingUserId) { - if (DEBUG) { - Slog.v(TAG, message + " passed canAccessProfile for targetuser" - + targetUserId + " because it is the same as the calling user"); - } - return true; - } + if (targetUserId == callingUserId) return true; if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) { - if (DEBUG) { - Slog.v(TAG, message + " passed because calling process" - + "has permission to interact across users"); - } return true; } @@ -535,25 +514,11 @@ public class LauncherAppsService extends SystemService { if (isHiddenProfile(UserHandle.of(targetUserId)) && !canAccessHiddenProfile(callingUid, callingPid)) { - Slog.w(TAG, message + " for hidden profile user " + targetUserId - + " from " + callingUserId + " not allowed"); - return false; } - final boolean ret = mUserManagerInternal.isProfileAccessible( - callingUserId, targetUserId, message, true); - if (DEBUG) { - final AndroidPackage callingPackage = - mPackageManagerInternal.getPackage(callingUid); - final String callingPackageName = callingPackage == null - ? null : callingPackage.getPackageName(); - Slog.v(TAG, "canAccessProfile returned " + ret + " for " + callingPackageName - + " for user " + callingUserId - + " requesting to access user " - + targetUserId + " when invoking " + message); - } - return ret; + return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId, + message, true); } private boolean isHiddenProfile(UserHandle targetUser) { @@ -1376,10 +1341,6 @@ public class LauncherAppsService extends SystemService { @Override public void pinShortcuts(String callingPackage, String packageName, List ids, UserHandle targetUser) { - if (DEBUG) { - Slog.v(TAG, "pinShortcuts: " + callingPackage + " is pinning shortcuts from " - + packageName + " for user " + targetUser); - } if (!mShortcutServiceInternal .areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) { // Requires strict ACCESS_SHORTCUTS permission for user-profiles with items @@ -1390,11 +1351,6 @@ public class LauncherAppsService extends SystemService { } ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) { - if (DEBUG) { - Slog.v(TAG, "pinShortcuts: " + callingPackage - + " is pinning shortcuts from " + packageName - + " for user " + targetUser + " but cannot access profile"); - } return; } @@ -2451,7 +2407,7 @@ public class LauncherAppsService extends SystemService { final int callbackUserId = callbackUser.getIdentifier(); final int shortcutUserId = shortcutUser.getIdentifier(); - if (shortcutUser == callbackUser) return true; + if ((shortcutUser.equals(callbackUser))) return true; return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId, null, false); } @@ -2485,16 +2441,28 @@ public class LauncherAppsService extends SystemService { final BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(cookie, user, "onPackageRemoved")) { + // b/350144057 + Slog.d(TAG, "onPackageRemoved: Skipping - profile not enabled" + + " or not accessible for user=" + user + + ", packageName=" + packageName); continue; } if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId( cookie.callingUid))) { + // b/350144057 + Slog.d(TAG, "onPackageRemoved: Skipping - appId not allowed" + + " for user=" + user + + ", packageName=" + packageName); continue; } try { + // b/350144057 + Slog.d(TAG, "onPackageRemoved: triggering onPackageRemoved" + + " for user=" + user + + ", packageName=" + packageName); listener.onPackageRemoved(user, packageName); } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + Slog.d(TAG, "onPackageRemoved: Callback failed ", re); } } } finally { @@ -2524,15 +2492,27 @@ public class LauncherAppsService extends SystemService { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(cookie, user, "onPackageAdded")) { + // b/350144057 + Slog.d(TAG, "onPackageAdded: Skipping - profile not enabled" + + " or not accessible for user=" + user + + ", packageName=" + packageName); continue; } if (!isPackageVisibleToListener(packageName, cookie, user)) { + // b/350144057 + Slog.d(TAG, "onPackageAdded: Skipping - package filtered" + + " for user=" + user + + ", packageName=" + packageName); continue; } try { + // b/350144057 + Slog.d(TAG, "onPackageAdded: triggering onPackageAdded" + + " for user=" + user + + ", packageName=" + packageName); listener.onPackageAdded(user, packageName); } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + Slog.d(TAG, "onPackageAdded: Callback failed ", re); } } } finally { @@ -2566,7 +2546,7 @@ public class LauncherAppsService extends SystemService { try { listener.onPackageChanged(user, packageName); } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); + Slog.d(TAG, "onPackageChanged: Callback failed ", re); } } } finally { diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index d65e30be9edb0c712959abac58eaf4074b7a39f9..045d4db0a1f18a51e33203eb862027a73e7d4c66 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -42,7 +42,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** * Launcher information used by {@link ShortcutService}. @@ -129,15 +128,9 @@ class ShortcutLauncher extends ShortcutPackageItem { */ public void pinShortcuts(@UserIdInt int packageUserId, @NonNull String packageName, @NonNull List ids, boolean forPinRequest) { - if (ShortcutService.DEBUG) { - Slog.v(TAG, "ShortcutLauncher#pinShortcuts: pin shortcuts from " + packageName - + " with userId=" + packageUserId + " shortcutIds=" - + ids.stream().collect(Collectors.joining(", ", "[", "]"))); - } final ShortcutPackage packageShortcuts = mShortcutUser.getPackageShortcutsIfExists(packageName); if (packageShortcuts == null) { - Slog.w(TAG, "ShortcutLauncher#pinShortcuts packageShortcuts is null"); return; // No need to instantiate. } @@ -162,10 +155,6 @@ class ShortcutLauncher extends ShortcutPackageItem { final String id = ids.get(i); final ShortcutInfo si = packageShortcuts.findShortcutById(id); if (si == null) { - if (ShortcutService.DEBUG) { - Slog.w(TAG, "ShortcutLauncher#pinShortcuts: cannot pin " - + id + " because it does not exist"); - } continue; } if (si.isDynamic() || si.isLongLived() @@ -185,13 +174,6 @@ class ShortcutLauncher extends ShortcutPackageItem { } } } - if (ShortcutService.DEBUG) { - Slog.v(TAG, "ShortcutLauncher#pinShortcuts: " - + " newSet: " + newSet.stream().collect( - Collectors.joining(", ", "[", "]")) - + " floatingSet: " + floatingSet.stream().collect( - Collectors.joining(", ", "[", "]"))); - } mPinnedShortcuts.put(up, newSet); } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index c9ad4988f8ca79280ce0a9f7a667f2a929c1f944..60056eb471d153c1d10b957ba7aec197be479a91 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -729,11 +729,6 @@ class ShortcutPackage extends ShortcutPackageItem { } pinnedShortcuts.addAll(pinned); }); - if (ShortcutService.DEBUG) { - Slog.v(TAG, "ShortcutPackage#refreshPinnedFlags: " - + " pinnedShortcuts: " + pinnedShortcuts.stream().collect( - Collectors.joining(", ", "[", "]"))); - } // Secondly, update the pinned state if necessary. final List pinned = findAll(pinnedShortcuts); if (pinned != null) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index ea495c9bee9c461727d9d06a67d460474e47a33b..a3ff1952205fe0378598e2ebb487a2e0e8405b9c 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -169,7 +169,7 @@ import java.util.stream.Collectors; public class ShortcutService extends IShortcutService.Stub { static final String TAG = "ShortcutService"; - static final boolean DEBUG = Build.IS_DEBUGGABLE; // STOPSHIP if true + static final boolean DEBUG = false; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE; @@ -3206,11 +3206,6 @@ public class ShortcutService extends IShortcutService.Stub { public void pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List shortcutIds, int userId) { - if (DEBUG) { - Slog.v(TAG, "pinShortcuts: " + callingPackage + ", with userId=" + launcherUserId - + ", is trying to pin shortcuts from " + packageName - + " with userId=" + userId); - } // Calling permission must be checked by LauncherAppsImpl. Preconditions.checkStringNotEmpty(packageName, "packageName"); Objects.requireNonNull(shortcutIds, "shortcutIds"); @@ -3235,11 +3230,6 @@ public class ShortcutService extends IShortcutService.Stub { && !si.isDeclaredInManifest(), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO, callingPackage, launcherUserId, false); - } else { - if (DEBUG) { - Slog.w(TAG, "specified package " + packageName + ", with userId=" + userId - + ", doesn't exist."); - } } // Get list of shortcuts that will get unpinned. ArraySet oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId); @@ -5458,17 +5448,6 @@ public class ShortcutService extends IShortcutService.Stub { */ private List prepareChangedShortcuts(ArraySet changedIds, ArraySet newIds, List deletedList, final ShortcutPackage ps) { - if (DEBUG) { - Slog.v(TAG, "prepareChangedShortcuts: " - + " changedIds=" + (changedIds == null - ? "n/a" : changedIds.stream().collect(Collectors.joining(", ", "[", "]"))) - + " newIds=" + (newIds == null - ? "n/a" : newIds.stream().collect(Collectors.joining(", ", "[", "]"))) - + " deletedList=" + (deletedList == null - ? "n/a" : deletedList.stream().map(ShortcutInfo::getId).collect( - Collectors.joining(", ", "[", "]"))) - + " ps=" + (ps == null ? "n/a" : ps.getPackageName())); - } if (ps == null) { // This can happen when package restore is not finished yet. return null; diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index c75622cb9bbc484cf35c9f47e0e1a9052fba5cd6..21a6df203015c5fd9ec3c7f4badec5e660b113ac 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -207,6 +207,22 @@ { "name": "CtsUpdateOwnershipEnforcementTestCases" }, + { + "name": "CtsPackageInstallerCUJDeviceAdminTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, { "name": "CtsPackageInstallerCUJInstallationTestCases", "file_patterns": [ @@ -223,6 +239,22 @@ } ] }, + { + "name": "CtsPackageInstallerCUJMultiUsersTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, { "name": "CtsPackageInstallerCUJUninstallationTestCases", "file_patterns": [ diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a683a8c54849920ce39b3fca32bee261003d8ce3..8bab9de903ba4aacbc53a50d12358d8ebe5edc1c 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1090,6 +1090,19 @@ public class UserManagerService extends IUserManager.Stub { mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null; mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler); emulateSystemUserModeIfNeeded(); + initPropertyInvalidatedCaches(); + } + + /** + * This method is used to invalidate the caches at server statup, + * so that caches can start working. + */ + private static final void initPropertyInvalidatedCaches() { + if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) { + UserManager.invalidateIsUserUnlockedCache(); + UserManager.invalidateQuietModeEnabledCache(); + UserManager.invalidateUserSerialNumberCache(); + } } private boolean doesDeviceHardwareSupportPrivateSpace() { @@ -2632,11 +2645,15 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public int getMainDisplayIdAssignedToUser() { - // Not checking for any permission as it returns info about calling user - int userId = UserHandle.getUserId(Binder.getCallingUid()); - int displayId = mUserVisibilityMediator.getMainDisplayAssignedToUser(userId); - return displayId; + public int getMainDisplayIdAssignedToUser(int userId) { + final int callingUserId = UserHandle.getCallingUserId(); + if (callingUserId != userId + && !hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) { + throw new SecurityException("Caller from user " + callingUserId + " needs MANAGE_USERS " + + "or INTERACT_ACROSS_USERS permission to get the main display for (" + userId + + ")"); + } + return mUserVisibilityMediator.getMainDisplayAssignedToUser(userId); } @Override diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index 46207c1860c04454843055c93bbeb2ec6453987e..b43ddaa92562c4d1487f6391e84e7af7f5246cc7 100644 --- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java +++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java @@ -572,7 +572,7 @@ public final class UserVisibilityMediator implements Dumpable { return false; } - // First check if the user started on display + // First check if the user is assigned to a display int userAssignedToDisplay = getUserStartedOnDisplay(displayId); if (userAssignedToDisplay != USER_NULL) { Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because display was assigned" @@ -918,10 +918,16 @@ public final class UserVisibilityMediator implements Dumpable { if (!isStartedVisibleProfileLocked(userId)) { return userId; } else if (DBG) { - Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's " - + "a profile", displayId, userId); + Slogf.d(TAG, + "getUserAssignedToDisplay(%d): skipping user %d because it's a profile", + displayId, userId); } } + int userAssignedToExtraDisplay = mExtraDisplaysAssignedToUsers.get(displayId, + USER_NULL); + if (userAssignedToExtraDisplay != USER_NULL) { + return userAssignedToExtraDisplay; + } } if (!returnCurrentUserByDefault) { if (DBG) { diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING index 8a1982a339eae1d38383439df670e666fb208232..db98c402eeeb6b3e0a0b94b47a6dd2a4946895e7 100644 --- a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "PackageManagerServiceUnitTests", - "options": [ - { - "include-filter": "com.android.server.pm.test.verify.domain" - } - ] + "name": "PackageManagerServiceUnitTests_verify_domain" }, { "name": "CtsDomainVerificationDeviceStandaloneTestCases" diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ed9dcfadab83615a0b2b98da41386a9284314085..63491e8434bf49e198afff8ebb233a8ebed0e426 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -47,6 +47,7 @@ import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; import static android.view.KeyEvent.KEYCODE_HOME; import static android.view.KeyEvent.KEYCODE_POWER; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; +import static android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL; import static android.view.KeyEvent.KEYCODE_UNKNOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_UP; @@ -83,6 +84,7 @@ import static android.view.contentprotection.flags.Flags.createAccessibilityOver import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable; import static com.android.hardware.input.Flags.modifierShortcutDump; +import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser; import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY; @@ -809,7 +811,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.recycle(); break; case MSG_HANDLE_ALL_APPS: - launchAllAppsAction((KeyEvent) msg.obj); + launchAllAppsAction(); break; case MSG_RINGER_TOGGLE_CHORD: handleRingerChordGesture(); @@ -820,7 +822,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_SWITCH_KEYBOARD_LAYOUT: SwitchKeyboardLayoutMessageObject object = (SwitchKeyboardLayoutMessageObject) msg.obj; - handleSwitchKeyboardLayout(object.keyEvent, object.direction, + handleSwitchKeyboardLayout(object.displayId, object.direction, object.focusedToken); break; case MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE: @@ -937,7 +939,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private record SwitchKeyboardLayoutMessageObject(KeyEvent keyEvent, IBinder focusedToken, + private record SwitchKeyboardLayoutMessageObject(int displayId, IBinder focusedToken, int direction) { } @@ -1813,9 +1815,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Secure.TV_USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; } - private void handleShortPressOnHome(KeyEvent event) { - notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_HOME); - + private void handleShortPressOnHome(int displayId) { // Turn on the connected TV and switch HDMI input if we're a HDMI playback device. final HdmiControl hdmiControl = getHdmiControl(); if (hdmiControl != null) { @@ -1831,7 +1831,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Go home! - launchHomeFromHotKey(event.getDisplayId()); + launchHomeFromHotKey(displayId); } /** @@ -1878,11 +1878,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void launchAllAppsAction(KeyEvent event) { + private void launchAllAppsAction() { if (mHasFeatureLeanback || mHasFeatureWatch) { // TV and watch support the all apps intent - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); Intent intent = new Intent(Intent.ACTION_ALL_APPS); if (mHasFeatureLeanback) { Intent intentLauncher = new Intent(Intent.ACTION_MAIN); @@ -1896,8 +1894,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } startActivityAsUser(intent, UserHandle.CURRENT); } else { - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS); AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal(); if (accessibilityManager != null) { accessibilityManager.performSystemAction( @@ -1949,7 +1945,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void run() { if (mPendingHomeKeyEvent != null) { - handleShortPressOnHome(mPendingHomeKeyEvent); + notifyKeyGestureCompleted(mPendingHomeKeyEvent, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME); + handleShortPressOnHome(mPendingHomeKeyEvent.getDisplayId()); mPendingHomeKeyEvent = null; } } @@ -2002,7 +2000,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Post to main thread to avoid blocking input pipeline. - mHandler.post(() -> handleShortPressOnHome(event)); + mHandler.post(() -> { + notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_HOME); + handleShortPressOnHome(event.getDisplayId()); + }); return true; } @@ -2080,7 +2081,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Home - Long Press"); switch (mLongPressOnHomeBehavior) { case LONG_PRESS_HOME_ALL_APPS: - launchAllAppsAction(event); + notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); + launchAllAppsAction(); break; case LONG_PRESS_HOME_ASSIST: notifyKeyGestureCompleted(event, @@ -2430,6 +2432,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy(); initKeyCombinationRules(); initSingleKeyGestureRules(injector.getLooper()); + initKeyGestures(); mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker(); mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager); } @@ -2641,7 +2644,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onKeyUp(long eventTime, int count, int displayId) { + void onKeyUp(long eventTime, int count, int displayId, int deviceId, int metaState) { if (mShouldEarlyShortPressOnPower && count == 1) { powerPress(eventTime, 1 /*pressCount*/, displayId); } @@ -2761,7 +2764,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onKeyUp(long eventTime, int count, int unusedDisplayId) { + void onKeyUp(long eventTime, int count, int displayId, int deviceId, int metaState) { if (count == 1) { // Save info about the most recent task on the first press of the stem key. This // may be used later to switch to the most recent app using double press gesture. @@ -2814,6 +2817,33 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + // TODO(b/358569822): Move to KeyGestureController. + private final class StylusTailButtonRule extends SingleKeyGestureDetector.SingleKeyRule { + StylusTailButtonRule() { + super(KEYCODE_STYLUS_BUTTON_TAIL); + } + + @Override + int getMaxMultiPressCount() { + return 2; + } + + @Override + void onPress(long downTime, int displayId) { + + } + + @Override + void onKeyUp(long eventTime, int pressCount, int displayId, int deviceId, int metaState) { + if (pressCount != 1) { + return; + } + // Single press on tail button triggers the open notes gesture. + handleKeyGestureInKeyGestureController(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, + deviceId, KEYCODE_STYLUS_BUTTON_TAIL, metaState); + } + } + private void initSingleKeyGestureRules(Looper looper) { mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext, looper); mSingleKeyGestureDetector.addRule(new PowerKeyRule()); @@ -2823,6 +2853,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (hasStemPrimaryBehavior()) { mSingleKeyGestureDetector.addRule(new StemPrimaryKeyRule()); } + mSingleKeyGestureDetector.addRule(new StylusTailButtonRule()); } /** @@ -3312,6 +3343,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { new int[]{event.getKeyCode()}, event.getMetaState(), gestureType); } + private void handleKeyGestureInKeyGestureController( + @KeyGestureEvent.KeyGestureType int gestureType, int deviceId, int keyCode, + int metaState) { + if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { + return; + } + mInputManagerInternal.handleKeyGestureInKeyGestureController(deviceId, new int[]{keyCode}, + metaState, gestureType); + } + @Override public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId); @@ -3354,12 +3395,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mConsumedKeysForDevice.put(deviceId, consumedKeys); } - // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts - if ((event.isMetaPressed() || KeyEvent.isMetaKey(keyCode)) - && shouldInterceptShortcuts(focusedToken)) { - return keyNotConsumed; - } - if (interceptSystemKeysAndShortcuts(focusedToken, event) && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { consumedKeys.add(keyCode); @@ -3388,6 +3423,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // conflicting events to application, make sure to consume the event on // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential // to maintain event parity and to not have incomplete key gestures. + // + // NOTE: Please try not to add new Shortcut combinations here and instead use KeyGestureEvents. + // Add shortcut trigger logic in {@code KeyGestureController} and add handling logic in + // {@link handleKeyGesture()} @SuppressLint("MissingPermission") private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) { final boolean keyguardOn = keyguardOn(); @@ -3516,6 +3555,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { injectBackGesture(event.getDownTime()); return true; } + break; case KeyEvent.KEYCODE_DPAD_UP: if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); @@ -3544,11 +3584,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), true /* leftOrTop */); notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION); + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT); } else if (event.isAltPressed()) { setSplitscreenFocus(true /* leftOrTop */); notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS); + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT); } else { notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_BACK); @@ -3563,12 +3603,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), false /* leftOrTop */); notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION); + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT); return true; } else if (event.isAltPressed()) { setSplitscreenFocus(false /* leftOrTop */); notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS); + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT); return true; } } @@ -3593,30 +3633,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (down) { int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1; - int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId; - - float minLinearBrightness = mPowerManager.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - float maxLinearBrightness = mPowerManager.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); - float linearBrightness = mDisplayManager.getBrightness(screenDisplayId); - - float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness); - float adjustedGammaBrightness = - gammaBrightness + 1f / BRIGHTNESS_STEPS * direction; - adjustedGammaBrightness = MathUtils.constrain(adjustedGammaBrightness, 0f, - 1f); - float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear( - adjustedGammaBrightness); - adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, - minLinearBrightness, maxLinearBrightness); - mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness); - - Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION - | Intent.FLAG_ACTIVITY_NO_USER_ACTION); - intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true); - startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + changeDisplayBrightnessValue(displayId, direction); int gestureType = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN @@ -3689,10 +3706,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_ALL_APPS: if (firstDown) { mHandler.removeMessages(MSG_HANDLE_ALL_APPS); - Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS, new KeyEvent(event)); msg.setAsynchronous(true); msg.sendToTarget(); + + notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); } return true; case KeyEvent.KEYCODE_NOTIFICATION: @@ -3720,7 +3738,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_LANGUAGE_SWITCH: if (firstDown) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - sendSwitchKeyboardLayout(event, focusedToken, direction); + sendSwitchKeyboardLayout(displayId, focusedToken, direction); notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH); return true; @@ -3745,7 +3763,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK); } else if (mPendingMetaAction) { if (!canceled) { - launchAllAppsAction(event); + launchAllAppsAction(); + notifyKeyGestureCompleted(event, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS); } mPendingMetaAction = false; } @@ -3829,13 +3849,229 @@ public class PhoneWindowManager implements WindowManagerPolicy { return (metaState & KeyEvent.META_META_ON) != 0; } - private boolean shouldInterceptShortcuts(IBinder focusedToken) { - KeyInterceptionInfo info = - mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken); - boolean hasInterceptWindowFlag = (info.layoutParamsPrivateFlags - & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0; - return hasInterceptWindowFlag && mButtonOverridePermissionChecker.canAppOverrideSystemKey( - mContext, info.windowOwnerUid); + @SuppressLint("MissingPermission") + private void initKeyGestures() { + if (!useKeyGestureEventHandler()) { + return; + } + mInputManager.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() { + @Override + public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, + @Nullable IBinder focusedToken) { + return PhoneWindowManager.this.handleKeyGestureEvent(event, focusedToken); + } + + @Override + public boolean isKeyGestureSupported(int gestureType) { + switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS: + case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH: + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: + case KeyGestureEvent.KEY_GESTURE_TYPE_HOME: + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS: + case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN: + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL: + case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT: + case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: + case KeyGestureEvent.KEY_GESTURE_TYPE_BACK: + case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: + case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE: + case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: + case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: + case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: + case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: + case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: + case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP: + case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: + case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: + case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS: + case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS: + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH: + case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH: + return true; + default: + return false; + } + } + }); + } + + @VisibleForTesting + boolean handleKeyGestureEvent(KeyGestureEvent event, IBinder focusedToken) { + boolean start = event.getAction() == KeyGestureEvent.ACTION_GESTURE_START; + boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE + && !event.isCancelled(); + int deviceId = event.getDeviceId(); + int gestureType = event.getKeyGestureType(); + int displayId = event.getDisplayId(); + int modifierState = event.getModifierState(); + boolean keyguardOn = keyguardOn(); + switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS: + if (complete) { + showRecentApps(false); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH: + if (!keyguardOn) { + if (start) { + preloadRecentApps(); + } else if (complete) { + toggleRecentApps(); + } + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: + if (complete) { + launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, + deviceId, SystemClock.uptimeMillis(), + AssistUtils.INVOCATION_TYPE_UNKNOWN); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_HOME: + if (complete) { + // Post to main thread to avoid blocking input pipeline. + mHandler.post(() -> handleShortPressOnHome(displayId)); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS: + if (complete) { + showSystemSettings(); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN: + if (complete) { + lockNow(null /* options */); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL: + if (complete) { + toggleNotificationPanel(); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT: + if (complete) { + interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: + if (complete && mEnableBugReportKeyboardShortcut) { + try { + mActivityManagerService.requestInteractiveBugReport(); + } catch (RemoteException e) { + Slog.d(TAG, "Error taking bugreport", e); + } + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_BACK: + if (complete) { + injectBackGesture(SystemClock.uptimeMillis()); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: + if (complete) { + StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); + if (statusbar != null) { + statusbar.moveFocusedTaskToFullscreen( + getTargetDisplayIdForKeyGestureEvent(event)); + } + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE: + if (complete) { + StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); + if (statusbar != null) { + statusbar.moveFocusedTaskToDesktop( + getTargetDisplayIdForKeyGestureEvent(event)); + } + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: + if (complete) { + moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event), + true /* leftOrTop */); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: + if (complete) { + setSplitscreenFocus(true /* leftOrTop */); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: + if (complete) { + moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event), + false /* leftOrTop */); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: + if (complete) { + setSplitscreenFocus(false /* leftOrTop */); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: + if (complete) { + toggleKeyboardShortcutsMenu(deviceId); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP: + case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: + if (complete) { + int direction = + gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP ? 1 : -1; + changeDisplayBrightnessValue(displayId, direction); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: + if (start) { + showRecentApps(true); + } else { + hideRecentApps(true, false); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS: + case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS: + if (complete) { + launchAllAppsAction(); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH: + if (complete) { + launchTargetSearchActivity(); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH: + if (complete) { + int direction = (modifierState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; + sendSwitchKeyboardLayout(displayId, focusedToken, direction); + } + return true; + } + return false; + } + + private void changeDisplayBrightnessValue(int displayId, int direction) { + int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId; + + float minLinearBrightness = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); + float maxLinearBrightness = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); + float linearBrightness = mDisplayManager.getBrightness(screenDisplayId); + + float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness); + float adjustedGammaBrightness = gammaBrightness + 1f / BRIGHTNESS_STEPS * direction; + adjustedGammaBrightness = MathUtils.constrain(adjustedGammaBrightness, 0f, 1f); + float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear( + adjustedGammaBrightness); + adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, + minLinearBrightness, maxLinearBrightness); + mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness); + + Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION + | Intent.FLAG_ACTIVITY_NO_USER_ACTION); + intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); } /** @@ -4081,7 +4317,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - sendSwitchKeyboardLayout(event, focusedToken, direction); + sendSwitchKeyboardLayout(event.getDisplayId(), focusedToken, direction); return true; } } @@ -4139,19 +4375,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, - @Nullable IBinder focusedToken, int direction) { + private void sendSwitchKeyboardLayout(int displayId, @Nullable IBinder focusedToken, + int direction) { SwitchKeyboardLayoutMessageObject object = - new SwitchKeyboardLayoutMessageObject(event, focusedToken, direction); + new SwitchKeyboardLayoutMessageObject(displayId, focusedToken, direction); mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, object).sendToTarget(); } - private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction, - IBinder focusedToken) { + private void handleSwitchKeyboardLayout(int displayId, int direction, IBinder focusedToken) { IBinder targetWindowToken = mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken); - InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction, - event.getDisplayId(), targetWindowToken); + InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction, displayId, + targetWindowToken); } private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent, @@ -7000,16 +7235,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private int getTargetDisplayIdForKeyEvent(KeyEvent event) { - int displayId = event.getDisplayId(); - - if (displayId == INVALID_DISPLAY) { - displayId = mTopFocusedDisplayId; + if (event.getDisplayId() != INVALID_DISPLAY) { + return event.getDisplayId(); + } + if (mTopFocusedDisplayId != INVALID_DISPLAY) { + return mTopFocusedDisplayId; } + return DEFAULT_DISPLAY; + } - if (displayId == INVALID_DISPLAY) { - return DEFAULT_DISPLAY; - } else { - return displayId; + private int getTargetDisplayIdForKeyGestureEvent(KeyGestureEvent event) { + if (event.getDisplayId() != INVALID_DISPLAY) { + return event.getDisplayId(); + } + if (mTopFocusedDisplayId != INVALID_DISPLAY) { + return mTopFocusedDisplayId; } + return DEFAULT_DISPLAY; } } diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index a060f504b8095e737fa52581a17c3933d3e9711a..441d3eaf2348e17aef6125aa66772758067e2296 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -105,9 +105,9 @@ public final class SingleKeyGestureDetector { /** * Maximum count of multi presses. - * Return 1 will trigger onPress immediately when {@link KeyEvent.ACTION_UP}. + * Return 1 will trigger onPress immediately when {@link KeyEvent#ACTION_UP}. * Otherwise trigger onMultiPress immediately when reach max count when - * {@link KeyEvent.ACTION_DOWN}. + * {@link KeyEvent#ACTION_DOWN}. */ int getMaxMultiPressCount() { return 1; @@ -153,8 +153,10 @@ public final class SingleKeyGestureDetector { * @param eventTime the timestamp of this event * @param pressCount the number of presses detected leading up to this key up event * @param displayId the display ID of the event + * @param deviceId the ID of the input device that generated this event + * @param metaState the state of the modifiers when this gesture was detected */ - void onKeyUp(long eventTime, int pressCount, int displayId) {} + void onKeyUp(long eventTime, int pressCount, int displayId, int deviceId, int metaState) {} @Override public String toString() { @@ -183,7 +185,11 @@ public final class SingleKeyGestureDetector { } private record MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, - int displayId) { + int displayId, int metaState, int deviceId) { + MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, KeyEvent event) { + this(activeRule, keyCode, pressCount, event.getDisplayId(), event.getMetaState(), + event.getDeviceId()); + } } static SingleKeyGestureDetector get(Context context, Looper looper) { @@ -236,7 +242,7 @@ public final class SingleKeyGestureDetector { mHandler.removeMessages(MSG_KEY_LONG_PRESS); mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); MessageObject object = new MessageObject(mActiveRule, keyCode, /* pressCount= */ 1, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); @@ -284,7 +290,7 @@ public final class SingleKeyGestureDetector { if (mKeyPressCounter == 1) { if (mActiveRule.supportLongPress()) { MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs()); @@ -292,7 +298,7 @@ public final class SingleKeyGestureDetector { if (mActiveRule.supportVeryLongPress()) { MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs()); @@ -310,7 +316,7 @@ public final class SingleKeyGestureDetector { + " reached the max count " + mKeyPressCounter); } MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); @@ -351,7 +357,7 @@ public final class SingleKeyGestureDetector { if (event.getKeyCode() == mActiveRule.mKeyCode) { // key-up action should always be triggered if not processed by long press. MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, - mKeyPressCounter, event.getDisplayId()); + mKeyPressCounter, event); Message msgKeyUp = mHandler.obtainMessage(MSG_KEY_UP, object); msgKeyUp.setAsynchronous(true); mHandler.sendMessage(msgKeyUp); @@ -362,7 +368,7 @@ public final class SingleKeyGestureDetector { Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode())); } object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, - /* pressCount= */ 1, event.getDisplayId()); + /* pressCount= */ 1, event); Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); @@ -373,7 +379,7 @@ public final class SingleKeyGestureDetector { // This could be a multi-press. Wait a little bit longer to confirm. if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) { object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, - mKeyPressCounter, event.getDisplayId()); + mKeyPressCounter, event); Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); @@ -452,7 +458,8 @@ public final class SingleKeyGestureDetector { Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode) + " on display " + displayId); } - rule.onKeyUp(mLastDownTime, pressCount, displayId); + rule.onKeyUp(mLastDownTime, pressCount, displayId, object.deviceId, + object.metaState); break; case MSG_KEY_LONG_PRESS: if (DEBUG) { diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING index bdb174d98137a397cb1880f9ec4ff6da02bb531c..76a0503503939048412c6b7d64df0b08cf80ba6f 100644 --- a/services/core/java/com/android/server/policy/TEST_MAPPING +++ b/services/core/java/com/android/server/policy/TEST_MAPPING @@ -1,32 +1,10 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.policy." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksServicesTests_android_server_policy_Presubmit" }, { - "name": "WmTests", - "options": [ - { - "include-filter": "com.android.server.policy." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "WmTests_server_policy_Presubmit" }, { "name": "CtsPermissionPolicyTestCases", @@ -49,30 +27,15 @@ "name": "CtsPermissionTestCases_Platform" }, { - "name": "CtsBackupTestCases", - "options": [ - { - "include-filter": "android.backup.cts.PermissionTest" - } - ] + "name": "CtsBackupTestCases_cts_permissiontest" } ], "postsubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.policy." - } - ] + "name": "FrameworksServicesTests_android_server_policy" }, { - "name": "WmTests", - "options": [ - { - "include-filter": "com.android.server.policy." - } - ] + "name": "WmTests_server_policy" }, { "name": "CtsPermissionPolicyTestCases", diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java index 20184e9fd1a7d8f0051c3135fe35b2d6343efbc0..f69a017fc45a7e579d91fb339cb6eb210915a97b 100644 --- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java +++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java @@ -16,13 +16,19 @@ package com.android.server.power; +import android.app.AlarmManager; +import android.app.IAlarmCompleteListener; +import android.app.IAlarmListener; +import android.app.IAlarmManager; import android.content.Context; import android.content.Intent; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.PowerManagerInternal; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.ShellCommand; +import android.os.SystemClock; import android.util.SparseArray; import android.view.Display; @@ -34,12 +40,26 @@ class PowerManagerShellCommand extends ShellCommand { private final Context mContext; private final PowerManagerService.BinderService mService; + private final IAlarmListener mAlarmListener; + private IAlarmManager mAlarmManager; private SparseArray mProxWakelocks = new SparseArray<>(); PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) { mContext = context; mService = service; + mAlarmManager = + IAlarmManager.Stub.asInterface(ServiceManager.getService(Context.ALARM_SERVICE)); + mAlarmListener = new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + mService.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "PowerManagerShellCommand", + mContext.getOpPackageName()); + } + }; } @Override @@ -65,6 +85,10 @@ class PowerManagerShellCommand extends ShellCommand { return runSetProx(); case "set-face-down-detector": return runSetFaceDownDetector(); + case "sleep": + return runSleep(); + case "wakeup": + return runWakeUp(); default: return handleDefaultCommands(cmd); } @@ -194,6 +218,70 @@ class PowerManagerShellCommand extends ShellCommand { return 0; } + private int runSleep() { + try { + mService.goToSleep( + SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_APPLICATION, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + } catch (Exception e) { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Error: " + e); + return -1; + } + return 0; + } + + private int runWakeUp() { + final PrintWriter pw = getOutPrintWriter(); + String delay = getNextArg(); + if (delay == null) { + try { + mService.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "PowerManagerShellCommand", + mContext.getOpPackageName()); + } catch (Exception e) { + pw.println("Error: " + e); + return -1; + } + } else { + long delayMillis; + try { + delayMillis = Long.parseLong(delay); + } catch (NumberFormatException e) { + pw.println("Error: Can't parse arg " + delay + " as a long: " + e); + return -1; + } + if (delayMillis < 0) { + pw.println("Error: Can't set a negative delay: " + delayMillis); + return -1; + } + long wakeUpTime = System.currentTimeMillis() + delayMillis; + if (mAlarmManager == null) { + // PowerManagerShellCommand may be initialized before AlarmManagerService + // is brought up. Make sure mAlarmManager exists. + mAlarmManager = IAlarmManager.Stub.asInterface( + ServiceManager.getService(Context.ALARM_SERVICE)); + } + try { + // This command is called by the shell, which has "com.android.shell" as package + // name. + pw.println("Schedule an alarm to wakeup in " + + delayMillis + " ms, on behalf of shell."); + mAlarmManager.set("com.android.shell", + AlarmManager.RTC_WAKEUP, wakeUpTime, + 0, 0, AlarmManager.FLAG_PRIORITIZE, + null, mAlarmListener, "PowerManagerShellCommand", null, null); + } catch (Exception e) { + pw.println("Error: " + e); + return -1; + } + } + return 0; + } + @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); @@ -221,6 +309,11 @@ class PowerManagerShellCommand extends ShellCommand { pw.println(" created by set-prox including their held status."); pw.println(" set-face-down-detector [true|false]"); pw.println(" sets whether we use face down detector timeouts or not"); + pw.println(" sleep"); + pw.println(" requests to sleep the device"); + pw.println(" wakeup "); + pw.println(" requests to wake up the device. If a delay of milliseconds is specified,"); + pw.println(" alarm manager will schedule a wake up after the delay."); pw.println(); Intent.printIntentArgsHelp(pw , ""); diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index 935a238bcee745ecaaf0aefe91c14f9c4789f484..f67f56db3c1e0ac97193337408b5d0dbf20c26ee 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -1,22 +1,13 @@ { "presubmit": [ { - "name": "CtsBatterySavingTestCases", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"} - ] + "name": "CtsBatterySavingTestCases_android_server_power" }, { "name": "FrameworksMockingServicesTests_android_server_power_Presubmit" }, { - "name": "PowerServiceTests", - "options": [ - {"include-filter": "com.android.server.power"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "PowerServiceTests_server_power" } ], "postsubmit": [ @@ -24,28 +15,16 @@ "name": "CtsBatterySavingTestCases" }, { - "name": "FrameworksMockingServicesTests", - "options": [ - {"include-filter": "com.android.server.power"} - ] + "name": "FrameworksMockingServicesTests_android_server_power" }, { "name": "FrameworksServicesTests_android_server_power" }, { - "name": "PowerServiceTests", - "options": [ - {"include-filter": "com.android.server.power"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "PowerServiceTests_server_power" }, { - "name": "CtsStatsdAtomHostTestCases", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"}, - {"include-filter": "android.cts.statsdatom.powermanager"} - ], + "name": "CtsStatsdAtomHostTestCases_statsdatom_powermanager", "file_patterns": [ "(/|^)ThermalManagerService.java" ] diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 68026ea9094acae8ab7a33b88c6617f6fcb3dd4c..e3d71e4998bede88890eb5d6a555727b07d44fa3 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -24,8 +24,11 @@ import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -78,6 +81,8 @@ import java.util.function.Consumer; public final class RollbackPackageHealthObserver implements PackageHealthObserver { private static final String TAG = "RollbackPackageHealthObserver"; private static final String NAME = "rollback-observer"; + private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName(); + private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -596,12 +601,40 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } }; - final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> { - mHandler.post(() -> onResult.accept(result)); - }); + if (Flags.refactorCrashrecovery()) { + // Define a BroadcastReceiver to handle the result + BroadcastReceiver rollbackReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent result) { + mHandler.post(() -> onResult.accept(result)); + } + }; + + // Register the BroadcastReceiver + mContext.registerReceiver(rollbackReceiver, + new IntentFilter(ACTION_NAME), + Context.RECEIVER_NOT_EXPORTED); + + Intent intentReceiver = new Intent(ACTION_NAME); + intentReceiver.putExtra("rollbackId", rollback.getRollbackId()); + intentReceiver.setPackage(mContext.getPackageName()); - rollbackManager.commitRollback(rollback.getRollbackId(), - Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender()); + PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext, + rollback.getRollbackId(), + intentReceiver, + PendingIntent.FLAG_MUTABLE); + + rollbackManager.commitRollback(rollback.getRollbackId(), + Collections.singletonList(failedPackage), + rollbackPendingIntent.getIntentSender()); + } else { + final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver(result -> { + mHandler.post(() -> onResult.accept(result)); + }); + + rollbackManager.commitRollback(rollback.getRollbackId(), + Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender()); + } } /** diff --git a/services/core/java/com/android/server/security/TEST_MAPPING b/services/core/java/com/android/server/security/TEST_MAPPING index 29d52fff3effb55af40167fd3e09b4dfaa213620..284e08e1e526ae0eae76fffbdb8ee46516da5238 100644 --- a/services/core/java/com/android/server/security/TEST_MAPPING +++ b/services/core/java/com/android/server/security/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsSecurityTestCases", - "options": [ - { - "include-filter": "android.security.cts.FileIntegrityManagerTest" - } - ], + "name": "CtsSecurityTestCases_cts_fileintegritymanagertest", "file_patterns": ["FileIntegrity[^/]*\\.java"] } ] diff --git a/services/core/java/com/android/server/statusbar/TEST_MAPPING b/services/core/java/com/android/server/statusbar/TEST_MAPPING index 67ea557d7806d79037909e7d900db1dfd48926b4..8c7e74c7e2c51f149b5319dcd6ff40ab00cd6d4c 100644 --- a/services/core/java/com/android/server/statusbar/TEST_MAPPING +++ b/services/core/java/com/android/server/statusbar/TEST_MAPPING @@ -1,29 +1,10 @@ { "presubmit": [ { - "name": "CtsTileServiceTestCases", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTileServiceTestCases" }, { - "name": "CtsAppTestCases", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "include-filter": "android.app.cts.RequestTileServiceAddTest" - } - ] + "name": "CtsAppTestCases_cts_requesttileserviceaddtest" } ] } \ No newline at end of file diff --git a/services/core/java/com/android/server/timedetector/TEST_MAPPING b/services/core/java/com/android/server/timedetector/TEST_MAPPING index 17d327e94d4d65c1c652702f45cac64304935ebd..f57b819e241fe32c060bbcf59b1d300e857675e0 100644 --- a/services/core/java/com/android/server/timedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timedetector/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsTimeTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTimeTestCases" }, { "name": "FrameworksTimeServicesTests" diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING index 004d7996435418a3413f160bc9bc2c5436ee2e5a..a237c346a637960b172e8f676b24e6d19668ee25 100644 --- a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsTimeTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTimeTestCases" }, { "name": "FrameworksTimeServicesTests" diff --git a/services/core/java/com/android/server/trust/TEST_MAPPING b/services/core/java/com/android/server/trust/TEST_MAPPING index 0de7c28c209bf7f904c38b4e682cfed360cad25b..4c08455f713a2e3ac2d302b67d318b2542e77d8a 100644 --- a/services/core/java/com/android/server/trust/TEST_MAPPING +++ b/services/core/java/com/android/server/trust/TEST_MAPPING @@ -1,41 +1,17 @@ { "presubmit": [ { - "name": "TrustTests", - "options": [ - { - "include-filter": "android.trust.test" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TrustTests_trust_test" } ], "postsubmit": [ { - "name": "FrameworksMockingServicesTests", - "options": [ - { - "include-filter": "com.android.server.trust" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksMockingServicesTests_android_server_trust" } ], "trust-tablet": [ { - "name": "TrustTests", - "options": [ - { - "include-filter": "android.trust.test" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TrustTests_trust_test" } ] } \ No newline at end of file diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 953aae9588dd5a542db4b135b0630fe7d8b01065..457196b74d2e1b7bbae9016a57f384d2426f731e 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -89,6 +89,7 @@ import com.android.internal.widget.LockSettingsInternal; import com.android.internal.widget.LockSettingsStateListener; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.UserManagerInternal; import com.android.server.servicewatcher.CurrentUserServiceSupplier; import com.android.server.servicewatcher.ServiceWatcher; import com.android.server.utils.Slogf; @@ -170,6 +171,7 @@ public class TrustManagerService extends SystemService { private final ActivityManager mActivityManager; private FingerprintManager mFingerprintManager; private FaceManager mFaceManager; + private UserManagerInternal mUserManagerInternal; private enum TrustState { // UNTRUSTED means that TrustManagerService is currently *not* giving permission for the @@ -1064,6 +1066,8 @@ public class TrustManagerService extends SystemService { Log.w(TAG, "Unable to check keyguard lock state", e); } currentUserIsUnlocked = unlockedUser == id; + } else if (isVisibleBackgroundUser(id)) { + showingKeyguard = !mUserManager.isUserUnlocked(id); } final boolean deviceLocked = secure && showingKeyguard && !trusted && !biometricAuthenticated; @@ -1095,6 +1099,16 @@ public class TrustManagerService extends SystemService { } } + private boolean isVisibleBackgroundUser(int userId) { + if (!mUserManager.isVisibleBackgroundUsersSupported()) { + return false; + } + if (mUserManagerInternal == null) { + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + } + return mUserManagerInternal.isVisibleBackgroundFullUser(userId); + } + private void notifyTrustAgentsOfDeviceLockState(int userId, boolean isLocked) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo agent = mActiveAgents.valueAt(i); diff --git a/services/core/java/com/android/server/uri/TEST_MAPPING b/services/core/java/com/android/server/uri/TEST_MAPPING index 0d756bb984d17ed2b0ef89d7ac95886fe35d8081..45e3051d4d5d9601fc8c16619d3dcee76b04d975 100644 --- a/services/core/java/com/android/server/uri/TEST_MAPPING +++ b/services/core/java/com/android/server/uri/TEST_MAPPING @@ -4,24 +4,7 @@ "name": "FrameworksServicesTests_android_server_uri" }, { - "name": "CtsStorageHostTestCases", - "options": [ - { - "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testGrantUriPermission" - }, - { - "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testGrantUriPermission29" - }, - { - "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testMediaNone" - }, - { - "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testMediaNone28" - }, - { - "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testMediaNone29" - } - ] + "name": "CtsStorageHostTestCases_android_server_uri" } ], "postsubmit": [ @@ -34,12 +17,7 @@ ] }, { - "name": "CtsWindowManagerDeviceWindow", - "options": [ - { - "include-filter": "android.server.wm.window.CrossAppDragAndDropTests" - } - ] + "name": "CtsWindowManagerDeviceWindow_window_crossappdraganddroptests" } ] } diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 96a25dac21e3b9f6e1ae17782db12a49e33daec9..1e82b89998346634b8ceca09fa1faa423aec4507 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -322,9 +322,16 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { if (SubscriptionManager.isValidSubscriptionId(subId)) { // Get only configs as needed to save memory. - final PersistableBundle carrierConfig = - CarrierConfigManager.getCarrierConfigSubset(mContext, subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + PersistableBundle carrierConfig = new PersistableBundle(); + try { + carrierConfig = + mCarrierConfigManager.getConfigForSubId( + subId, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + + } catch (RuntimeException exception) { + Slog.w(TAG, "CarrierConfigLoader is not available."); + } + if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) { mReadySubIdsBySlotId.put(slotId, subId); diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 8d378a021f1751392612bbd62cfcf0bd4b2e2928..b5747828349e7e16a0e54fcc981b9c68c5a5ce3f 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -177,6 +177,10 @@ public class VcnGatewayConnection extends StateMachine { /** Default number of parallel SAs requested */ static final int TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT = 1; + // The returned string of + // TelephonyManager#getNetworkTypeName(TelephonyManager.NETWORK_TYPE_UNKNOWN) + private static final String NETWORK_TYPE_STRING_UNKNOWN = "UNKNOWN"; + // Matches DataConnection.NETWORK_TYPE private constant, and magic string from // ConnectivityManager#getNetworkTypeName() @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -1815,9 +1819,7 @@ public class VcnGatewayConnection extends StateMachine { .setLegacyType(ConnectivityManager.TYPE_MOBILE) .setLegacyTypeName(NETWORK_INFO_NETWORK_TYPE_STRING) .setLegacySubType(TelephonyManager.NETWORK_TYPE_UNKNOWN) - .setLegacySubTypeName( - TelephonyManager.getNetworkTypeName( - TelephonyManager.NETWORK_TYPE_UNKNOWN)) + .setLegacySubTypeName(NETWORK_TYPE_STRING_UNKNOWN) .setLegacyExtraInfo(NETWORK_INFO_EXTRA_INFO) .build(); diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java index 3f8d39e72e89e790f7b02c24f96fb16138522fda..2b0ca0802359277fb4292ef0c898c4451b6c6c63 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -360,7 +360,10 @@ public class UnderlyingNetworkController { final NetworkRequest.Builder nrBuilder = getBaseNetworkRequestBuilder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)); + .setNetworkSpecifier( + new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(subId) + .build()); for (CapabilityMatchCriteria capMatchCriteria : capsMatchCriteria) { final int cap = capMatchCriteria.capability; diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index cae6b34d4c732d38a9bdc6c63483002f4786fd0c..006a5bb1d1d09970ca867f075f1b735313473f2e 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -16,6 +16,8 @@ package com.android.server.vibrator; +import static android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; @@ -121,7 +123,6 @@ public final class HapticFeedbackVibrationProvider { return getVibrationForHapticFeedback(effectId); } - // TODO(b/354049335): handle input source customized VibrationAttributes. /** * Provides the {@link VibrationAttributes} that should be used for a haptic feedback. * @@ -131,7 +132,7 @@ public final class HapticFeedbackVibrationProvider { * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}. * @return the {@link VibrationAttributes} that should be used for the provided haptic feedback. */ - public VibrationAttributes getVibrationAttributesForHapticFeedback(int effectId, + public VibrationAttributes getVibrationAttributes(int effectId, @HapticFeedbackConstants.Flags int flags, @HapticFeedbackConstants.PrivateFlags int privFlags) { VibrationAttributes attrs; @@ -142,10 +143,13 @@ public final class HapticFeedbackVibrationProvider { break; case HapticFeedbackConstants.ASSISTANT_BUTTON: case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: + attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; + break; case HapticFeedbackConstants.SCROLL_TICK: case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: case HapticFeedbackConstants.SCROLL_LIMIT: - attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; + attrs = hapticFeedbackInputSourceCustomizationEnabled() ? TOUCH_VIBRATION_ATTRIBUTES + : HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; break; case HapticFeedbackConstants.KEYBOARD_TAP: case HapticFeedbackConstants.KEYBOARD_RELEASE: @@ -158,19 +162,32 @@ public final class HapticFeedbackVibrationProvider { default: attrs = TOUCH_VIBRATION_ATTRIBUTES; } + return getVibrationAttributesWithFlags(attrs, effectId, flags); + } - int vibFlags = 0; - boolean bypassVibrationIntensitySetting = - (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0; - if (bypassVibrationIntensitySetting) { - vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; - } - if (shouldBypassInterruptionPolicy(effectId)) { - vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; + /** + * Similar to {@link #getVibrationAttributes(int, int, int)} but also handles + * input source customization. + * + * @param inputSource the {@link InputDevice.Source} that customizes the + * {@link VibrationAttributes}. + */ + public VibrationAttributes getVibrationAttributes(int effectId, + int inputSource, + @HapticFeedbackConstants.Flags int flags, + @HapticFeedbackConstants.PrivateFlags int privFlags) { + if (hapticFeedbackInputSourceCustomizationEnabled() + && inputSource == InputDevice.SOURCE_ROTARY_ENCODER) { + switch (effectId) { + case HapticFeedbackConstants.SCROLL_TICK, + HapticFeedbackConstants.SCROLL_ITEM_FOCUS, + HapticFeedbackConstants.SCROLL_LIMIT -> { + return getVibrationAttributesWithFlags(HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES, + effectId, flags); + } + } } - - return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs) - .setFlags(vibFlags).build(); + return getVibrationAttributes(effectId, flags, privFlags); } /** @@ -344,6 +361,20 @@ public final class HapticFeedbackVibrationProvider { return IME_FEEDBACK_VIBRATION_ATTRIBUTES; } + private VibrationAttributes getVibrationAttributesWithFlags(VibrationAttributes attrs, + int effectId, int flags) { + int vibFlags = 0; + if ((flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0) { + vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + } + if (shouldBypassInterruptionPolicy(effectId)) { + vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; + } + + return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs) + .setFlags(vibFlags).build(); + } + private static boolean shouldBypassInterruptionPolicy(int effectId) { switch (effectId) { case HapticFeedbackConstants.SCROLL_TICK: diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 899f0b121a8dbaccab57ef34053b397122c77a86..a76d8d6bded04442b2dbbe084683fd631847f422 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -509,7 +509,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token, hapticVibrationProvider.getVibration(constant), - hapticVibrationProvider.getVibrationAttributesForHapticFeedback( + hapticVibrationProvider.getVibrationAttributes( constant, flags, privFlags)); } @@ -534,8 +534,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token, hapticVibrationProvider.getVibration(constant, inputSource), - hapticVibrationProvider.getVibrationAttributesForHapticFeedback( - constant, flags, privFlags)); + hapticVibrationProvider.getVibrationAttributes(constant, inputSource, flags, + privFlags)); } private HalVibration performHapticFeedbackWithEffect(int uid, int deviceId, String opPkg, diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java index b792f7909fc80a8ced475cbca7bd228073ee652e..15f86e9c08ffd39736592288384e849c7c9d880a 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperData.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java @@ -78,10 +78,13 @@ class WallpaperData { /** * The component name of the currently set live wallpaper. */ - ComponentName wallpaperComponent; + private ComponentName mWallpaperComponent; + // TODO(b/347235611) Remove this field /** * The component name of the wallpaper that should be set next. + * + * @deprecated */ ComponentName nextWallpaperComponent; @@ -192,7 +195,7 @@ class WallpaperData { */ WallpaperData(WallpaperData source) { this.userId = source.userId; - this.wallpaperComponent = source.wallpaperComponent; + this.mWallpaperComponent = source.mWallpaperComponent; this.mWhich = source.mWhich; this.wallpaperId = source.wallpaperId; this.cropHint.set(source.cropHint); @@ -227,6 +230,14 @@ class WallpaperData { return result; } + ComponentName getComponent() { + return mWallpaperComponent; + } + + void setComponent(ComponentName componentName) { + this.mWallpaperComponent = componentName; + } + @Override public String toString() { StringBuilder out = new StringBuilder(defaultString(this)); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index 4aefb54889aaba986387eccc8146ea18205cb778..74ca230386666fcedb84e66bac2bab0e833c1088 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -16,6 +16,7 @@ package com.android.server.wallpaper; +import static android.app.Flags.removeNextWallpaperComponent; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; @@ -170,48 +171,9 @@ public class WallpaperDataParser { stream = new FileInputStream(file); TypedXmlPullParser parser = Xml.resolvePullParser(stream); - int type; - do { - type = parser.next(); - if (type == XmlPullParser.START_TAG) { - String tag = parser.getName(); - if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) { - if ("kwp".equals(tag)) { - lockWallpaper = new WallpaperData(userId, FLAG_LOCK); - } - WallpaperData wallpaperToParse = - "wp".equals(tag) ? wallpaper : lockWallpaper; - - if (!multiCrop()) { - parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); - } + lockWallpaper = loadSettingsFromSerializer(parser, wallpaper, userId, loadSystem, + loadLock, keepDimensionHints, wpdData); - String comp = parser.getAttributeValue(null, "component"); - wallpaperToParse.nextWallpaperComponent = comp != null - ? ComponentName.unflattenFromString(comp) - : null; - if (wallpaperToParse.nextWallpaperComponent == null - || "android".equals(wallpaperToParse.nextWallpaperComponent - .getPackageName())) { - wallpaperToParse.nextWallpaperComponent = mImageWallpaper; - } - - if (multiCrop()) { - parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); - } - - if (DEBUG) { - Slog.v(TAG, "mWidth:" + wpdData.mWidth); - Slog.v(TAG, "mHeight:" + wpdData.mHeight); - Slog.v(TAG, "cropRect:" + wallpaper.cropHint); - Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); - Slog.v(TAG, "mName:" + wallpaper.name); - Slog.v(TAG, "mNextWallpaperComponent:" - + wallpaper.nextWallpaperComponent); - } - } - } - } while (type != XmlPullParser.END_DOCUMENT); success = true; } catch (FileNotFoundException e) { Slog.w(TAG, "no current wallpaper -- first boot?"); @@ -259,6 +221,75 @@ public class WallpaperDataParser { return new WallpaperLoadingResult(wallpaper, lockWallpaper, success); } + // This method updates `wallpaper` in place, but returns `lockWallpaper`. This is because + // `wallpaper` already exists if it's being read per `loadSystem`, but `lockWallpaper` is + // created conditionally if there is lock screen wallpaper data to read. + @VisibleForTesting + WallpaperData loadSettingsFromSerializer(TypedXmlPullParser parser, WallpaperData wallpaper, + int userId, boolean loadSystem, boolean loadLock, boolean keepDimensionHints, + DisplayData wpdData) throws IOException, XmlPullParserException { + WallpaperData lockWallpaper = null; + int type; + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) { + if ("kwp".equals(tag)) { + lockWallpaper = new WallpaperData(userId, FLAG_LOCK); + } + WallpaperData wallpaperToParse = + "wp".equals(tag) ? wallpaper : lockWallpaper; + + if (!multiCrop()) { + parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); + } + + String comp = parser.getAttributeValue(null, "component"); + if (removeNextWallpaperComponent()) { + wallpaperToParse.setComponent(comp != null + ? ComponentName.unflattenFromString(comp) + : null); + if (wallpaperToParse.getComponent() == null + || "android".equals(wallpaperToParse.getComponent() + .getPackageName())) { + wallpaperToParse.setComponent(mImageWallpaper); + } + } else { + wallpaperToParse.nextWallpaperComponent = comp != null + ? ComponentName.unflattenFromString(comp) + : null; + if (wallpaperToParse.nextWallpaperComponent == null + || "android".equals(wallpaperToParse.nextWallpaperComponent + .getPackageName())) { + wallpaperToParse.nextWallpaperComponent = mImageWallpaper; + } + } + + if (multiCrop()) { + parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); + } + + if (DEBUG) { + Slog.v(TAG, "mWidth:" + wpdData.mWidth); + Slog.v(TAG, "mHeight:" + wpdData.mHeight); + Slog.v(TAG, "cropRect:" + wallpaper.cropHint); + Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); + Slog.v(TAG, "mName:" + wallpaper.name); + if (removeNextWallpaperComponent()) { + Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent()); + } else { + Slog.v(TAG, "mNextWallpaperComponent:" + + wallpaper.nextWallpaperComponent); + } + } + } + } + } while (type != XmlPullParser.END_DOCUMENT); + + return lockWallpaper; + } + private void ensureSaneWallpaperData(WallpaperData wallpaper) { // Only overwrite cropHint if the rectangle is invalid. if (wallpaper.cropHint.width() < 0 @@ -324,7 +355,9 @@ public class WallpaperDataParser { getAttributeInt(parser, "totalCropTop", 0), getAttributeInt(parser, "totalCropRight", 0), getAttributeInt(parser, "totalCropBottom", 0)); - if (multiCrop() && mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) { + ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.getComponent() + : wallpaper.nextWallpaperComponent; + if (multiCrop() && mImageWallpaper.equals(componentName)) { wallpaper.mCropHints = new SparseArray<>(); for (Pair pair: screenDimensionPairs()) { Rect cropHint = new Rect( @@ -431,18 +464,7 @@ public class WallpaperDataParser { try { fstream = new FileOutputStream(journal.chooseForWrite(), false); TypedXmlSerializer out = Xml.resolveSerializer(fstream); - out.startDocument(null, true); - - if (wallpaper != null) { - writeWallpaperAttributes(out, "wp", wallpaper); - } - - if (lockWallpaper != null) { - writeWallpaperAttributes(out, "kwp", lockWallpaper); - } - - out.endDocument(); - + saveSettingsToSerializer(out, wallpaper, lockWallpaper); fstream.flush(); FileUtils.sync(fstream); fstream.close(); @@ -453,6 +475,22 @@ public class WallpaperDataParser { } } + @VisibleForTesting + void saveSettingsToSerializer(TypedXmlSerializer out, WallpaperData wallpaper, + WallpaperData lockWallpaper) throws IOException { + out.startDocument(null, true); + + if (wallpaper != null) { + writeWallpaperAttributes(out, "wp", wallpaper); + } + + if (lockWallpaper != null) { + writeWallpaperAttributes(out, "kwp", lockWallpaper); + } + + out.endDocument(); + } + @VisibleForTesting void writeWallpaperAttributes(TypedXmlSerializer out, String tag, WallpaperData wallpaper) throws IllegalArgumentException, IllegalStateException, IOException { @@ -462,7 +500,7 @@ public class WallpaperDataParser { out.startTag(null, tag); out.attributeInt(null, "id", wallpaper.wallpaperId); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { if (wallpaper.mCropHints == null) { Slog.e(TAG, "cropHints should not be null when saved"); wallpaper.mCropHints = new SparseArray<>(); @@ -562,10 +600,10 @@ public class WallpaperDataParser { } out.attribute(null, "name", wallpaper.name); - if (wallpaper.wallpaperComponent != null - && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) { + if (wallpaper.getComponent() != null + && !wallpaper.getComponent().equals(mImageWallpaper)) { out.attribute(null, "component", - wallpaper.wallpaperComponent.flattenToShortString()); + wallpaper.getComponent().flattenToShortString()); } if (wallpaper.allowBackup) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 78359bd1571755a31378b82316b8b3c94609d99b..4754ffb5cf6e611164ef43433cabf1cc7cc0d709 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.Flags.removeNextWallpaperComponent; import static android.app.WallpaperManager.COMMAND_REAPPLY; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; @@ -275,7 +276,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final boolean isMigration = moved && lockWallpaperChanged; final boolean isRestore = moved && !isMigration; final boolean isAppliedToLock = (wallpaper.mWhich & FLAG_LOCK) != 0; - final boolean needsUpdate = wallpaper.wallpaperComponent == null + final boolean needsUpdate = wallpaper.getComponent() == null || event != CLOSE_WRITE // includes the MOVED_TO case || wallpaper.imageWallpaperPending; @@ -526,7 +527,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * @return true unless the wallpaper changed during the color computation */ private boolean extractColors(WallpaperData wallpaper) { - if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent); + if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.getComponent()); String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; @@ -549,8 +550,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Not having a wallpaperComponent means it's a lock screen wallpaper. - final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) - || wallpaper.wallpaperComponent == null; + final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.getComponent()) + || wallpaper.getComponent() == null; if (imageWallpaper && wallpaper.getCropFile().exists()) { cropFile = wallpaper.getCropFile().getAbsolutePath(); } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) { @@ -823,13 +824,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); - t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent); + t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent()); if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken); mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId, null /* options */); mWindowManagerInternal.setWallpaperShowWhenLocked( mToken, (wallpaper.mWhich & FLAG_LOCK) != 0); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { mWindowManagerInternal.setWallpaperCropHints(mToken, mWallpaperCropper.getRelativeCropHints(wallpaper)); } else { @@ -905,7 +906,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) { - Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.getComponent() + ", reverting to built-in wallpaper!"); clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null); } @@ -1034,9 +1035,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { Slog.w(TAG, "Wallpaper service gone: " + name); - if (!Objects.equals(name, mWallpaper.wallpaperComponent)) { + if (!Objects.equals(name, mWallpaper.getComponent())) { Slog.e(TAG, "Does not match expected wallpaper component " - + mWallpaper.wallpaperComponent); + + mWallpaper.getComponent()); } mService = null; forEachDisplayConnector(connector -> connector.mEngine = null); @@ -1064,7 +1065,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS); if (DEBUG_LIVE) { Slog.i(TAG, - "Started wallpaper reconnect timeout for " + mWallpaper.wallpaperComponent); + "Started wallpaper reconnect timeout for " + mWallpaper.getComponent()); } } @@ -1080,7 +1081,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); // The broadcast of package update could be delayed after service disconnected. Try // to re-bind the service for 10 seconds. mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND; @@ -1109,7 +1110,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // The wallpaper disappeared. If this isn't a system-default one, track // crashes and fall back to default if it continues to misbehave. if (this == mWallpaper.connection) { - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId && !Objects.equals(mDefaultWallpaperComponent, wpService) @@ -1187,7 +1188,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Do not broadcast changes on ImageWallpaper since it's handled // internally by this class. - boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent); + boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.getComponent()); if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) { return; } @@ -1302,7 +1303,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mNewWallpaper.mWhich == FLAG_SYSTEM) { // New wp is system only, so old system+lock is now lock only final boolean originalIsStatic = mImageWallpaper.equals( - mOriginalSystem.wallpaperComponent); + mOriginalSystem.getComponent()); if (originalIsStatic) { // Static wp: image file rename has already been tried via // migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map @@ -1313,8 +1314,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.v(TAG, "static system+lock to system success"); } - lockWp.wallpaperComponent = - mOriginalSystem.wallpaperComponent; + lockWp.setComponent(mOriginalSystem.getComponent()); lockWp.connection = mOriginalSystem.connection; lockWp.connection.mWallpaper = lockWp; mOriginalSystem.mWhich = FLAG_LOCK; @@ -1375,7 +1375,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - final ComponentName wpService = wallpaper.wallpaperComponent; + final ComponentName wpService = wallpaper.getComponent(); if (wpService != null && wpService.getPackageName().equals(packageName)) { if (DEBUG_LIVE) { Slog.i(TAG, "Wallpaper " + wpService + " update has finished"); @@ -1401,8 +1401,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { doPackagesChangedLocked(true, wallpaper); } } @@ -1416,10 +1416,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { if (DEBUG_LIVE) { - Slog.i(TAG, "Wallpaper service " + wallpaper.wallpaperComponent + Slog.i(TAG, "Wallpaper service " + wallpaper.getComponent() + " is updating"); } wallpaper.wallpaperUpdating = true; @@ -1461,47 +1461,52 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) { boolean changed = false; - if (wallpaper.wallpaperComponent != null) { - int change = isPackageDisappearing(wallpaper.wallpaperComponent + if (wallpaper.getComponent() != null) { + int change = isPackageDisappearing(wallpaper.getComponent() .getPackageName()); if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { changed = true; if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } } - if (wallpaper.nextWallpaperComponent != null) { - int change = isPackageDisappearing(wallpaper.nextWallpaperComponent - .getPackageName()); - if (change == PACKAGE_PERMANENT_CHANGE - || change == PACKAGE_TEMPORARY_CHANGE) { - wallpaper.nextWallpaperComponent = null; + if (!removeNextWallpaperComponent()) { + if (wallpaper.nextWallpaperComponent != null) { + int change = isPackageDisappearing(wallpaper.nextWallpaperComponent + .getPackageName()); + if (change == PACKAGE_PERMANENT_CHANGE + || change == PACKAGE_TEMPORARY_CHANGE) { + wallpaper.nextWallpaperComponent = null; + } } } - if (wallpaper.wallpaperComponent != null - && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) { + if (wallpaper.getComponent() != null + && isPackageModified(wallpaper.getComponent().getPackageName())) { try { - mContext.getPackageManager().getServiceInfo(wallpaper.wallpaperComponent, + mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(), PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } - if (wallpaper.nextWallpaperComponent != null - && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) { - try { - mContext.getPackageManager().getServiceInfo(wallpaper.nextWallpaperComponent, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - } catch (NameNotFoundException e) { - wallpaper.nextWallpaperComponent = null; + if (!removeNextWallpaperComponent()) { + if (wallpaper.nextWallpaperComponent != null + && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) { + try { + mContext.getPackageManager().getServiceInfo( + wallpaper.nextWallpaperComponent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } catch (NameNotFoundException e) { + wallpaper.nextWallpaperComponent = null; + } } } return changed; @@ -1628,7 +1633,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM); // If we think we're going to be using the system image wallpaper imagery, make // sure we have something to render - if (mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) { + boolean isImageComponent; + if (removeNextWallpaperComponent()) { + isImageComponent = wallpaper.getComponent() == null + || mImageWallpaper.equals(wallpaper.getComponent()); + } else { + isImageComponent = mImageWallpaper.equals(wallpaper.nextWallpaperComponent); + } + if (isImageComponent) { // No crop file? Make sure we've finished the processing sequence if necessary if (!wallpaper.cropExists()) { if (DEBUG) { @@ -1877,8 +1889,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false; if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false; - final ComponentName cname = wallpaper.wallpaperComponent != null ? - wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; + final ComponentName cname; + if (removeNextWallpaperComponent()) { + cname = wallpaper.getComponent(); + } else { + cname = (wallpaper.getComponent() != null) + ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; + } if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) { // We failed to bind the desired wallpaper, but that might // happen if the wallpaper isn't direct-boot aware @@ -1905,10 +1922,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked"); - // We might end up persisting the current wallpaper data - // while locked, so pretend like the component was actually - // bound into place - wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; + if (!removeNextWallpaperComponent()) { + // We might end up persisting the current wallpaper data + // while locked, so pretend like the component was actually + // bound into place + wallpaper.setComponent(wallpaper.nextWallpaperComponent); + } final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich); // files from the previous static wallpaper may still be stored in memory. @@ -1984,7 +2003,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // lock only case: set the system wallpaper component to both screens if (which == FLAG_LOCK) { - component = wallpaper.wallpaperComponent; + component = wallpaper.getComponent(); finalWhich = FLAG_LOCK | FLAG_SYSTEM; } else { component = null; @@ -2290,7 +2309,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub checkPermission(READ_WALLPAPER_INTERNAL); WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId) : mWallpaperMap.get(userId); - if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) { return null; } SparseArray relativeSuggestedCrops = @@ -2740,7 +2759,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap) .get(mCurrentUserId); if (wallpaperData == null) return false; - return mImageWallpaper.equals(wallpaperData.wallpaperComponent); + return mImageWallpaper.equals(wallpaperData.getComponent()); } } @@ -2976,7 +2995,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId); final boolean systemIsStatic = originalSystemWallpaper != null && mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; /* If we're setting system but not lock, and lock is currently sharing the system @@ -3170,7 +3189,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } final boolean systemIsStatic = mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; if (which == FLAG_SYSTEM && systemIsBoth && systemIsStatic) { @@ -3192,7 +3211,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub liveSync = new WallpaperDestinationChangeHandler( newWallpaper); boolean same = changingToSame(name, newWallpaper.connection, - newWallpaper.wallpaperComponent); + newWallpaper.getComponent()); /* * If we have a shared system+lock wallpaper, and we reapply the same wallpaper @@ -3223,7 +3242,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } boolean lockBitmapCleared = false; - if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) { + if (!mImageWallpaper.equals(newWallpaper.getComponent())) { clearWallpaperBitmaps(newWallpaper); lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK; } @@ -3304,7 +3323,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Has the component changed? if (!force && changingToSame(componentName, wallpaper.connection, - wallpaper.wallpaperComponent)) { + wallpaper.getComponent())) { try { if (DEBUG_LIVE) { Slog.v(TAG, "Changing to the same component, ignoring"); @@ -3441,7 +3460,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } maybeDetachLastWallpapers(wallpaper); - wallpaper.wallpaperComponent = componentName; + wallpaper.setComponent(componentName); wallpaper.connection = newConn; newConn.mReply = reply; updateCurrentWallpapers(wallpaper); @@ -3566,7 +3585,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private void clearWallpaperComponentLocked(WallpaperData wallpaper) { - wallpaper.wallpaperComponent = null; + wallpaper.setComponent(null); detachWallpaperLocked(wallpaper); } @@ -3810,10 +3829,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM); wallpaper.wallpaperId = makeWallpaperIdLocked(); // always bump id at restore wallpaper.allowBackup = true; // by definition if it was restored - if (wallpaper.nextWallpaperComponent != null - && !wallpaper.nextWallpaperComponent.equals(mImageWallpaper)) { + ComponentName componentName = + removeNextWallpaperComponent() ? wallpaper.getComponent() + : wallpaper.nextWallpaperComponent; + if (componentName != null && !componentName.equals(mImageWallpaper)) { wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS; - if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false, + if (!bindWallpaperComponentLocked(componentName, false, false, wallpaper, null)) { // No such live wallpaper or other failure; fall back to the default // live wallpaper (since the profile being restored indicated that the @@ -3837,8 +3858,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (success) { mWallpaperCropper.generateCrop(wallpaper); // based on the new image + metadata wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_STATIC; - bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, true, false, - wallpaper, null); + bindWallpaperComponentLocked(componentName, true, false, wallpaper, null); } } } @@ -3886,7 +3906,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (multiCrop()) pw.print(" mCropHints="); pw.println(wallpaper.mCropHints); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); - pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); + pw.print(" mWallpaperComponent="); pw.println(wallpaper.getComponent()); pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim); pw.println(" mUidToDimAmount:"); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 8c23eaad55213892ddc2723cc6b5f92d030f17e6..ccc9b17ff840e66dafa3ce09f685e2c0913a8e6f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2641,7 +2641,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return true; } // Only do transfer after transaction has done when starting window exist. - if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommitCount > 0) { + if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) { mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT; return true; } @@ -2804,11 +2804,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void waitForSyncTransactionCommit(ArraySet wcAwaitingCommit) { - // Only add once per transition. - final boolean added = wcAwaitingCommit.contains(this); super.waitForSyncTransactionCommit(wcAwaitingCommit); - if (!added && mStartingData != null) { - mStartingData.mWaitForSyncTransactionCommitCount++; + if (mStartingData != null) { + mStartingData.mWaitForSyncTransactionCommit = true; } } @@ -2819,7 +2817,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } final StartingData lastData = mStartingData; - lastData.mWaitForSyncTransactionCommitCount--; + lastData.mWaitForSyncTransactionCommit = false; if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) { removeStartingWindowAnimation(lastData.mPrepareRemoveAnimation); } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) { @@ -2849,7 +2847,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean animate; final boolean hasImeSurface; if (mStartingData != null) { - if (mStartingData.mWaitForSyncTransactionCommitCount > 0 + if (mStartingData.mWaitForSyncTransactionCommit || mSyncState != SYNC_STATE_NONE) { mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY; mStartingData.mPrepareRemoveAnimation = prepareAnimation; @@ -8152,13 +8150,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * into account orientation per-app overrides applied by the device manufacturers. */ @Override + @ActivityInfo.ScreenOrientation protected int getOverrideOrientation() { - if (mWmService.mConstants.mIgnoreActivityOrientationRequest - && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { - return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + final int candidateOrientation; + if (!mWmService.mConstants.mIgnoreActivityOrientationRequest + || info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) { + candidateOrientation = super.getOverrideOrientation(); + } else { + candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } return mAppCompatController.getOrientationPolicy() - .overrideOrientationIfNeeded(super.getOverrideOrientation()); + .overrideOrientationIfNeeded(candidateOrientation); } /** diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 3d5b2732e948a38a32419af0f283668ccaf6ad00..6009b4a70e3e726b9e26cd89a989988fa693efcf 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6218,7 +6218,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void onProcessRemoved(String name, int uid) { synchronized (mGlobalLockWithoutBoost) { final WindowProcessController proc = mProcessNames.remove(name, uid); - if (proc != null && !mStartingProcessActivities.isEmpty()) { + if (proc != null && !proc.mHasEverAttached + && !mStartingProcessActivities.isEmpty()) { // Use a copy in case finishIfPossible changes the list indirectly. final ArrayList activities = new ArrayList<>(mStartingProcessActivities); diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java index 3be266e2951ba90c3b5a4cf0c45e768009625246..f069dcdbc86b38e907fe458d9336b96f5aa9d11d 100644 --- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java @@ -145,11 +145,13 @@ class AppCompatSizeCompatModePolicy { } } - void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) { + void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds, + @NonNull Configuration newParentConfig) { mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy() .findOpaqueNotFinishingActivityBelow() .map(activityRecord -> mSizeCompatScale) - .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds)); + .orElseGet(() -> calculateSizeCompatScale( + resolvedAppBounds, containerAppBounds, newParentConfig)); } void clearSizeCompatModeAttributes() { @@ -290,7 +292,7 @@ class AppCompatSizeCompatModePolicy { // Calculates the scale the size compatibility bounds into the region which is available // to application. final float lastSizeCompatScale = mSizeCompatScale; - updateSizeCompatScale(resolvedAppBounds, containerAppBounds); + updateSizeCompatScale(resolvedAppBounds, containerAppBounds, newParentConfiguration); final int containerTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = @@ -423,7 +425,7 @@ class AppCompatSizeCompatModePolicy { } private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds, - @NonNull Rect containerAppBounds) { + @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig) { final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); @@ -432,7 +434,8 @@ class AppCompatSizeCompatModePolicy { // original container or if it's a freeform window in desktop mode. boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH) || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext) - && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM); + && newParentConfig.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FREEFORM); return shouldAllowUpscaling ? Math.min( (float) viewportW / contentW, (float) viewportH / contentH) : 1f; } diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index 19941741ed19db39373729a1446d94bb5a3bd7a2..3a2cffbe1d8518344db3c100d23ac9603a5b0cbd 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -422,6 +422,7 @@ class DeferredDisplayUpdater { || first.brightnessMaximum != second.brightnessMaximum || first.brightnessDefault != second.brightnessDefault || first.installOrientation != second.installOrientation + || first.isForceSdr != second.isForceSdr || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate) || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio) || !first.thermalRefreshRateThrottling.contentEquals( diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index cc6904f9b3afdc7234c2c529cffa53266bc54d8b..34b5f6a24d413992cabe33d481915af6687eb279 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -103,7 +103,7 @@ public final class DesktopModeBoundsCalculator { final TaskDisplayArea displayArea = task.getDisplayArea(); final Rect screenBounds = displayArea.getBounds(); final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE); - if (!DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) { + if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { return centerInScreen(idealSize, screenBounds); } if (activity.mAppCompatController.getAppCompatAspectRatioOverrides() diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index 61fbb96882ec5a130bde61b2380d2ff20c9bdd9f..b5ea0bdfc27acb334a443d6eab0d12d7a48129d1 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -35,8 +35,8 @@ public final class DesktopModeHelper { "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); /** Whether desktop mode is enabled. */ - static boolean isDesktopModeEnabled(@NonNull Context context) { - return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context); + static boolean isDesktopModeEnabled() { + return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue(); } /** @@ -60,7 +60,7 @@ public final class DesktopModeHelper { * Return {@code true} if desktop mode can be entered on the current device. */ static boolean canEnterDesktopMode(@NonNull Context context) { - return isDesktopModeEnabled(context) + return isDesktopModeEnabled() && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context)); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 10e0641b0582d32fafa430c324b8ea9e08c509e9..21212e573ffa6c6cfa65dcea043f44d668d2f718 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -707,6 +707,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Retention(RetentionPolicy.SOURCE) @interface InputMethodTarget {} + /** The surface parent window of the IME container. */ + private WindowContainer mInputMethodSurfaceParentWindow; /** The surface parent of the IME container. */ @VisibleForTesting SurfaceControl mInputMethodSurfaceParent; @@ -1529,6 +1531,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayRotation.getLastOrientation(); } + WindowContainer getImeParentWindow() { + return mInputMethodSurfaceParentWindow; + } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { mAppTransitionController.registerRemoteAnimations(definition); } @@ -4733,13 +4739,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp Slog.i(TAG_WM, "ImeContainer is organized. Skip updateImeParent."); } // Leave the ImeContainer where the DisplayAreaPolicy placed it. - // FEATURE_IME is organized by vendor so they are responible for placing the surface. + // FEATURE_IME is organized by vendor so they are responsible for placing the surface. + mInputMethodSurfaceParentWindow = null; mInputMethodSurfaceParent = null; return; } - final SurfaceControl newParent = computeImeParent(); + final var newParentWindow = computeImeParent(); + final SurfaceControl newParent = + newParentWindow != null ? newParentWindow.getSurfaceControl() : null; if (newParent != null && newParent != mInputMethodSurfaceParent) { + mInputMethodSurfaceParentWindow = newParentWindow; mInputMethodSurfaceParent = newParent; getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent); if (DEBUG_IME_VISIBILITY) { @@ -4800,7 +4810,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Computes the window the IME should be attached to. */ @VisibleForTesting - SurfaceControl computeImeParent() { + WindowContainer computeImeParent() { if (!ImeTargetVisibilityPolicy.canComputeImeParent(mImeLayeringTarget, mImeInputTarget)) { return null; } @@ -4808,11 +4818,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // screen. If it's not covering the entire screen the IME might extend beyond the apps // bounds. if (shouldImeAttachedToApp()) { - return mImeLayeringTarget.mActivityRecord.getSurfaceControl(); + return mImeLayeringTarget.mActivityRecord; } // Otherwise, we just attach it to where the display area policy put it. - return mImeWindowsContainer.getParent() != null - ? mImeWindowsContainer.getParent().getSurfaceControl() : null; + return mImeWindowsContainer.getParent(); } void setLayoutNeeded() { diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index 169a76fe3afd09e1ed525b4ec9b2cc0daf8908a9..e007b1d07b343d0dfbec36c1ab9ff9bf322628ba 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -33,6 +33,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.InputApplicationHandle; import android.view.InputChannel; +import android.view.WindowInsets; import android.window.InputTransferToken; import com.android.internal.protolog.ProtoLog; @@ -180,22 +181,30 @@ class EmbeddedWindowController { return true; } - boolean transferToHost(@NonNull InputTransferToken embeddedWindowToken, + boolean transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken, @NonNull WindowState transferToHostWindowState) { EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken); if (!isValidTouchGestureParams(transferToHostWindowState, ew)) { return false; } + if (callingUid != ew.mOwnerUid) { + throw new SecurityException( + "Transfer request must originate from owner of transferFromToken"); + } return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(), transferToHostWindowState.mInputChannelToken); } - boolean transferToEmbedded(WindowState hostWindowState, + boolean transferToEmbedded(int callingUid, WindowState hostWindowState, @NonNull InputTransferToken transferToToken) { final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken); if (!isValidTouchGestureParams(hostWindowState, ew)) { return false; } + if (callingUid != hostWindowState.mOwnerUid) { + throw new SecurityException( + "Transfer request must originate from owner of transferFromToken"); + } return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken, ew.getInputChannelToken()); } @@ -222,6 +231,10 @@ class EmbeddedWindowController { private boolean mIsFocusable; + // The EmbeddedWindow can only request the IME. All other insets types are requested by + // the host window. + private @WindowInsets.Type.InsetsType int mRequestedVisibleTypes = 0; + /** * @param session calling session to check ownership of the window * @param clientToken client token used to clean up the map if the embedding process dies @@ -310,6 +323,27 @@ class EmbeddedWindowController { return mClient; } + @Override + public boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types) { + return (mRequestedVisibleTypes & types) != 0; + } + + @Override + public @WindowInsets.Type.InsetsType int getRequestedVisibleTypes() { + return mRequestedVisibleTypes; + } + + /** + * Only the IME can be requested from the EmbeddedWindow. + * @param requestedVisibleTypes other types than {@link WindowInsets.Type.IME} are + * not sent to system server via WindowlessWindowManager. + */ + void setRequestedVisibleTypes(@WindowInsets.Type.InsetsType int requestedVisibleTypes) { + if (mRequestedVisibleTypes != requestedVisibleTypes) { + mRequestedVisibleTypes = requestedVisibleTypes; + } + } + @Override public int getPid() { return mOwnerPid; @@ -375,6 +409,11 @@ class EmbeddedWindowController { @Override public boolean shouldControlIme() { + if (android.view.inputmethod.Flags.refactorInsetsController()) { + // EmbeddedWindow should never be able to control the IME directly, but only the + // RemoteInsetsControlTarget. + return false; + } return mHostWindowState != null; } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 43c3d05ac49dee8002e0e5fa7d4217e17382b862..e178203fed92237976b7d1602dd8636286b5b398 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -268,7 +268,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // TODO(b/353463205) change statsToken to be NonNull, after the flag is permanently enabled @Override - protected boolean updateClientVisibility(InsetsControlTarget caller, + protected boolean updateClientVisibility(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { InsetsControlTarget controlTarget = getControlTarget(); if (caller != controlTarget) { @@ -283,12 +283,13 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); controlTarget.setImeInputTargetRequestedVisibility(imeVisible); - } else { + } else if (caller instanceof InsetsControlTarget) { // In case of a virtual display that cannot show the IME, the // controlTarget will be null here, as no controlTarget was set yet. In // that case, proceed similar to the multi window mode (fallback = // RemoteInsetsControlTarget of the default display) - controlTarget = mDisplayContent.getImeHostOrFallback(caller.getWindow()); + controlTarget = mDisplayContent.getImeHostOrFallback( + ((InsetsControlTarget) caller).getWindow()); if (controlTarget != caller) { ImeTracker.forLogging().onProgress(statsToken, @@ -300,8 +301,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } } - WindowState windowState = caller.getWindow(); - invokeOnImeRequestedChangedListener(windowState, statsToken); + invokeOnImeRequestedChangedListener(caller, statsToken); } else { // TODO(b/353463205) add ImeTracker? } @@ -309,20 +309,16 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { return false; } boolean changed = super.updateClientVisibility(caller, statsToken); - if (!Flags.refactorInsetsController()) { + if (!Flags.refactorInsetsController() && caller instanceof InsetsControlTarget) { if (changed && caller.isRequestedVisible(mSource.getType())) { - reportImeDrawnForOrganizerIfNeeded(caller); + reportImeDrawnForOrganizerIfNeeded((InsetsControlTarget) caller); } } changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate(); if (Flags.refactorInsetsController()) { if (changed) { - // RemoteInsetsControlTarget does not have a window. In this case, we use the - // windowState from the imeInputTarget - WindowState windowState = caller.getWindow() != null ? caller.getWindow() - : ((mDisplayContent.getImeInputTarget() != null) - ? mDisplayContent.getImeInputTarget().getWindowState() : null); - invokeOnImeRequestedChangedListener(windowState, statsToken); + invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(), + statsToken); } else { // TODO(b/329229469) change phase and check cancelled / failed ImeTracker.forLogging().onCancelled(statsToken, @@ -334,32 +330,31 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { void onInputTargetChanged(InputTarget target) { if (Flags.refactorInsetsController() && target != null) { - WindowState targetWin = target.getWindowState(); InsetsControlTarget imeControlTarget = getControlTarget(); - if (target != imeControlTarget && targetWin != null) { + if (target != imeControlTarget) { // If the targetWin is not the imeControlTarget (=RemoteInsetsControlTarget) let it // know about the new requestedVisibleTypes for the IME. if (imeControlTarget != null) { imeControlTarget.setImeInputTargetRequestedVisibility( - (targetWin.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); + (target.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); } } } } // TODO(b/353463205) check callers to see if we can make statsToken @NonNull - private void invokeOnImeRequestedChangedListener(WindowState windowState, + private void invokeOnImeRequestedChangedListener(InsetsTarget insetsTarget, @Nullable ImeTracker.Token statsToken) { final var imeListener = mDisplayContent.mWmService.mOnImeRequestedChangedListener; if (imeListener != null) { - if (windowState != null) { + if (insetsTarget != null) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY); mDisplayContent.mWmService.mH.post(() -> { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER); - imeListener.onImeRequestedChanged(windowState.mClient.asBinder(), - windowState.isRequestedVisible(WindowInsets.Type.ime()), statsToken); + imeListener.onImeRequestedChanged(insetsTarget.getWindowToken(), + insetsTarget.isRequestedVisible(WindowInsets.Type.ime()), statsToken); }); } else { ImeTracker.forLogging().onFailed(statsToken, @@ -676,7 +671,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { return target == mDisplayContent.getImeFallback(); } - private boolean isImeInputTarget(@NonNull InsetsControlTarget target) { + private boolean isImeInputTarget(@NonNull InsetsTarget target) { return target == mDisplayContent.getImeInputTarget(); } diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java index 0c0b794182e7bee0d5c7e74a131e0c24679402af..40ce9db8a60888cefb1cafe1e64ea29c42722546 100644 --- a/services/core/java/com/android/server/wm/InputTarget.java +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import android.os.IBinder; import android.util.proto.ProtoOutputStream; /** @@ -25,16 +24,13 @@ import android.util.proto.ProtoOutputStream; * Both WindowState and EmbeddedWindows can receive input. This consolidates some common properties * of both targets. */ -interface InputTarget { +interface InputTarget extends InsetsTarget { /* Get the WindowState associated with the target. */ WindowState getWindowState(); /* Display id of the target. */ int getDisplayId(); - /* Client IWindow for the target. */ - IBinder getWindowToken(); - /* Owning pid of the target. */ int getPid(); int getUid(); diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java index 07e249a2004f55bf71a66d56aa45ddae2f4299bc..7043aacfc44dba3195d3436fe2b1a2cd76b62cb2 100644 --- a/services/core/java/com/android/server/wm/InsetsControlTarget.java +++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java @@ -18,6 +18,7 @@ package com.android.server.wm; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; +import android.os.IBinder; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.ImeTracker; @@ -25,7 +26,7 @@ import android.view.inputmethod.ImeTracker; /** * Generalization of an object that can control insets state. */ -interface InsetsControlTarget { +interface InsetsControlTarget extends InsetsTarget { /** * Notifies the control target that the insets control has changed. @@ -42,16 +43,17 @@ interface InsetsControlTarget { return null; } - /** - * @return {@code true} if any of the {@link InsetsType} is requested visible by this target. - */ + @Override + default IBinder getWindowToken() { + return null; + } + + @Override default boolean isRequestedVisible(@InsetsType int types) { return (WindowInsets.Type.defaultVisible() & types) != 0; } - /** - * @return {@link InsetsType}s which are requested visible by this target. - */ + @Override default @InsetsType int getRequestedVisibleTypes() { return WindowInsets.Type.defaultVisible(); } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 129078b0a23530d3c9bed155ef5c651c8a616637..b414a862f87474c6998fcad35a57f4835b9f750a 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -434,7 +434,7 @@ class InsetsPolicy { return originalState; } - void onRequestedVisibleTypesChanged(InsetsControlTarget caller, + void onRequestedVisibleTypesChanged(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { mStateController.onRequestedVisibleTypesChanged(caller, statsToken); checkAbortTransient(caller); @@ -449,7 +449,7 @@ class InsetsPolicy { * * @param caller who changed the insets state. */ - private void checkAbortTransient(InsetsControlTarget caller) { + private void checkAbortTransient(InsetsTarget caller) { if (mShowingTransientTypes == 0) { return; } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 8f90b2d183e893eb6fd721a884449642ad4417aa..f0a4763796e380c7b8cd82e4799e30fd06033afe 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -529,13 +529,27 @@ class InsetsSourceProvider { setClientVisible((WindowInsets.Type.defaultVisible() & mSource.getType()) != 0); return; } + boolean initiallyVisible = mClientVisible; final Point surfacePosition = getWindowFrameSurfacePosition(); mAdapter = new ControlAdapter(surfacePosition); if (mSource.getType() == WindowInsets.Type.ime()) { + if (android.view.inputmethod.Flags.refactorInsetsController()) { + if (mClientVisible && mServerVisible) { + WindowContainer imeParentWindow = mDisplayContent.getImeParentWindow(); + // If the IME is attached to an app window, only consider it initially visible + // if the parent is visible and wasn't part of a transition. + initiallyVisible = + imeParentWindow != null && !imeParentWindow.inTransitionSelfOrParent() + && imeParentWindow.isVisible() + && imeParentWindow.isVisibleRequested(); + } else { + initiallyVisible = false; + } + } setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime())); } final Transaction t = mWindowContainer.getSyncTransaction(); - mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */, + mWindowContainer.startAnimation(t, mAdapter, !initiallyVisible /* hidden */, ANIMATION_TYPE_INSETS_CONTROL); // The leash was just created. We cannot dispatch it until its surface transaction is @@ -545,14 +559,16 @@ class InsetsSourceProvider { final SurfaceControl leash = mAdapter.mCapturedLeash; mControlTarget = target; updateVisibility(); - boolean initiallyVisible = mClientVisible; if (mSource.getType() == WindowInsets.Type.ime()) { - // The IME cannot be initially visible, see ControlAdapter#startAnimation below. - // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing control, - // but this won't have reached here yet by the time the new control is created. - // Note: The DisplayImeController needs the correct previous client's visibility, so we - // only override the initiallyVisible here. - initiallyVisible = false; + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + // The IME cannot be initially visible, see ControlAdapter#startAnimation below. + // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing + // control, but this won't have reached here yet by the time the new control is + // created. + // Note: The DisplayImeController needs the correct previous client's visibility, + // so we only override the initiallyVisible here. + initiallyVisible = false; + } } mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash, initiallyVisible, surfacePosition, getInsetsHint()); @@ -598,7 +614,7 @@ class InsetsSourceProvider { mSeamlessRotating = false; } - boolean updateClientVisibility(InsetsControlTarget caller, + boolean updateClientVisibility(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { final boolean requestedVisible = caller.isRequestedVisible(mSource.getType()); if (caller != mControlTarget || requestedVisible == mClientVisible) { @@ -799,7 +815,7 @@ class InsetsSourceProvider { @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { // TODO(b/166736352): Check if we still need to control the IME visibility here. if (mSource.getType() == WindowInsets.Type.ime()) { - if (!android.view.inputmethod.Flags.refactorInsetsController() || !mClientVisible) { + if (!android.view.inputmethod.Flags.refactorInsetsController()) { // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed. t.setAlpha(animationLeash, 1 /* alpha */); t.hide(animationLeash); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 481ecd3447f10d7f97076de284846b98e938831b..3e39a45fa5f34887bf939c5e0cdd1a42bf1b14b1 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -219,7 +219,7 @@ class InsetsStateController { } } - void onRequestedVisibleTypesChanged(InsetsControlTarget caller, + void onRequestedVisibleTypesChanged(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { boolean changed = false; for (int i = mProviders.size() - 1; i >= 0; i--) { @@ -238,7 +238,7 @@ class InsetsStateController { } } - @InsetsType int getFakeControllingTypes(InsetsControlTarget target) { + @InsetsType int getFakeControllingTypes(InsetsTarget target) { @InsetsType int types = 0; for (int i = mProviders.size() - 1; i >= 0; i--) { final InsetsSourceProvider provider = mProviders.valueAt(i); diff --git a/services/core/java/com/android/server/wm/InsetsTarget.java b/services/core/java/com/android/server/wm/InsetsTarget.java new file mode 100644 index 0000000000000000000000000000000000000000..b918ca3dbff62c361cc1fa94033bd12416da255b --- /dev/null +++ b/services/core/java/com/android/server/wm/InsetsTarget.java @@ -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.server.wm; + +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.WindowInsets; + +/** + * A common parent for {@link InputTarget} and {@link InsetsControlTarget}: Some types (like the + * {@link EmbeddedWindowController.EmbeddedWindow}) should not be a control target for insets in + * general, but should be able to request the IME. To archive this, the InsetsTarget contains the + * minimal information that those interfaces share (and what is needed to show the IME. + */ +public interface InsetsTarget { + + /** + * @return Client IWindow token for the target. + */ + @Nullable + IBinder getWindowToken(); + + /** + * @param types The {@link WindowInsets.Type}s which requestedVisibility status is returned. + * @return {@code true} if any of the {@link WindowInsets.Type.InsetsType} is requested + * visible by this target. + */ + boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types); + + /** + * @return {@link WindowInsets.Type.InsetsType}s which are requested visible by this target. + */ + @WindowInsets.Type.InsetsType int getRequestedVisibleTypes(); +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 8f5612c61e1c1179e9950d729f1c181c3ef8da58..84072e26761a91c8677f1c372b2d19ab3e4d2c53 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1841,6 +1841,7 @@ class RootWindowContainer extends WindowContainer } boolean attachApplication(WindowProcessController app) throws RemoteException { + app.mHasEverAttached = true; final ArrayList activities = mService.mStartingProcessActivities; RemoteException remoteException = null; boolean hasActivityStarted = false; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 2ea2aeb6b74e30a78a48db9f8b92c14132b61429..5550f3efaa3afef8e4d6e7b90b92f56a9d583c29 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -705,8 +705,25 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win, imeStatsToken); } else { - ImeTracker.forLogging().onFailed(imeStatsToken, - ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + EmbeddedWindowController.EmbeddedWindow embeddedWindow = null; + if (android.view.inputmethod.Flags.refactorInsetsController()) { + embeddedWindow = mService.mEmbeddedWindowController.getByWindowToken( + window.asBinder()); + } + if (embeddedWindow != null) { + // If there is no WindowState for the IWindow, it could be still an + // EmbeddedWindow. Therefore, check the EmbeddedWindowController as well + // TODO(b/329229469) Use different phase here + ImeTracker.forLogging().onProgress(imeStatsToken, + ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + embeddedWindow.setRequestedVisibleTypes( + requestedVisibleTypes & WindowInsets.Type.ime()); + embeddedWindow.getDisplayContent().getInsetsPolicy() + .onRequestedVisibleTypesChanged(embeddedWindow, imeStatsToken); + } else { + ImeTracker.forLogging().onFailed(imeStatsToken, + ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + } } } } diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index 22c7e8c98808e4b168706f793b4f158b861b25ab..24fb20731c436555da53fb16e3a9e4095e13f061 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -69,7 +69,7 @@ public abstract class StartingData { * Note this isn't equal to transition playing, the period should be * Sync finishNow -> Start transaction apply. */ - int mWaitForSyncTransactionCommitCount; + boolean mWaitForSyncTransactionCommit; /** * For Shell transition. @@ -112,7 +112,7 @@ public abstract class StartingData { public String toString() { return getClass().getSimpleName() + "{" + Integer.toHexString(System.identityHashCode(this)) - + " mWaitForSyncTransactionCommitCount=" + mWaitForSyncTransactionCommitCount + + " waitForSyncTransactionCommit=" + mWaitForSyncTransactionCommit + " removeAfterTransaction= " + mRemoveAfterTransaction + "}"; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3490b3e12b2a80aea1791430bd81248ce02c4c93..86bb75ab3f8c9de538c095a15ab29d6246f3a4f5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -35,6 +35,8 @@ import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; +import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; +import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; @@ -49,6 +51,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.SurfaceControl.METADATA_TASK_ID; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; @@ -133,6 +136,7 @@ import android.app.IActivityController; import android.app.PictureInPictureParams; import android.app.TaskInfo; import android.app.WindowConfiguration; +import android.app.compat.CompatChanges; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -503,6 +507,12 @@ class Task extends TaskFragment { int mOffsetXForInsets; int mOffsetYForInsets; + /** + * Whether the compatibility overrides that change the resizability of the app should be allowed + * for the specific app. + */ + boolean mAllowForceResizeOverride = true; + private final AnimatingActivityRegistry mAnimatingActivityRegistry = new AnimatingActivityRegistry(); @@ -666,6 +676,7 @@ class Task extends TaskFragment { intent = _intent; mMinWidth = minWidth; mMinHeight = minHeight; + updateAllowForceResizeOverride(); } mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity); mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper); @@ -1028,6 +1039,7 @@ class Task extends TaskFragment { mTaskSupervisor.mRecentTasks.remove(this); mTaskSupervisor.mRecentTasks.add(this); } + updateAllowForceResizeOverride(); } /** Sets the original minimal width and height. */ @@ -1823,6 +1835,17 @@ class Task extends TaskFragment { -1 /* don't check PID */, -1 /* don't check UID */, this); } + private void updateAllowForceResizeOverride() { + try { + mAllowForceResizeOverride = mAtmService.mContext.getPackageManager().getProperty( + PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, + getBasePackageName()).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + // Package not found or property not defined, reset to default value. + mAllowForceResizeOverride = true; + } + } + /** * Check that a given bounds matches the application requested orientation. * @@ -2789,11 +2812,15 @@ class Task extends TaskFragment { @Override void onDisplayChanged(DisplayContent dc) { + final int lastDisplayId = getDisplayId(); super.onDisplayChanged(dc); if (isLeafTask()) { final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; - mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged( - mTaskId, displayId); + //Send the callback when the task reparented to another display. + if (lastDisplayId != displayId) { + mWmService.mAtmService.getTaskChangeNotificationController() + .notifyTaskDisplayChanged(mTaskId, displayId); + } } if (isRootTask()) { updateSurfaceBounds(); @@ -2808,7 +2835,18 @@ class Task extends TaskFragment { boolean isResizeable(boolean checkPictureInPictureSupport) { final boolean forceResizable = mAtmService.mForceResizableActivities && getActivityType() == ACTIVITY_TYPE_STANDARD; - return forceResizable || ActivityInfo.isResizeableMode(mResizeMode) + if (forceResizable) return true; + + final UserHandle userHandle = UserHandle.getUserHandleForUid(mUserId); + final boolean forceResizableOverride = mAllowForceResizeOverride + && CompatChanges.isChangeEnabled( + FORCE_RESIZE_APP, getBasePackageName(), userHandle); + final boolean forceNonResizableOverride = mAllowForceResizeOverride + && CompatChanges.isChangeEnabled( + FORCE_NON_RESIZE_APP, getBasePackageName(), userHandle); + + if (forceNonResizableOverride) return false; + return forceResizableOverride || ActivityInfo.isResizeableMode(mResizeMode) || (mSupportsPictureInPicture && checkPictureInPictureSupport); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 638e92f112c7247783324d51177b14dc6715ec7d..42ea5a88a09b0e115b2761c9f5802c37217e6df1 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -28,6 +28,7 @@ import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ActivityRecord.State.RESUMED; @@ -57,6 +58,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; +import com.android.server.pm.UserManagerInternal; import com.android.server.wm.LaunchParamsController.LaunchParams; import java.io.PrintWriter; @@ -1761,10 +1763,10 @@ final class TaskDisplayArea extends DisplayArea { * @return last reparented root task, or {@code null} if the root tasks had to be destroyed. */ Task remove() { + final TaskDisplayArea toDisplayArea = getReparentToTaskDisplayArea(getFocusedRootTask()); mPreferredTopFocusableRootTask = null; // TODO(b/153090332): Allow setting content removal mode per task display area final boolean destroyContentOnRemoval = mDisplayContent.shouldDestroyContentOnRemove(); - final TaskDisplayArea toDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); Task lastReparentedRootTask = null; // Root tasks could be reparented from the removed display area to other display area. After @@ -1830,6 +1832,41 @@ final class TaskDisplayArea extends DisplayArea { return lastReparentedRootTask; } + /** + * Returns the {@link TaskDisplayArea} to which root tasks should be reparented. + * + *

In the automotive multi-user multi-display environment where background users have + * UI access on their assigned displays (a.k.a. visible background users), it's not allowed to + * launch an activity on an unassigned display. If an activity is attempted to launch on an + * unassigned display, it throws an exception. + *

This method determines the appropriate {@link TaskDisplayArea} for reparenting root tasks + * when a display is removed, in order to avoid the exception. If the root task is null, + * the visible background user is not supported or the user associated with the root task is + * not a visible background user, it returns the default {@link TaskDisplayArea} of the default + * display. Otherwise, it returns the default {@link TaskDisplayArea} of the main display + * assigned to the user. + * + * @param rootTask The root task whose {@link TaskDisplayArea} needs to be determined. + * @return The {@link TaskDisplayArea} where the root tasks should be reparented to. + */ + private TaskDisplayArea getReparentToTaskDisplayArea(Task rootTask) { + final TaskDisplayArea defaultTaskDisplayArea = + mRootWindowContainer.getDefaultTaskDisplayArea(); + if (rootTask == null) { + return defaultTaskDisplayArea; + } + UserManagerInternal userManagerInternal = mAtmService.mWindowManager.mUmInternal; + if (!userManagerInternal.isVisibleBackgroundFullUser(rootTask.mUserId)) { + return defaultTaskDisplayArea; + } + int toDisplayId = userManagerInternal.getMainDisplayAssignedToUser(rootTask.mUserId); + if (toDisplayId == INVALID_DISPLAY) { + return defaultTaskDisplayArea; + } + DisplayContent dc = mRootWindowContainer.getDisplayContent(toDisplayId); + return dc != null ? dc.getDefaultTaskDisplayArea() : defaultTaskDisplayArea; + } + /** Whether this task display area can request orientation. */ boolean canSpecifyOrientation(@ScreenOrientation int orientation) { // Only allow to specify orientation if this TDA is the last focused one on this logical diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 4eba36fd52a4b8d8a61ba710d586d8a32d79bca3..0a47522f7df62d5222fb74b26eb9ab264debafba 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2875,19 +2875,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return out; } - // Get the animation theme from the top-most application window - // when Flags.customAnimationsBehindTranslucent() is false. final AnimationOptions animOptionsForActivityTransition = calculateAnimationOptionsForActivityTransition(type, sortedTargets); - if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) { out.setAnimationOptions(animOptionsForActivityTransition); } - // Store the animation options of the topmost non-translucent change - // (Used when Flags.customAnimationsBehindTranslucent() is true) - AnimationOptions activityAboveAnimationOptions = null; - final ArraySet occludedAtEndContainers = new ArraySet<>(); // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. final int count = sortedTargets.size(); @@ -2932,6 +2925,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final TaskFragment taskFragment = target.asTaskFragment(); final boolean isEmbeddedTaskFragment = taskFragment != null && taskFragment.isEmbedded(); + final IBinder taskFragmentToken = + taskFragment != null ? taskFragment.getFragmentToken() : null; + change.setTaskFragmentToken(taskFragmentToken); final ActivityRecord activityRecord = target.asActivityRecord(); if (task != null) { @@ -3006,26 +3002,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255)); } - // Calculate the animation options for this change + AnimationOptions animOptions = null; if (Flags.moveAnimationOptionsToChange()) { - AnimationOptions animOptions = null; - if (Flags.customAnimationsBehindTranslucent() && activityRecord != null) { - if (activityAboveAnimationOptions != null) { - // Inherit the options from one of the changes on top of this - animOptions = activityAboveAnimationOptions; - } else { - // Create the options based on this change's custom animations and layout - // parameters - animOptions = getOptions(activityRecord /* customAnimActivity */, - activityRecord /* animLpActivity */); - if (!change.hasFlags(FLAG_TRANSLUCENT)) { - // If this change is not translucent, its options are going to be - // inherited by the changes below - activityAboveAnimationOptions = animOptions; - } - } - } else if (activityRecord != null && animOptionsForActivityTransition != null) { - // Use the same options from the top activity for all the activities + if (activityRecord != null && animOptionsForActivityTransition != null) { animOptions = animOptionsForActivityTransition; } else if (Flags.activityEmbeddingOverlayPresentationFlag() && isEmbeddedTaskFragment) { @@ -3074,42 +3053,28 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { @Nullable private static AnimationOptions calculateAnimationOptionsForActivityTransition( @TransitionType int type, @NonNull ArrayList sortedTargets) { + TransitionInfo.AnimationOptions animOptions = null; + + // Check if the top-most app is an activity (ie. activity->activity). If so, make sure + // to honor its custom transition options. WindowContainer topApp = null; for (int i = 0; i < sortedTargets.size(); i++) { - if (!isWallpaper(sortedTargets.get(i).mContainer)) { - topApp = sortedTargets.get(i).mContainer; - break; - } + if (isWallpaper(sortedTargets.get(i).mContainer)) continue; + topApp = sortedTargets.get(i).mContainer; + break; } - ActivityRecord animLpActivity = findAnimLayoutParamsActivityRecord(type, sortedTargets); - return getOptions(topApp.asActivityRecord() /* customAnimActivity */, - animLpActivity /* animLpActivity */); - } - - /** - * Updates and returns animOptions with the layout parameters of animLpActivity - * @param customAnimActivity the activity that drives the custom animation options - * @param animLpActivity the activity that drives the animation options with its layout - * parameters - * @return the options extracted from the provided activities - */ - @Nullable - private static AnimationOptions getOptions(@Nullable ActivityRecord customAnimActivity, - @Nullable ActivityRecord animLpActivity) { - AnimationOptions animOptions = null; - // Custom - if (customAnimActivity != null) { - animOptions = addCustomActivityTransition(customAnimActivity, true /* open */, - animOptions); - animOptions = addCustomActivityTransition(customAnimActivity, false /* open */, + if (topApp.asActivityRecord() != null) { + final ActivityRecord topActivity = topApp.asActivityRecord(); + animOptions = addCustomActivityTransition(topActivity, true/* open */, + null /* animOptions */); + animOptions = addCustomActivityTransition(topActivity, false/* open */, animOptions); } - - // Layout parameters + final ActivityRecord animLpActivity = + findAnimLayoutParamsActivityRecord(type, sortedTargets); final WindowState mainWindow = animLpActivity != null ? animLpActivity.findMainWindow() : null; - final WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null; - + WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null; if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING && animLp.windowAnimations != 0) { // Don't send animation options if no windowAnimations have been set or if the we @@ -3249,9 +3214,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return ancestor; } - @Nullable - private static ActivityRecord findAnimLayoutParamsActivityRecord( - @TransitionType int transit, @NonNull List sortedTargets) { + private static ActivityRecord findAnimLayoutParamsActivityRecord(int type, + ArrayList sortedTargets) { + // Find the layout params of the top-most application window that is part of the + // transition, which is what will control the animation theme. final ArraySet activityTypes = new ArraySet<>(); final int targetCount = sortedTargets.size(); for (int i = 0; i < targetCount; ++i) { @@ -3271,7 +3237,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // activity through the layout parameter animation style. return null; } + return findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes); + } + private static ActivityRecord findAnimLayoutParamsActivityRecord( + List sortedTargets, + @TransitionType int transit, ArraySet activityTypes) { // Remote animations always win, but fullscreen windows override non-fullscreen windows. ActivityRecord result = lookForTopWindowWithFilter(sortedTargets, w -> w.getRemoteAnimationDefinition() != null diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 9d46529574871125fa5b49239507dbf2f62f8d1c..0a9cb1c38dabd55cbc59e3a1ff51dda271f48353 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -35,6 +35,7 @@ import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; @@ -115,7 +116,6 @@ import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.AlwaysTruePredicate; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -457,7 +457,7 @@ class WindowContainer extends ConfigurationContainer< source.setFrame(provider.getArbitraryRectangle()) .updateSideHint(getBounds()) .setBoundingRects(provider.getBoundingRects()); - if (Flags.enableCaptionCompatInsetForceConsumption()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { source.setFlags(provider.getFlags()); } mLocalInsetsSources.put(id, source); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 33f2dd103c2e378be66c373b7357d79bb1dbe90c..b8f47cce60055c144f363916752b2b038a82c173 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9212,6 +9212,8 @@ public class WindowManagerService extends IWindowManager.Stub final InputApplicationHandle applicationHandle; final String name; Objects.requireNonNull(outInputChannel); + Objects.requireNonNull(inputTransferToken); + synchronized (mGlobalLock) { WindowState hostWindowState = hostInputTransferToken != null ? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null; @@ -9236,6 +9238,7 @@ public class WindowManagerService extends IWindowManager.Stub Objects.requireNonNull(transferFromToken); Objects.requireNonNull(transferToToken); + final int callingUid = Binder.getCallingUid(); final long identity = Binder.clearCallingIdentity(); boolean didTransfer; try { @@ -9245,12 +9248,14 @@ public class WindowManagerService extends IWindowManager.Stub // represents an embedded window so transfer from host to embedded. WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken()); if (windowStateTo != null) { - didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken, + didTransfer = mEmbeddedWindowController.transferToHost(callingUid, + transferFromToken, windowStateTo); } else { WindowState windowStateFrom = mInputToWindowMap.get( transferFromToken.getToken()); - didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom, + didTransfer = mEmbeddedWindowController.transferToEmbedded(callingUid, + windowStateFrom, transferToToken); } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 976be4aa3bd4ee4087a16a423dcb5daecf27e40f..32fe303b9e9024302afc3e38a6b18619a8a92ffa 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -204,6 +204,9 @@ public class WindowProcessController extends ConfigurationContainer 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) { - stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM; + && minTaskLayer > 1) { + if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) { + stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM; + } else { + stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE; + } } stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; if (visible) { diff --git a/services/core/java/com/android/server/wm/utils/TEST_MAPPING b/services/core/java/com/android/server/wm/utils/TEST_MAPPING index aa69d2a189481629c83ff3a6fdd22cc9794fb2fe..6f34cd047d5f8fff160b5d183eb8202f78f66d05 100644 --- a/services/core/java/com/android/server/wm/utils/TEST_MAPPING +++ b/services/core/java/com/android/server/wm/utils/TEST_MAPPING @@ -1,18 +1,7 @@ { "presubmit": [ { - "name": "WmTests", - "options": [ - { - "include-filter": "com.android.server.wm.utils" - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "WmTests_wm_utils_Presubmit" } ] } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 67346ab1b3586f0a984a924f23aa6133ec4e9150..5cd117b512d4d5e7c59265e72c174ec1866b97b6 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -107,6 +107,7 @@ static struct { jclass clazz; jmethodID notifyInputDevicesChanged; jmethodID notifyTouchpadHardwareState; + jmethodID notifyTouchpadGestureInfo; jmethodID notifySwitch; jmethodID notifyInputChannelBroken; jmethodID notifyNoFocusedWindowAnr; @@ -356,6 +357,7 @@ public: FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId); void setStylusPointerIconEnabled(bool enabled); void setInputMethodConnectionIsActive(bool isActive); + void setKeyRemapping(const std::map& keyRemapping); /* --- InputReaderPolicyInterface implementation --- */ @@ -363,6 +365,7 @@ public: void notifyInputDevicesChanged(const std::vector& inputDevices) override; void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs, int32_t deviceId) override; + void notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) override; std::shared_ptr getKeyboardLayoutOverlay( const InputDeviceIdentifier& identifier, const std::optional keyboardLayoutInfo) override; @@ -502,6 +505,9 @@ private: // True if there is an active input method connection. bool isInputMethodConnectionActive{false}; + + // Keycodes to be remapped. + std::map keyRemapping{}; } mLocked GUARDED_BY(mLock); std::atomic mInteractive; @@ -759,6 +765,8 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled; outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled; + + outConfig->keyRemapping = mLocked.keyRemapping; } // release lock } @@ -996,6 +1004,15 @@ void NativeInputManager::notifyTouchpadHardwareState(const SelfContainedHardware checkAndClearExceptionFromCallback(env, "notifyTouchpadHardwareState"); } +void NativeInputManager::notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) { + ATRACE_CALL(); + JNIEnv* env = jniEnv(); + + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyTouchpadGestureInfo, type, deviceId); + + checkAndClearExceptionFromCallback(env, "notifyTouchpadGestureInfo"); +} + std::shared_ptr NativeInputManager::getKeyboardLayoutOverlay( const InputDeviceIdentifier& identifier, const std::optional keyboardLayoutInfo) { @@ -1797,9 +1814,30 @@ void NativeInputManager::loadAdditionalMouseResources( ATRACE_CALL(); JNIEnv* env = jniEnv(); - for (int32_t iconId = static_cast(PointerIconStyle::TYPE_CONTEXT_MENU); - iconId <= static_cast(PointerIconStyle::TYPE_HANDWRITING); ++iconId) { - const PointerIconStyle pointerIconStyle = static_cast(iconId); + constexpr static std::array ADDITIONAL_STYLES{PointerIconStyle::TYPE_CONTEXT_MENU, + PointerIconStyle::TYPE_HAND, + PointerIconStyle::TYPE_HELP, + PointerIconStyle::TYPE_WAIT, + PointerIconStyle::TYPE_CELL, + PointerIconStyle::TYPE_CROSSHAIR, + PointerIconStyle::TYPE_TEXT, + PointerIconStyle::TYPE_VERTICAL_TEXT, + PointerIconStyle::TYPE_ALIAS, + PointerIconStyle::TYPE_COPY, + PointerIconStyle::TYPE_NO_DROP, + PointerIconStyle::TYPE_ALL_SCROLL, + PointerIconStyle::TYPE_HORIZONTAL_DOUBLE_ARROW, + PointerIconStyle::TYPE_VERTICAL_DOUBLE_ARROW, + PointerIconStyle::TYPE_TOP_RIGHT_DOUBLE_ARROW, + PointerIconStyle::TYPE_TOP_LEFT_DOUBLE_ARROW, + PointerIconStyle::TYPE_ZOOM_IN, + PointerIconStyle::TYPE_ZOOM_OUT, + PointerIconStyle::TYPE_GRAB, + PointerIconStyle::TYPE_GRABBING, + PointerIconStyle::TYPE_HANDWRITING, + PointerIconStyle::TYPE_SPOT_HOVER}; + + for (const auto pointerIconStyle : ADDITIONAL_STYLES) { PointerIcon pointerIcon = loadPointerIcon(env, displayId, pointerIconStyle); (*outResources)[pointerIconStyle] = toSpriteIcon(pointerIcon); if (!pointerIcon.bitmapFrames.empty()) { @@ -1878,6 +1916,16 @@ void NativeInputManager::setInputMethodConnectionIsActive(bool isActive) { mInputManager->getDispatcher().setInputMethodConnectionIsActive(isActive); } +void NativeInputManager::setKeyRemapping(const std::map& keyRemapping) { + { // acquire lock + std::scoped_lock _l(mLock); + mLocked.keyRemapping = keyRemapping; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::Change::KEY_REMAPPING); +} + // ---------------------------------------------------------------------------- static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) { @@ -1951,10 +1999,19 @@ static std::vector getIntArray(JNIEnv* env, jintArray arr) { return vec; } -static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId, - jint fromKeyCode, jint toKeyCode) { +static void nativeSetKeyRemapping(JNIEnv* env, jobject nativeImplObj, jintArray fromKeyCodesArr, + jintArray toKeyCodesArr) { + const std::vector fromKeycodes = getIntArray(env, fromKeyCodesArr); + const std::vector toKeycodes = getIntArray(env, toKeyCodesArr); + if (fromKeycodes.size() != toKeycodes.size()) { + jniThrowRuntimeException(env, "FromKeycodes and toKeycodes cannot match."); + } NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode); + std::map keyRemapping; + for (int i = 0; i < fromKeycodes.size(); i++) { + keyRemapping.insert_or_assign(fromKeycodes[i], toKeycodes[i]); + } + im->setKeyRemapping(keyRemapping); } static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask, @@ -2459,7 +2516,7 @@ static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeKeyboardMicMute); } else { - ALOGW("Unknown light type %d", lightInfo.type); + ALOGW("Unknown light type %s", ftl::enum_string(lightInfo.type).c_str()); continue; } @@ -2693,12 +2750,13 @@ static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, } static void nativeSetKeyRepeatConfiguration(JNIEnv* env, jobject nativeImplObj, jint timeoutMs, - jint delayMs) { + jint delayMs, jboolean keyRepeatEnabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); im->getInputManager()->getDispatcher().setKeyRepeatConfiguration(std::chrono::milliseconds( timeoutMs), std::chrono::milliseconds( - delayMs)); + delayMs), + keyRepeatEnabled); } static jobject createInputSensorInfo(JNIEnv* env, jstring name, jstring vendor, jint version, @@ -2922,7 +2980,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState}, {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState}, {"getSwitchState", "(III)I", (void*)nativeGetSwitchState}, - {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping}, + {"setKeyRemapping", "([I[I)V", (void*)nativeSetKeyRemapping}, {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys}, {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation}, {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;", @@ -2997,7 +3055,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setDisplayEligibilityForPointerCapture", "(IZ)V", (void*)nativeSetDisplayEligibilityForPointerCapture}, {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled}, - {"setKeyRepeatConfiguration", "(II)V", (void*)nativeSetKeyRepeatConfiguration}, + {"setKeyRepeatConfiguration", "(IIZ)V", (void*)nativeSetKeyRepeatConfiguration}, {"getSensorList", "(I)[Landroid/hardware/input/InputSensorInfo;", (void*)nativeGetSensorList}, {"getTouchpadHardwareProperties", @@ -3068,6 +3126,9 @@ int register_android_server_InputManager(JNIEnv* env) { "notifyTouchpadHardwareState", "(Lcom/android/server/input/TouchpadHardwareState;I)V") + GET_METHOD_ID(gServiceClassInfo.notifyTouchpadGestureInfo, clazz, "notifyTouchpadGestureInfo", + "(II)V") + GET_METHOD_ID(gServiceClassInfo.notifySwitch, clazz, "notifySwitch", "(JII)V"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b6e45fc803f7c786b9a5fedb71673dcdb09f1eea..6314b8564c543735223b0e6d539590ae3fcf0f82 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -10625,8 +10625,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final DevicePolicyData policyData = getUserData(userId); if (transitionCheckNeeded) { // Optional state transition check for non-ADB case. - checkUserProvisioningStateTransition(policyData.mUserProvisioningState, - newState); + try { + checkUserProvisioningStateTransition( + policyData.mUserProvisioningState, + newState); + + } catch (IllegalStateException e) { + Slogf.e(LOG_TAG, + "Exception caught while changing provisioning state", e); + throw e; + } } policyData.mUserProvisioningState = newState; saveSettingsLocked(userId); @@ -10637,6 +10645,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void checkUserProvisioningStateTransition(int currentState, int newState) { + if (Flags.userProvisioningSameState()) { + Preconditions.checkState(newState != currentState, "New state cannot" + + " be the same as the current state: [" + newState + "]"); + } // Valid transitions for normal use-cases. switch (currentState) { case DevicePolicyManager.STATE_USER_UNMANAGED: diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 24ee46fbcd6ff79ac13247a575ac031e04a0aba9..f271162bbfa44fba9ca825bb4626f1dcec80df2e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -102,9 +102,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context), new BooleanPolicySerializer()); - // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual policy with the correct arguments (packageName and permission name) - // when reading the policies from xml. static final PolicyDefinition GENERIC_PERMISSION_GRANT = new PolicyDefinition<>( new PackagePermissionPolicyKey(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY), @@ -123,10 +120,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::setPermissionGrantState, new IntegerPolicySerializer()); - /** - * Passing in {@code null} for {@code packageName} or {@code permissionName} will return a - * {@link #GENERIC_PERMISSION_GRANT}. - */ static PolicyDefinition PERMISSION_GRANT( @NonNull String packageName, @NonNull String permissionName) { Objects.requireNonNull(packageName, "packageName must not be null"); @@ -170,9 +163,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::setUserControlDisabledPackages, new PackageSetPolicySerializer()); - // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual policy with the correct arguments (i.e. packageName) when reading the policies from - // xml. static PolicyDefinition GENERIC_PERSISTENT_PREFERRED_ACTIVITY = new PolicyDefinition<>( new IntentFilterPolicyKey( @@ -184,10 +174,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::addPersistentPreferredActivity, new ComponentNamePolicySerializer()); - /** - * Passing in {@code null} for {@code intentFilter} will return - * {@link #GENERIC_PERSISTENT_PREFERRED_ACTIVITY}. - */ static PolicyDefinition PERSISTENT_PREFERRED_ACTIVITY( @NonNull IntentFilter intentFilter) { Objects.requireNonNull(intentFilter, "intentFilter must not be null"); @@ -197,9 +183,6 @@ final class PolicyDefinition { intentFilter)); } - // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual policy with the correct arguments (i.e. packageName) when reading the policies from - // xml. static PolicyDefinition GENERIC_PACKAGE_UNINSTALL_BLOCKED = new PolicyDefinition<>( new PackagePolicyKey( @@ -209,10 +192,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::setUninstallBlocked, new BooleanPolicySerializer()); - /** - * Passing in {@code null} for {@code packageName} will return - * {@link #GENERIC_PACKAGE_UNINSTALL_BLOCKED}. - */ static PolicyDefinition PACKAGE_UNINSTALL_BLOCKED(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition( @@ -220,9 +199,6 @@ final class PolicyDefinition { DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName)); } - // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual policy with the correct arguments (i.e. packageName) when reading the policies from - // xml. static PolicyDefinition GENERIC_APPLICATION_RESTRICTIONS = new PolicyDefinition<>( new PackagePolicyKey( @@ -237,10 +213,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::setApplicationRestrictions, new BundlePolicySerializer()); - /** - * Passing in {@code null} for {@code packageName} will return - * {@link #GENERIC_APPLICATION_RESTRICTIONS}. - */ static PolicyDefinition APPLICATION_RESTRICTIONS(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition( @@ -266,9 +238,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::noOp, new IntegerPolicySerializer()); - // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual policy with the correct arguments (i.e. packageName) when reading the policies from - // xml. static PolicyDefinition GENERIC_APPLICATION_HIDDEN = new PolicyDefinition<>( new PackagePolicyKey( @@ -281,10 +250,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::setApplicationHidden, new BooleanPolicySerializer()); - /** - * Passing in {@code null} for {@code packageName} will return - * {@link #GENERIC_APPLICATION_HIDDEN}. - */ static PolicyDefinition APPLICATION_HIDDEN(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_APPLICATION_HIDDEN.createPolicyDefinition( @@ -292,9 +257,6 @@ final class PolicyDefinition { DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY, packageName)); } - // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual policy with the correct arguments (i.e. packageName) when reading the policies from - // xml. static PolicyDefinition GENERIC_ACCOUNT_MANAGEMENT_DISABLED = new PolicyDefinition<>( new AccountTypePolicyKey( @@ -305,10 +267,6 @@ final class PolicyDefinition { PolicyEnforcerCallbacks::noOp, new BooleanPolicySerializer()); - /** - * Passing in {@code null} for {@code accountType} will return - * {@link #GENERIC_ACCOUNT_MANAGEMENT_DISABLED}. - */ static PolicyDefinition ACCOUNT_MANAGEMENT_DISABLED(@NonNull String accountType) { Objects.requireNonNull(accountType, "accountType must not be null"); return GENERIC_ACCOUNT_MANAGEMENT_DISABLED.createPolicyDefinition( @@ -668,8 +626,6 @@ final class PolicyDefinition { throw new UnsupportedOperationException("Non-coexistable global policies not supported," + "please add support."); } - // TODO: maybe use this instead of manually adding to the map -// sPolicyDefinitions.put(policyDefinitionKey, this); } void saveToXml(TypedXmlSerializer serializer) throws IOException { diff --git a/services/foldables/devicestateprovider/TEST_MAPPING b/services/foldables/devicestateprovider/TEST_MAPPING index 47de131803c52c909a01665e50bc385a53cbd3b1..05383814a040150307acbfc58ce607570deebe64 100644 --- a/services/foldables/devicestateprovider/TEST_MAPPING +++ b/services/foldables/devicestateprovider/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "foldable-device-state-provider-tests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "foldable-device-state-provider-tests" } ] } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp index 1db9e8d545e4d25877df430c8bc03cadb61858f7..6393e11b7432b007b506e0923758a86a5e7ff783 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp @@ -1,7 +1,7 @@ aconfig_declarations { name: "device_state_flags", package: "com.android.server.policy.feature.flags", - container: "system_ext", + container: "system", srcs: [ "device_state_flags.aconfig", ], diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig index f827b5508015119c73f42cda39646f8815f77258..21e33dd1b99ac6b6a8d5a4ae23cccc981008ff18 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -1,5 +1,5 @@ package: "com.android.server.policy.feature.flags" -container: "system_ext" +container: "system" flag { name: "enable_dual_display_blocking" diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING index 4c9403c9b21abb1d0ff269df27d8cb2ad51ad603..cbb99627d9185a6744d4e64ecb175f42257ff3f4 100644 --- a/services/incremental/TEST_MAPPING +++ b/services/incremental/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsPackageManagerStatsHostTestCases", - "options": [ - { - "include-filter": "com.android.cts.packagemanager.stats.host.PackageInstallerV2StatsTests" - } - ] + "name": "CtsPackageManagerStatsHostTestCases_host_packageinstallerv2statstests" }, { "name": "CtsPackageManagerIncrementalStatsHostTestCases", diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ab459df1cdf6425f2c7a1eb81c9afe96f820487c..3b334ec69231a2f9aca045409c6653079f86622d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -107,7 +107,7 @@ import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.ProtoLog; -import com.android.internal.protolog.ProtoLogConfigurationService; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; @@ -436,6 +436,10 @@ public final class SystemServer implements Dumpable { private static final String PROFILING_SERVICE_JAR_PATH = "/apex/com.android.profiling/javalib/service-profiling.jar"; + private static final String RANGING_APEX_SERVICE_JAR_PATH = + "/apex/com.android.uwb/javalib/service-ranging.jar"; + private static final String RANGING_SERVICE_CLASS = "com.android.server.ranging.RangingService"; + private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; @@ -1097,7 +1101,7 @@ public final class SystemServer implements Dumpable { if (android.tracing.Flags.clientSideProtoLogging()) { t.traceBegin("StartProtoLogConfigurationService"); ServiceManager.addService( - Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService()); + Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationServiceImpl()); t.traceEnd(); } @@ -3015,6 +3019,17 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } + if (com.android.ranging.flags.Flags.rangingStackEnabled()) { + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB) + || context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WIFI_RTT)) { + t.traceBegin("RangingService"); + mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS, + RANGING_APEX_SERVICE_JAR_PATH); + t.traceEnd(); + } + } + t.traceBegin("StartBootPhaseDeviceSpecificServicesReady"); mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY); t.traceEnd(); diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml index a311d07e52fb58295872bdf1ac1abb53c3958f05..95da56da156cd0280dd3b842c473ee8714f6db32 100644 --- a/services/lint-baseline.xml +++ b/services/lint-baseline.xml @@ -1,5 +1,5 @@ - + @@ -19,7 +19,7 @@ errorLine2=" ^"> @@ -30,7 +30,7 @@ errorLine2=" ^"> @@ -41,7 +41,7 @@ errorLine2=" ^"> @@ -52,8 +52,30 @@ errorLine2=" ^"> - \ No newline at end of file + + + + + + + + + diff --git a/services/people/java/com/android/server/people/TEST_MAPPING b/services/people/java/com/android/server/people/TEST_MAPPING index 55b355cbc99195dcac411fabb50e48f619d7505b..86773375496763418ea2f52e4e48b3a14e204638 100644 --- a/services/people/java/com/android/server/people/TEST_MAPPING +++ b/services/people/java/com/android/server/people/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.people.data" - } - ] + "name": "FrameworksServicesTests_people_data" } ] } \ No newline at end of file diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING index 4de4a56aa806518577c9ff19ec41743c6c39c6aa..af4aaf9736d89d2dc30da9fe17a23bb8d702f767 100644 --- a/services/permission/TEST_MAPPING +++ b/services/permission/TEST_MAPPING @@ -105,26 +105,10 @@ ] }, { - "name": "CtsVirtualDevicesAudioTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest" - } - ] + "name": "CtsVirtualDevicesAudioTestCases_audio_virtualaudiopermissiontest" }, { - "name": "CtsVirtualDevicesAppLaunchTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest" - } - ] + "name": "CtsVirtualDevicesAppLaunchTestCases_applaunch_virtualdevicepermissiontest" } ], "imports": [ diff --git a/services/print/java/com/android/server/print/TEST_MAPPING b/services/print/java/com/android/server/print/TEST_MAPPING index 4fa882265e53a3f3d1184885e56adadc13ed0684..1033b1a86edb5693b8fc08e4447ec3a0c207f4a0 100644 --- a/services/print/java/com/android/server/print/TEST_MAPPING +++ b/services/print/java/com/android/server/print/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsPrintTestCases", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - } - ] + "name": "CtsPrintTestCases_Presubmit" } ] } diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING index de9f771a2a3620b78d9ee177114c55c1baee97e4..7313941f57b407f6b9e65f23be12ed5020e391d1 100644 --- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING +++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING @@ -1,22 +1,12 @@ { "presubmit": [ { - "name": "FrameworksInputMethodSystemServerTests", - "options": [ - {"include-filter": "com.android.server.inputmethod"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "FrameworksInputMethodSystemServerTests_server_inputmethod" } ], "postsubmit": [ { - "name": "FrameworksImeTests", - "options": [ - {"include-filter": "com.android.inputmethodservice"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "FrameworksImeTests_android_inputmethodservice" } ] } diff --git a/services/tests/PackageManagerServiceTests/TEST_MAPPING b/services/tests/PackageManagerServiceTests/TEST_MAPPING index 5d96af9df1fbd06540d542ab9b0964170817e454..13ba3171e4558e33bc44cd160c6c21885b747d34 100644 --- a/services/tests/PackageManagerServiceTests/TEST_MAPPING +++ b/services/tests/PackageManagerServiceTests/TEST_MAPPING @@ -4,21 +4,7 @@ "name": "AppEnumerationInternalTests" }, { - "name": "PackageManagerServiceServerTests", - "options": [ - { - "include-filter": "com.android.server.pm." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "PackageManagerServiceServerTests_server_pm_Presubmit" } ], "postsubmit": [ @@ -26,21 +12,7 @@ "name": "PackageManagerServiceHostTests" }, { - "name": "PackageManagerServiceServerTests", - "options": [ - { - "include-filter": "com.android.server.pm." - }, - { - "include-annotation": "android.platform.test.annotations.Postsubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "PackageManagerServiceServerTests_server_pm_Postsubmit" } ], "kernel-presubmit": [ diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt index 650e520ebb7d4f23d4d2615d8d79e6088c492bb5..da3e94f64e569d4a88dd1ad579cbedfd1227552b 100644 --- a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt +++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt @@ -101,4 +101,39 @@ class AppFunctionRuntimeMetadataTest { assertThat(actualPackageName).isEqualTo(expectedPackageName) } + + @Test + fun testBuild() { + val runtimeMetadata = AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").build() + + assertThat(runtimeMetadata.packageName).isEqualTo("com.pkg") + assertThat(runtimeMetadata.functionId).isEqualTo("funcId") + assertThat(runtimeMetadata.enabled).isNull() + assertThat(runtimeMetadata.appFunctionStaticMetadataQualifiedId) + .isEqualTo("android\$apps-db/app_functions#com.pkg/funcId") + } + + @Test + fun setEnabled_true() { + val runtimeMetadata = + AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(true).build() + + assertThat(runtimeMetadata.enabled).isTrue() + } + + @Test + fun setEnabled_false() { + val runtimeMetadata = + AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(false).build() + + assertThat(runtimeMetadata.enabled).isFalse() + } + + @Test + fun setEnabled_null() { + val runtimeMetadata = + AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(null).build() + + assertThat(runtimeMetadata.enabled).isNull() + } } diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt index edcbb9e81d87be60212059829aae7dc798ce8b36..e761e8d82c920e39bf7871e57dca6c52d8cec846 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt @@ -84,7 +84,7 @@ class FutureAppSearchSessionTest { val schema = session.setSchema(setSchemaRequest) assertThat(schema.get()).isNotNull() val appFunctionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder() .addGenericDocuments(appFunctionRuntimeMetadata) @@ -110,7 +110,7 @@ class FutureAppSearchSessionTest { val schema = session.setSchema(setSchemaRequest) assertThat(schema.get()).isNotNull() val appFunctionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder() .addGenericDocuments(appFunctionRuntimeMetadata) @@ -144,7 +144,7 @@ class FutureAppSearchSessionTest { val schema = session.setSchema(setSchemaRequest) assertThat(schema.get()).isNotNull() val appFunctionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder() .addGenericDocuments(appFunctionRuntimeMetadata) @@ -175,7 +175,7 @@ class FutureAppSearchSessionTest { .build() session.setSchema(setSchemaRequest).get() val appFunctionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID).build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder() .addGenericDocuments(appFunctionRuntimeMetadata) diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt index 38cba6537c956cd74e8f2d24d987567f5f14be7b..7fe726346a77bea5f7fba61ba632e3c2271ade4c 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt @@ -94,8 +94,7 @@ class FutureGlobalSearchSessionTest { val schema = session.setSchema(setSchemaRequest) assertThat(schema.get()).isNotNull() val appFunctionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, TEST_FUNCTION_ID, "") - .build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, TEST_FUNCTION_ID).build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder() .addGenericDocuments(appFunctionRuntimeMetadata) diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt index 6930b3c0699b51eb71504cbeb3621d73cd07915a..c05c3819ca2882b3318cd5b7cf56348cd9db69c8 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt @@ -53,7 +53,7 @@ class MetadataSyncAdapterTest { fun getPackageToFunctionIdMap() { val searchSession = FakeSearchSession() val functionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() searchSession.put(putDocumentsRequest).get() @@ -74,13 +74,13 @@ class MetadataSyncAdapterTest { fun getPackageToFunctionIdMap_multipleDocuments() { val searchSession = FakeSearchSession() val functionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build() val functionRuntimeMetadata1 = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId1", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId1").build() val functionRuntimeMetadata2 = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId2", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId2").build() val functionRuntimeMetadata3 = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId3", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId3").build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder() .addGenericDocuments( @@ -133,22 +133,21 @@ class MetadataSyncAdapterTest { val runtimeSearchSession = FakeSearchSession() val staticSearchSession = FakeSearchSession() val functionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() runtimeSearchSession.put(putDocumentsRequest).get() staticSearchSession.put(putDocumentsRequest).get() val metadataSyncAdapter = - MetadataSyncAdapter( - testExecutor, - runtimeSearchSession, + MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + + val submitSyncRequest = + metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( staticSearchSession, - packageManager, + runtimeSearchSession, ) - val submitSyncRequest = metadataSyncAdapter.submitSyncRequest() - - assertThat(submitSyncRequest.get()).isTrue() + assertThat(submitSyncRequest).isInstanceOf(Unit::class.java) } @Test @@ -177,21 +176,20 @@ class MetadataSyncAdapterTest { val runtimeSearchSession = FakeSearchSession() val staticSearchSession = FakeSearchSession() val functionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() staticSearchSession.put(putDocumentsRequest).get() val metadataSyncAdapter = - MetadataSyncAdapter( - testExecutor, - runtimeSearchSession, + MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + + val submitSyncRequest = + metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( staticSearchSession, - packageManager, + runtimeSearchSession, ) - val submitSyncRequest = metadataSyncAdapter.submitSyncRequest() - - assertThat(submitSyncRequest.get()).isTrue() + assertThat(submitSyncRequest).isInstanceOf(Unit::class.java) } @Test @@ -234,21 +232,20 @@ class MetadataSyncAdapterTest { val runtimeSearchSession = FakeSearchSession() val staticSearchSession = FakeSearchSession() val functionRuntimeMetadata = - AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build() + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId").build() val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() runtimeSearchSession.put(putDocumentsRequest).get() val metadataSyncAdapter = - MetadataSyncAdapter( - testExecutor, - runtimeSearchSession, + MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + + val submitSyncRequest = + metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( staticSearchSession, - packageManager, + runtimeSearchSession, ) - val submitSyncRequest = metadataSyncAdapter.submitSyncRequest() - - assertThat(submitSyncRequest.get()).isTrue() + assertThat(submitSyncRequest).isInstanceOf(Unit::class.java) } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 8b80f85aec000bce8d1a1c99173230e9eaa00c63..255dcb0835189e66b71e21ffa74638795c376098 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -27,6 +27,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_D import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; +import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT; @@ -195,8 +196,8 @@ public class DisplayManagerServiceTest { private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS = STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED; @@ -238,6 +239,8 @@ public class DisplayManagerServiceTest { private UserManager mUserManager; + private int[] mAllowedHdrOutputTypes; + private final DisplayManagerService.Injector mShortMockedInjector = new DisplayManagerService.Injector() { @Override @@ -256,11 +259,12 @@ public class DisplayManagerServiceTest { displayAdapterListener, flags, mMockedDisplayNotificationManager, new LocalDisplayAdapter.Injector() { - @Override - public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { - return mSurfaceControlProxy; - } - }); + @Override + public LocalDisplayAdapter.SurfaceControlProxy + getSurfaceControlProxy() { + return mSurfaceControlProxy; + } + }); } @Override @@ -320,7 +324,7 @@ public class DisplayManagerServiceTest { @Override int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, - int[] autoHdrTypes) { + int[] allowedHdrOutputTypes) { mHdrConversionMode = conversionMode; mPreferredHdrOutputType = preferredHdrOutputType; return Display.HdrCapabilities.HDR_TYPE_INVALID; @@ -1295,11 +1299,11 @@ public class DisplayManagerServiceTest { .setUniqueId("uniqueId --- mirror display"); assertThrows(SecurityException.class, () -> { localService.createVirtualDisplay( - builder.build(), - mMockAppToken /* callback */, - null /* virtualDeviceToken */, - mock(DisplayWindowPolicyController.class), - PACKAGE_NAME); + builder.build(), + mMockAppToken /* callback */, + null /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); }); } @@ -1433,7 +1437,7 @@ public class DisplayManagerServiceTest { // The virtual display should not have FLAG_ALWAYS_UNLOCKED set. assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags - & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED)); + & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED)); } /** @@ -1466,7 +1470,7 @@ public class DisplayManagerServiceTest { // The virtual display should not have FLAG_PRESENTATION set. assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags - & DisplayDeviceInfo.FLAG_PRESENTATION)); + & DisplayDeviceInfo.FLAG_PRESENTATION)); } @Test @@ -2358,6 +2362,7 @@ public class DisplayManagerServiceTest { HdrConversionMode.HDR_CONVERSION_FORCE, Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION); displayManager.setHdrConversionModeInternal(mode); + assertEquals(mode, displayManager.getHdrConversionModeSettingInternal()); assertEquals(mode.getConversionMode(), mHdrConversionMode); assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType); @@ -2401,6 +2406,86 @@ public class DisplayManagerServiceTest { assertEquals(mode, displayManager.getHdrConversionModeInternal()); } + @Test + public void testSetAreUserDisabledHdrTypesAllowed_withFalse_whenHdrDisabled_stripsHdrType() { + DisplayManagerService displayManager = new DisplayManagerService( + mContext, new BasicInjector() { + @Override + int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, + int[] allowedTypes) { + mAllowedHdrOutputTypes = allowedTypes; + return Display.HdrCapabilities.HDR_TYPE_INVALID; + } + + // Overriding this method to capture the allowed HDR type + @Override + int[] getSupportedHdrOutputTypes() { + return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}; + } + }); + + // Setup: no HDR types disabled, userDisabledTypes allowed, system conversion + displayManager.setUserDisabledHdrTypesInternal(new int [0]); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(true); + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + + assertEquals(1, mAllowedHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]); + + // Action: disable Dolby Vision, set userDisabledTypes not allowed + displayManager.setUserDisabledHdrTypesInternal( + new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(false); + + assertEquals(0, mAllowedHdrOutputTypes.length); + } + + @Test + public void testGetEnabledHdrTypesLocked_whenTypesDisabled_stripsDisabledTypes() { + DisplayManagerService displayManager = new DisplayManagerService( + mContext, new BasicInjector() { + @Override + int[] getSupportedHdrOutputTypes() { + return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}; + } + }); + + displayManager.setUserDisabledHdrTypesInternal(new int [0]); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(true); + int [] enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(1, enabledHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]); + + displayManager.setAreUserDisabledHdrTypesAllowedInternal(false); + enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(1, enabledHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]); + + displayManager.setUserDisabledHdrTypesInternal( + new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}); + enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(0, enabledHdrOutputTypes.length); + } + + @Test + public void testSetHdrConversionModeInternal_isForceSdrIsUpdated() { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); + FakeDisplayDevice displayDevice = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL); + LogicalDisplay logicalDisplay = + logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true); + + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID)); + assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr); + + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr); + } + @Test public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); @@ -3505,7 +3590,7 @@ public class DisplayManagerServiceTest { } private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager, - Display.Mode[] modes) { + Display.Mode[] modes) { FakeDisplayDevice displayDevice = new FakeDisplayDevice(); DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); displayDeviceInfo.supportedModes = modes; @@ -3761,9 +3846,9 @@ public class DisplayManagerServiceTest { public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) { for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) { if (mode.matchesIfValid( - preferredMode.getPhysicalWidth(), - preferredMode.getPhysicalHeight(), - preferredMode.getRefreshRate())) { + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getRefreshRate())) { mPreferredMode = mode; break; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java deleted file mode 100644 index 306b4f86024e17cb51586cae61537d44b758515d..0000000000000000000000000000000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java +++ /dev/null @@ -1,126 +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.server.display.brightness.clamper; - -import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_OFF; -import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_ON; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.verify; - -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.provider.Settings; -import android.testing.TestableContext; - -import androidx.annotation.NonNull; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.android.internal.display.BrightnessSynchronizer; -import com.android.server.testutils.TestHandler; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class BrightnessWearBedtimeModeClamperTest { - - private static final float BRIGHTNESS_CAP = 0.3f; - - @Mock - private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; - - @Rule - public final TestableContext mContext = new TestableContext( - InstrumentationRegistry.getInstrumentation().getContext()); - - private final TestHandler mTestHandler = new TestHandler(null); - private final TestInjector mInjector = new TestInjector(); - private BrightnessWearBedtimeModeClamper mClamper; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mClamper = new BrightnessWearBedtimeModeClamper(mInjector, mTestHandler, mContext, - mMockClamperChangeListener, () -> BRIGHTNESS_CAP); - mTestHandler.flush(); - } - - @Test - public void testBrightnessCap() { - assertEquals(BRIGHTNESS_CAP, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON); - } - - @Test - public void testBedtimeModeOn() { - setBedtimeModeEnabled(true); - assertTrue(mClamper.isActive()); - verify(mMockClamperChangeListener).onChanged(); - } - - @Test - public void testBedtimeModeOff() { - setBedtimeModeEnabled(false); - assertFalse(mClamper.isActive()); - verify(mMockClamperChangeListener).onChanged(); - } - - @Test - public void testType() { - assertEquals(BrightnessClamper.Type.WEAR_BEDTIME_MODE, mClamper.getType()); - } - - @Test - public void testOnDisplayChanged() { - float newBrightnessCap = 0.61f; - - mClamper.onDisplayChanged(() -> newBrightnessCap); - mTestHandler.flush(); - - assertEquals(newBrightnessCap, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON); - verify(mMockClamperChangeListener).onChanged(); - } - - private void setBedtimeModeEnabled(boolean enabled) { - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE, - enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF); - mInjector.notifyBedtimeModeChanged(); - mTestHandler.flush(); - } - - private static class TestInjector extends BrightnessWearBedtimeModeClamper.Injector { - - private ContentObserver mObserver; - - @Override - void registerBedtimeModeObserver(@NonNull ContentResolver cr, - @NonNull ContentObserver observer) { - mObserver = observer; - } - - private void notifyBedtimeModeChanged() { - if (mObserver != null) { - mObserver.dispatchChange(/* selfChange= */ false, - Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE)); - } - } - } -} diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8271a0f44fc6ab009bd9c0789be0febf7a98962c --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java @@ -0,0 +1,180 @@ +/* + * 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.server.display.brightness.clamper; + +import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeModifier.BEDTIME_MODE_OFF; +import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeModifier.BEDTIME_MODE_ON; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; +import android.os.IBinder; +import android.os.PowerManager; +import android.provider.Settings; +import android.testing.TestableContext; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.display.BrightnessSynchronizer; +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class BrightnessWearBedtimeModeModifierTest { + private static final int NO_MODIFIER = 0; + private static final float BRIGHTNESS_CAP = 0.3f; + private static final String DISPLAY_ID = "displayId"; + + @Mock + private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; + @Mock + private DisplayManagerInternal.DisplayPowerRequest mMockRequest; + @Mock + private DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock + private IBinder mMockBinder; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + private final TestHandler mTestHandler = new TestHandler(null); + private final TestInjector mInjector = new TestInjector(); + private BrightnessWearBedtimeModeModifier mModifier; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mModifier = new BrightnessWearBedtimeModeModifier(mInjector, mTestHandler, mContext, + mMockClamperChangeListener, () -> BRIGHTNESS_CAP); + mTestHandler.flush(); + } + + @Test + public void testBedtimeModeOff() { + setBedtimeModeEnabled(false); + assertModifierState( + 0.5f, true, + PowerManager.BRIGHTNESS_MAX, 0.5f, + false, true); + verify(mMockClamperChangeListener).onChanged(); + } + + @Test + public void testBedtimeModeOn() { + setBedtimeModeEnabled(true); + assertModifierState( + 0.5f, true, + BRIGHTNESS_CAP, BRIGHTNESS_CAP, + true, false); + verify(mMockClamperChangeListener).onChanged(); + } + + @Test + public void testOnDisplayChanged() { + setBedtimeModeEnabled(true); + clearInvocations(mMockClamperChangeListener); + float newBrightnessCap = 0.61f; + onDisplayChange(newBrightnessCap); + mTestHandler.flush(); + + assertModifierState( + 0.5f, true, + newBrightnessCap, 0.5f, + true, false); + verify(mMockClamperChangeListener).onChanged(); + } + + private void setBedtimeModeEnabled(boolean enabled) { + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE, + enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF); + mInjector.notifyBedtimeModeChanged(); + mTestHandler.flush(); + } + + private void onDisplayChange(float brightnessCap) { + when(mMockDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode()) + .thenReturn(brightnessCap); + mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData( + mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID)); + } + + private void assertModifierState( + float currentBrightness, + boolean currentSlowChange, + float maxBrightness, float brightness, + boolean isActive, + boolean isSlowChange) { + ModifiersAggregatedState modifierState = new ModifiersAggregatedState(); + DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder(); + stateBuilder.setBrightness(currentBrightness); + stateBuilder.setIsSlowChange(currentSlowChange); + + int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE + : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER; + + mModifier.applyStateChange(modifierState); + assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness); + assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason); + + mModifier.apply(mMockRequest, stateBuilder); + + assertThat(stateBuilder.getMaxBrightness()) + .isWithin(BrightnessSynchronizer.EPSILON).of(maxBrightness); + assertThat(stateBuilder.getBrightness()) + .isWithin(BrightnessSynchronizer.EPSILON).of(brightness); + assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason); + assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier); + assertThat(stateBuilder.isSlowChange()).isEqualTo(isSlowChange); + } + + + private static class TestInjector extends BrightnessWearBedtimeModeModifier.Injector { + + private ContentObserver mObserver; + + @Override + void registerBedtimeModeObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + mObserver = observer; + } + + private void notifyBedtimeModeChanged() { + if (mObserver != null) { + mObserver.dispatchChange(/* selfChange= */ false, + Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE)); + } + } + } +} diff --git a/services/tests/dreamservicetests/TEST_MAPPING b/services/tests/dreamservicetests/TEST_MAPPING index a644ea690dcd62abf3261274b26c708755cc4cfc..38d7000ceb6e8382d27880ae7c24e9e9115ddb13 100644 --- a/services/tests/dreamservicetests/TEST_MAPPING +++ b/services/tests/dreamservicetests/TEST_MAPPING @@ -1,21 +1,12 @@ { "presubmit": [ { - "name": "DreamServiceTests", - "options": [ - {"include-filter": "com.android.server.dreams"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "DreamServiceTests_server_dreams" } ], "postsubmit": [ { - "name": "DreamServiceTests", - "options": [ - {"include-filter": "com.android.server.dreams"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "DreamServiceTests_server_dreams" } ] } diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java index 8bdfc500fdc2114d44a17c906151739dc334c39b..121145672d68a4e84392860852bea6d8775d6919 100644 --- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java @@ -346,14 +346,16 @@ public class AudioManagerRouteControllerTest { .thenReturn(mAvailableAudioDeviceInfos.toArray(new AudioDeviceInfo[0])); } - private static AudioDeviceAttributes createAudioDeviceAttribute(int type) { + private static AudioDeviceAttributes createAudioDeviceAttribute( + @AudioDeviceInfo.AudioDeviceType int type) { // Address is unused. return new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, type, /* address= */ ""); } private static AudioDeviceInfo createAudioDeviceInfo( - int type, @NonNull String name, @NonNull String address) { + @AudioDeviceInfo.AudioDeviceType int type, @NonNull String name, + @NonNull String address) { return new AudioDeviceInfo(AudioDevicePort.createForTesting(type, name, address)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index 8b653378664e09de99ce6cade014206781c19049..32135f1cb7fa1770b330c975118921b42962a5c2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -22,9 +22,10 @@ import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PRO import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -36,6 +37,7 @@ import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Process; +import android.os.RemoteException; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -137,9 +139,17 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener = spy(mSensitiveContentProtectionManagerService.mNotificationListener); - doCallRealMethod() - .when(mSensitiveContentProtectionManagerService.mNotificationListener) - .onListenerConnected(); + + // Unexpected NLS interactions when registered cause test flakes. For purposes of this test, + // the test will control any NLS calls. + try { + doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener) + .registerAsSystemService(any(), any(), anyInt()); + doNothing().when(mSensitiveContentProtectionManagerService.mNotificationListener) + .unregisterAsSystemService(); + } catch (RemoteException e) { + // Intra-process call, should never happen. + } // Setup RankingMap and two possilbe rankings when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java index 3dd2f24aa4e41d0ca40b1d5bc886f90f210ce018..1cad255b85d77b12d77552c4141f75bec4fea831 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java @@ -509,8 +509,12 @@ public class ApplicationStartInfoTest { mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT), false /* isAlarm */); + // Add a brief delay between timestamps to make sure the clock, which is in milliseconds has + // actually incremented. + sleep(1); mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT), false /* isAlarm */); + sleep(1); mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT), false /* isAlarm */); @@ -557,9 +561,10 @@ public class ApplicationStartInfoTest { // Now load from disk. mAppStartInfoTracker.loadExistingProcessStartInfo(); - // Confirm clock has been set and that its current time is greater than the previous one. + // Confirm clock has been set and that its current time is greater than or equal to the + // previous one, thereby ensuring it was loaded from disk. assertNotNull(mAppStartInfoTracker.mMonotonicClock); - assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime); + assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime); } private static void setFieldValue(Class clazz, Object obj, String fieldName, T val) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 5ec53023dc6720b22eef7ca8f501bbdf39ef5b68..2107406f9b135da88d2c00e0d104be766b3ceecc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -62,6 +62,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; +import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND; @@ -228,8 +229,10 @@ public class MockingOomAdjusterTests { doCallRealMethod().when(mService).enqueueOomAdjTargetLocked(any(ProcessRecord.class)); doCallRealMethod().when(mService).updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_ACTIVITY); setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object()); - doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr) - .enqueueProcessChangeItemLocked(anyInt(), anyInt()); + doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(), + anyInt()); + doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(), + anyBoolean()); mService.mOomAdjuster = mService.mConstants.ENABLE_NEW_OOMADJ ? new OomAdjusterModernImpl(mService, mService.mProcessList, new ActiveUids(mService, false), mInjector) @@ -534,6 +537,14 @@ public class MockingOomAdjusterTests { updateOomAdj(app); assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP); assertEquals("perceptible-freeform-activity", app.mState.getAdjType()); + + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE + | WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) + .when(wpc).getActivityStateFlags(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, + SCHED_GROUP_FOREGROUND_WINDOW); + assertEquals("vis-multi-window-activity", app.mState.getAdjType()); } @SuppressWarnings("GuardedBy") diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java index a82658b52a7733acd67d56e48e779634bf37877f..9ba272446689b656e01e3489c453b3c82d63d663 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java @@ -16,13 +16,19 @@ package com.android.server.pm; +import static android.media.AudioAttributes.USAGE_ALARM; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.testng.AssertJUnit.assertEquals; import android.app.Notification; import android.app.NotificationManager; @@ -31,6 +37,9 @@ import android.content.pm.UserInfo; import android.media.AudioAttributes; import android.media.AudioFocusInfo; import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.PlayerProxy; +import android.media.audiopolicy.AudioPolicy; import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; @@ -45,6 +54,10 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + @RunWith(JUnit4.class) public class BackgroundUserSoundNotifierTest { @@ -63,7 +76,10 @@ public class BackgroundUserSoundNotifierTest { MockitoAnnotations.initMocks(this); mSpiedContext = spy(mRealContext); mUsersToRemove = new ArraySet<>(); - mUserManager = UserManager.get(mRealContext); + + mUserManager = spy(mSpiedContext.getSystemService(UserManager.class)); + doReturn(mUserManager) + .when(mSpiedContext).getSystemService(UserManager.class); doReturn(mNotificationManager) .when(mSpiedContext).getSystemService(NotificationManager.class); mBackgroundUserSoundNotifier = new BackgroundUserSoundNotifier(mSpiedContext); @@ -74,12 +90,9 @@ public class BackgroundUserSoundNotifierTest { mUsersToRemove.stream().toList().forEach(this::removeUser); } @Test - public void testAlarmOnBackgroundUser_ForegroundUserNotified() throws RemoteException { - AudioAttributes aa = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ALARM).build(); - UserInfo user = createUser("User", - UserManager.USER_TYPE_FULL_SECONDARY, - 0); + public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException { + AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); + UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0); final int fgUserId = mSpiedContext.getUserId(); final int bgUserUid = user.id * 100000; doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser(); @@ -95,10 +108,9 @@ public class BackgroundUserSoundNotifierTest { } @Test - public void testMediaOnBackgroundUser_ForegroundUserNotNotified() throws RemoteException { + public void testMediaOnBackgroundUser_foregroundUserNotNotified() throws RemoteException { AudioAttributes aa = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA).build(); - UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0); final int bgUserUid = mSpiedContext.getUserId() * 100000; AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN, @@ -109,9 +121,9 @@ public class BackgroundUserSoundNotifierTest { } @Test - public void testAlarmOnForegroundUser_ForegroundUserNotNotified() throws RemoteException { + public void testAlarmOnForegroundUser_foregroundUserNotNotified() throws RemoteException { AudioAttributes aa = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ALARM).build(); + .setUsage(USAGE_ALARM).build(); final int fgUserId = mSpiedContext.getUserId(); final int fgUserUid = fgUserId * 100000; doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser(); @@ -123,6 +135,110 @@ public class BackgroundUserSoundNotifierTest { verifyZeroInteractions(mNotificationManager); } + @Test + public void testMuteAlarmSounds() { + final int fgUserId = mSpiedContext.getUserId(); + int bgUserId = fgUserId + 1; + int bgUserUid = bgUserId * 100000; + mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid; + + AudioManager mockAudioManager = mock(AudioManager.class); + when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager); + + AudioPlaybackConfiguration apc1 = mock(AudioPlaybackConfiguration.class); + when(apc1.getClientUid()).thenReturn(bgUserUid); + when(apc1.getPlayerProxy()).thenReturn(mock(PlayerProxy.class)); + + AudioPlaybackConfiguration apc2 = mock(AudioPlaybackConfiguration.class); + when(apc2.getClientUid()).thenReturn(bgUserUid + 1); + when(apc2.getPlayerProxy()).thenReturn(mock(PlayerProxy.class)); + + List configs = new ArrayList<>(); + configs.add(apc1); + configs.add(apc2); + when(mockAudioManager.getActivePlaybackConfigurations()).thenReturn(configs); + + AudioPolicy mockAudioPolicy = mock(AudioPolicy.class); + + AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); + AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", /* packageName= */ "", + AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, + Build.VERSION.SDK_INT); + Stack focusStack = new Stack<>(); + focusStack.add(afi); + doReturn(focusStack).when(mockAudioPolicy).getFocusStack(); + mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy; + + mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext); + + verify(apc1.getPlayerProxy()).stop(); + verify(mockAudioPolicy).sendFocusLossAndUpdate(afi); + verify(apc2.getPlayerProxy(), never()).stop(); + } + + @Test + public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() { + final int fgUserId = mSpiedContext.getUserId(); + UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0); + int bgUserUid = bgUser.id * 100000; + + AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); + AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", "", + AudioManager.AUDIOFOCUS_GAIN, 0, 0, Build.VERSION.SDK_INT); + + mBackgroundUserSoundNotifier.getAudioPolicyFocusListener() + .onAudioFocusGrant(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + + verify(mNotificationManager) + .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()), + eq(afi.getClientUid()), any(Notification.class), + eq(UserHandle.of(fgUserId))); + } + + + @Test + public void testCreateNotification_UserSwitcherEnabled_bothActionsAvailable() { + String userName = "BgUser"; + + doReturn(true).when(mUserManager).isUserSwitcherEnabled(); + doReturn(UserManager.SWITCHABILITY_STATUS_OK) + .when(mUserManager).getUserSwitchability(any()); + + Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, + mSpiedContext); + + assertEquals("Alarm for BgUser", notification.extras.getString( + Notification.EXTRA_TITLE)); + assertEquals(Notification.CATEGORY_REMINDER, notification.category); + assertEquals(Notification.VISIBILITY_PUBLIC, notification.visibility); + assertEquals(com.android.internal.R.drawable.ic_audio_alarm, + notification.getSmallIcon().getResId()); + + assertEquals(2, notification.actions.length); + assertEquals(mSpiedContext.getString( + com.android.internal.R.string.bg_user_sound_notification_button_mute), + notification.actions[0].title); + assertEquals(mSpiedContext.getString( + com.android.internal.R.string.bg_user_sound_notification_button_switch_user), + notification.actions[1].title); + } + + @Test + public void testCreateNotification_UserSwitcherDisabled_onlyMuteActionAvailable() { + String userName = "BgUser"; + + doReturn(false).when(mUserManager).isUserSwitcherEnabled(); + doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED) + .when(mUserManager).getUserSwitchability(any()); + + Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, + mSpiedContext); + + assertEquals(1, notification.actions.length); + assertEquals(mSpiedContext.getString( + com.android.internal.R.string.bg_user_sound_notification_button_mute), + notification.actions[0].title); + } private UserInfo createUser(String name, String userType, int flags) { UserInfo user = mUserManager.createUser(name, userType, flags); diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING index 4ac4484956fc31683a7d5aad6758a1dd29f1bcbc..ef2d60530a73a500b12680e101ad14eea4178a0f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "RollbackPackageHealthObserverTests", - "options": [ - { - "include-filter": "com.android.server.rollback" - } - ] + "name": "RollbackPackageHealthObserverTests_server_rollback" } ] } \ No newline at end of file diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java index 1a398c5f1ec3d6f64df628b770dc713b3b012823..e0c393cada49c0d083c936f7aaa99a783439aa3d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java @@ -100,6 +100,7 @@ import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; +import com.android.server.pm.UserManagerInternal; import org.junit.After; import org.junit.Before; @@ -145,6 +146,7 @@ public class TrustManagerServiceTest { private static final String URI_SCHEME_PACKAGE = "package"; private static final int TEST_USER_ID = 50; + private static final int TEST_VISIBLE_BACKGROUND_USER_ID = 51; private static final UserInfo TEST_USER = new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL); private static final int PARENT_USER_ID = 60; @@ -170,6 +172,7 @@ public class TrustManagerServiceTest { private @Mock KeyStoreAuthorization mKeyStoreAuthorization; private @Mock LockPatternUtils mLockPatternUtils; private @Mock LockSettingsInternal mLockSettingsInternal; + private @Mock UserManagerInternal mUserManagerInternal; private @Mock PackageManager mPackageManager; private @Mock UserManager mUserManager; private @Mock IWindowManager mWindowManager; @@ -224,6 +227,7 @@ public class TrustManagerServiceTest { when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER)); when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]); when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER); + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(false); when(mWindowManager.isKeyguardLocked()).thenReturn(true); @@ -593,6 +597,54 @@ public class TrustManagerServiceTest { verify(mTrustListener, never()).onTrustManagedChanged(anyBoolean(), anyInt()); } + @Test + public void testDeviceLocked_visibleBackgroundUser_userLocked() throws RemoteException { + setupVisibleBackgroundUser(/* visible= */ true, /* unlocked= */ false); + mService.waitForIdle(); + mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID); + mService.waitForIdle(); + assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isTrue(); + } + + @Test + public void testDeviceLocked_visibleBackgroundUser_userUnlocked() throws RemoteException { + setupVisibleBackgroundUser(/* visible= */ true, /* unlocked= */ true); + mService.waitForIdle(); + mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID); + mService.waitForIdle(); + assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isFalse(); + } + + @Test + public void testDeviceLocked_invisibleBackgroundUser_userUnlocked() throws RemoteException { + setupVisibleBackgroundUser(/* visible= */ false, /* unlocked= */ true); + mService.waitForIdle(); + mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID); + mService.waitForIdle(); + assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isTrue(); + } + + private void setupVisibleBackgroundUser(boolean visible, boolean unlocked) { + UserInfo info = new UserInfo(TEST_VISIBLE_BACKGROUND_USER_ID, "visible bg user", + UserInfo.FLAG_FULL); + + when(mActivityManager.isUserRunning(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(true); + + when(mLockPatternUtils.isSecure(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(true); + + when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER, info)); + when(mUserManager.getEnabledProfileIds(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn( + new int[0]); + when(mUserManager.getUserInfo(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(info); + when(mUserManager.isUserUnlocked(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(unlocked); + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true); + + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mUserManagerInternal); + when(mUserManagerInternal.isVisibleBackgroundFullUser( + TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(visible); + } + private void setUpRenewableTrust(ITrustAgentService trustAgent) throws RemoteException { ITrustAgentServiceCallback callback = getCallback(trustAgent); callback.setManagingTrust(true); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 15ae4634b5738ab8dfe7ab80c71f12655bcb1af6..9983fb4748a59d2e137ecb73f092a7a1704f8db8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -32,6 +32,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -50,6 +52,7 @@ import static org.mockito.Mockito.verify; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.Flags; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.ComponentName; @@ -64,7 +67,10 @@ import android.hardware.display.DisplayManager; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.WallpaperService; @@ -91,8 +97,10 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.RuleChain; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -100,6 +108,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.quality.Strictness; import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -125,6 +134,7 @@ public class WallpaperManagerServiceTests { @ClassRule public static final TestableContext sContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + private static ComponentName sImageWallpaperComponentName; private static ComponentName sDefaultWallpaperComponent; @@ -133,8 +143,11 @@ public class WallpaperManagerServiceTests { @Mock private DisplayManager mDisplayManager; + private final TemporaryFolder mFolder = new TemporaryFolder(); + private final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule - public final TemporaryFolder mFolder = new TemporaryFolder(); + public RuleChain rules = RuleChain.outerRule(mSetFlagsRule).around(mFolder); + private final SparseArray mTempDirs = new SparseArray<>(); private WallpaperManagerService mService; private static IWallpaperConnection.Stub sWallpaperService; @@ -290,7 +303,7 @@ public class WallpaperManagerServiceTests { final WallpaperData fallbackData = mService.mFallbackWallpaper; assertEquals("Fallback wallpaper component should be ImageWallpaper.", - sImageWallpaperComponentName, fallbackData.wallpaperComponent); + sImageWallpaperComponentName, fallbackData.getComponent()); verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent); verifyDisplayData(); @@ -325,6 +338,7 @@ public class WallpaperManagerServiceTests { * is issued to the wallpaper. */ @Test + @Ignore("b/368345733") public void testSetCurrentComponent() throws Exception { final int testUserId = USER_SYSTEM; mService.switchUser(testUserId, null); @@ -411,26 +425,84 @@ public class WallpaperManagerServiceTests { } @Test - public void testXmlSerializationRoundtrip() { + @EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT) + public void testSaveLoadSettings() { + WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0); + expectedData.setComponent(sDefaultWallpaperComponent); + expectedData.primaryColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + expectedData.mWallpaperDimAmount = 0.5f; + expectedData.mUidToDimAmount.put(0, 0.5f); + expectedData.mUidToDimAmount.put(1, 0.4f); + + ByteArrayOutputStream ostream = new ByteArrayOutputStream(); + try { + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(ostream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, expectedData, null); + ostream.close(); + } catch (IOException e) { + fail("exception occurred while writing system wallpaper attributes"); + } + + WallpaperData actualData = new WallpaperData(0, FLAG_SYSTEM); + try { + ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray()); + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + parser.setInput(istream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.loadSettingsFromSerializer(parser, + actualData, /* userId= */0, /* loadSystem= */ true, /* loadLock= */ + false, /* keepDimensionHints= */ true, + new WallpaperDisplayHelper.DisplayData(0)); + } catch (IOException | XmlPullParserException e) { + fail("exception occurred while parsing wallpaper"); + } + + assertThat(actualData.getComponent()).isEqualTo(expectedData.getComponent()); + assertThat(actualData.primaryColors).isEqualTo(expectedData.primaryColors); + assertThat(actualData.mWallpaperDimAmount).isEqualTo(expectedData.mWallpaperDimAmount); + assertThat(actualData.mUidToDimAmount).isNotNull(); + assertThat(actualData.mUidToDimAmount.size()).isEqualTo( + expectedData.mUidToDimAmount.size()); + for (int i = 0; i < actualData.mUidToDimAmount.size(); i++) { + int key = actualData.mUidToDimAmount.keyAt(0); + assertThat(actualData.mUidToDimAmount.get(key)).isEqualTo( + expectedData.mUidToDimAmount.get(key)); + } + } + + @Test + @DisableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT) + public void testSaveLoadSettings_legacyNextComponent() { WallpaperData systemWallpaperData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0); + systemWallpaperData.setComponent(sDefaultWallpaperComponent); + ByteArrayOutputStream ostream = new ByteArrayOutputStream(); try { TypedXmlSerializer serializer = Xml.newBinarySerializer(); - serializer.setOutput(new ByteArrayOutputStream(), StandardCharsets.UTF_8.name()); - serializer.startDocument(StandardCharsets.UTF_8.name(), true); - mService.mWallpaperDataParser.writeWallpaperAttributes( - serializer, "wp", systemWallpaperData); + serializer.setOutput(ostream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, systemWallpaperData, + null); + ostream.close(); } catch (IOException e) { fail("exception occurred while writing system wallpaper attributes"); } WallpaperData shouldMatchSystem = new WallpaperData(0, FLAG_SYSTEM); try { + ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray()); TypedXmlPullParser parser = Xml.newBinaryPullParser(); - mService.mWallpaperDataParser.parseWallpaperAttributes(parser, shouldMatchSystem, true); - } catch (XmlPullParserException e) { + parser.setInput(istream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.loadSettingsFromSerializer(parser, + shouldMatchSystem, /* userId= */0, /* loadSystem= */ true, /* loadLock= */ + false, /* keepDimensionHints= */ true, + new WallpaperDisplayHelper.DisplayData(0)); + } catch (IOException | XmlPullParserException e) { fail("exception occurred while parsing wallpaper"); } - assertEquals(systemWallpaperData.primaryColors, shouldMatchSystem.primaryColors); + + assertThat(shouldMatchSystem.nextWallpaperComponent).isEqualTo( + systemWallpaperData.getComponent()); + assertThat(shouldMatchSystem.primaryColors).isEqualTo(systemWallpaperData.primaryColors); } @Test @@ -580,7 +652,7 @@ public class WallpaperManagerServiceTests { final WallpaperData lastData = mService.mLastWallpaper; assertNotNull("Last wallpaper must not be null", lastData); assertEquals("Last wallpaper component must be equals.", expectedComponent, - lastData.wallpaperComponent); + lastData.getComponent()); assertEquals("The user id in last wallpaper should be the last switched user", lastUserId, lastData.userId); assertNotNull("Must exist user data connection on last wallpaper data", diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING index 1e8d2de36023872b7a06d510925e8bdf71ef08aa..d3d3cf641d9a09eac8b2e35eaccb6e8c47bdd5f4 100644 --- a/services/tests/powerstatstests/TEST_MAPPING +++ b/services/tests/powerstatstests/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "PowerStatsTests", - "options": [ - {"include-filter": "com.android.server.power.stats"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "PowerStatsTests_power_stats" } ], "ravenwood-presubmit": [ @@ -20,11 +15,7 @@ ], "postsubmit": [ { - "name": "PowerStatsTests", - "options": [ - {"include-filter": "com.android.server.power.stats"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "PowerStatsTests_power_stats" } ] } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index a25621a8975f3daa794de3200e5e86c850e6101c..390eb937fe2564b1e8568b975ff37f3221f4a79b 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -61,7 +61,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; @@ -95,7 +94,6 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManagerInternal; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; @@ -214,10 +212,7 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); - doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mInjector).showKeyguard(any()); + doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. @@ -432,6 +427,7 @@ public class UserControllerTest { mUserController.registerUserSwitchObserver(observer, "mock"); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -440,7 +436,6 @@ public class UserControllerTest { // Call dispatchUserSwitch and verify that observer was called only once mInjector.mHandler.clearAllRecordedMessages(); mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); Set expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG); Set actualCodes = mInjector.mHandler.getMessageCodes(); @@ -463,6 +458,7 @@ public class UserControllerTest { mUserController.registerUserSwitchObserver(observer, "mock"); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -471,7 +467,6 @@ public class UserControllerTest { // Call dispatchUserSwitch and verify that observer was called only once mInjector.mHandler.clearAllRecordedMessages(); mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout) Set actualCodes = mInjector.mHandler.getMessageCodes(); @@ -554,6 +549,7 @@ public class UserControllerTest { expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG); + expectedCodes.add(0); // this is for directly posting in stopping. } if (expectScheduleBackgroundUserStopping) { expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG); @@ -1579,13 +1575,21 @@ public class UserControllerTest { // mock the device to be secure in order to expect the keyguard to be shown when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); - // call real showKeyguard method for this test - doCallRealMethod().when(mInjector).showKeyguard(any()); + // call real lockDeviceNowAndWaitForKeyguardShown method for this test + doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); - mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); + // call startUser on a thread because we're expecting it to be blocked + Thread threadStartUser = new Thread(()-> { + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + }); + threadStartUser.start(); - // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet - verify(mInjector, never()).dismissUserSwitchingDialog(any()); + // make sure the switch is stalled... + Thread.sleep(2000); + // by checking REPORT_USER_SWITCH_MSG is not sent yet + assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is still alive + assertTrue(threadStartUser.isAlive()); // mock send the keyguard shown event ArgumentCaptor captor = ArgumentCaptor.forClass( @@ -1593,42 +1597,12 @@ public class UserControllerTest { verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); captor.getValue().onKeyguardStateChanged(true); - // verify the switch now moves on by checking the UserSwitchingDialog is dismissed - verify(mInjector, atLeastOnce()).dismissUserSwitchingDialog(any()); - - // verify that SHOW_KEYGUARD_TIMEOUT is ignored and does not crash the system - try { - mInjector.mHandler.processPostDelayedCallbacksWithin( - UserController.SHOW_KEYGUARD_TIMEOUT_MS); - } catch (RuntimeException e) { - throw new AssertionError( - "SHOW_KEYGUARD_TIMEOUT is not ignored and crashed the system", e); - } - } - - @Test - public void testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown() throws Exception { - // enable user switch ui, because keyguard is only shown then - mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, - /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false, - /* backgroundUserScheduledStopTimeSecs= */ -1); - - // mock the device to be secure in order to expect the keyguard to be shown - when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); - - // suppress showKeyguard method for this test - doNothing().when(mInjector).showKeyguard(any()); - - mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); - - // verify that the system has crashed - assertThrows("Should have thrown RuntimeException", RuntimeException.class, () -> { - mInjector.mHandler.processPostDelayedCallbacksWithin( - UserController.SHOW_KEYGUARD_TIMEOUT_MS); - }); - - // make sure the UserSwitchingDialog is not dismissed - verify(mInjector, never()).dismissUserSwitchingDialog(any()); + // verify the switch now moves on... + Thread.sleep(1000); + // by checking REPORT_USER_SWITCH_MSG is sent + assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is finished + assertFalse(threadStartUser.isAlive()); } private void setUpAndStartUserInBackground(int userId) throws Exception { @@ -1989,9 +1963,7 @@ public class UserControllerTest { Set getMessageCodes() { Set result = new LinkedHashSet<>(); for (Message msg : mMessages) { - if (msg.what != 0) { // ignore mHandle.post and mHandler.postDelayed messages - result.add(msg.what); - } + result.add(msg.what); } return result; } @@ -2015,28 +1987,14 @@ public class UserControllerTest { @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - final Runnable cb = msg.getCallback(); - if (cb != null && uptimeMillis <= SystemClock.uptimeMillis()) { - // run mHandler.post calls immediately - cb.run(); - return true; - } Message copy = new Message(); copy.copyFrom(msg); - copy.setCallback(cb); mMessages.add(copy); - return super.sendMessageAtTime(msg, uptimeMillis); - } - - public void processPostDelayedCallbacksWithin(long millis) { - final long whenMax = SystemClock.uptimeMillis() + millis; - for (Message msg : mMessages) { - final Runnable cb = msg.getCallback(); - if (cb != null && msg.getWhen() <= whenMax) { - msg.setCallback(null); - cb.run(); - } + if (msg.getCallback() != null) { + msg.getCallback().run(); + msg.setCallback(null); } + return super.sendMessageAtTime(msg, uptimeMillis); } } } diff --git a/services/tests/servicestests/src/com/android/server/audio/MediaFocusControlTest.java b/services/tests/servicestests/src/com/android/server/audio/MediaFocusControlTest.java new file mode 100644 index 0000000000000000000000000000000000000000..34878c87747af9230a98296bfec3bc9fb28fc4b9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/MediaFocusControlTest.java @@ -0,0 +1,128 @@ +/* + * 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.server.audio; + +import android.annotation.NonNull; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioFocusInfo; +import android.media.AudioManager; +import android.os.Binder; +import android.os.IBinder; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class MediaFocusControlTest { + private static final String TAG = "MediaFocusControlTest"; + + private Context mContext; + private MediaFocusControl mMediaFocusControl; + private final IBinder mICallBack = new Binder(); + + + private static class NoopPlayerFocusEnforcer implements PlayerFocusEnforcer { + public boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser, + boolean forceDuck) { + return true; + } + + public void restoreVShapedPlayers(@NonNull FocusRequester winner) { + } + + public void mutePlayersForCall(int[] usagesToMute) { + } + + public void unmutePlayersForCall() { + } + + public boolean fadeOutPlayers(@NonNull FocusRequester winner, + @NonNull FocusRequester loser) { + return true; + } + + public void forgetUid(int uid) { + } + + public long getFadeOutDurationMillis(@NonNull AudioAttributes aa) { + return 100; + } + + public long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa) { + return 100; + } + + public boolean shouldEnforceFade() { + return false; + } + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mMediaFocusControl = new MediaFocusControl(mContext, new NoopPlayerFocusEnforcer()); + } + + private static final AudioAttributes MEDIA_ATTRIBUTES = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA).build(); + private static final AudioAttributes ALARM_ATTRIBUTES = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM).build(); + private static final int MEDIA_UID = 10300; + private static final int ALARM_UID = 10301; + + /** + * Test {@link MediaFocusControl#sendFocusLossAndUpdate(AudioFocusInfo)} + */ + @Test + public void testSendFocusLossAndUpdate() throws Exception { + // simulate a media app requesting focus, followed by an alarm + mMediaFocusControl.requestAudioFocus(MEDIA_ATTRIBUTES, AudioManager.AUDIOFOCUS_GAIN, + mICallBack, null /*focusDispatcher*/, "clientMedia", "packMedia", + AudioManager.AUDIOFOCUS_FLAG_TEST /*flags*/, 35 /*sdk*/, false/*forceDuck*/, + MEDIA_UID, true /*permissionOverridesCheck*/); + final AudioFocusInfo alarm = new AudioFocusInfo(ALARM_ATTRIBUTES, ALARM_UID, + "clientAlarm", "packAlarm", + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0/*lossReceived*/, + AudioManager.AUDIOFOCUS_FLAG_TEST /*flags*/, 35 /*sdk*/); + mMediaFocusControl.requestAudioFocus(alarm.getAttributes(), alarm.getGainRequest(), + mICallBack, null /*focusDispatcher*/, alarm.getClientId(), alarm.getPackageName(), + alarm.getFlags(), alarm.getSdkTarget(), false/*forceDuck*/, + alarm.getClientUid(), true /*permissionOverridesCheck*/); + // verify stack is in expected state + List stack = mMediaFocusControl.getFocusStack(); + Assert.assertEquals("focus stack should have 2 entries", 2, stack.size()); + Assert.assertEquals("focus loser should have received LOSS_TRANSIENT_CAN_DUCK", + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK, stack.get(0).getLossReceived()); + + // make alarm app lose focus and check stack + mMediaFocusControl.sendFocusLossAndUpdate(alarm); + stack = mMediaFocusControl.getFocusStack(); + Assert.assertEquals("focus stack should have 1 entry after sendFocusLossAndUpdate", + 1, stack.size()); + Assert.assertEquals("new top of stack should be media app", + MEDIA_UID, stack.get(0).getClientUid()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 44aa868716eb9af5ca4c2ad03366756b1c632556..a55aa2364aa55e37ff1faa51474ef5656f7fca22 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -30,7 +30,6 @@ import android.graphics.fonts.SystemFonts; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.system.Os; @@ -41,8 +40,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.text.flags.Flags; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -1106,7 +1103,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1126,7 +1122,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1); @@ -1146,7 +1141,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1166,7 +1160,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1186,7 +1179,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1206,7 +1198,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1226,7 +1217,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1246,7 +1236,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1266,7 +1255,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1286,7 +1274,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1); @@ -1306,7 +1293,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1326,7 +1312,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1346,7 +1331,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1366,7 +1350,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1386,7 +1369,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1406,7 +1388,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1426,7 +1407,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1446,7 +1426,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1466,7 +1445,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1486,7 +1464,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1506,7 +1483,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1527,7 +1503,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1548,7 +1523,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1569,7 +1543,6 @@ public final class UpdatableFontDirTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 95a7f4b6c80f2a6ec7a288dd962700b916301185..a0005d968e31dc723ae78ef773e8d65230a288a3 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -25,6 +25,7 @@ import static com.android.server.hdmi.HdmiCecLocalDevicePlayback.POPUP_AFTER_ACT import static com.android.server.hdmi.HdmiCecLocalDevicePlayback.STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; +import static com.android.server.hdmi.PowerStatusMonitorActionFromPlayback.MONITORING_INTERVAL_MS; import static com.google.common.truth.Truth.assertThat; @@ -145,6 +146,11 @@ public class HdmiCecLocalDevicePlaybackTest { protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { // do nothing } + + @Override + protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() { + return true; + } }; mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); @@ -2556,6 +2562,44 @@ public class HdmiCecLocalDevicePlaybackTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage); } + @Test + public void powerStatusMonitorActionFromPlayback_TvReportPowerOff_goToSleep() { + mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + assertThat(mHdmiCecLocalDevicePlayback.getActions( + PowerStatusMonitorActionFromPlayback.class)).hasSize(1); + assertThat(mPowerManager.isInteractive()).isTrue(); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(MONITORING_INTERVAL_MS); + mTestLooper.dispatchAll(); + + HdmiCecMessage givePowerStatus = + HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mPlaybackLogicalAddress, + Constants.ADDR_TV); + HdmiCecMessage reportPowerStatusTvOn = + HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, + HdmiControlManager.POWER_STATUS_ON); + HdmiCecMessage reportPowerStatusTvStandby = + HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, + HdmiControlManager.POWER_STATUS_STANDBY); + + assertThat(mNativeWrapper.getResultMessages().contains(givePowerStatus)).isTrue(); + mNativeWrapper.onCecMessage(reportPowerStatusTvOn); + mTestLooper.dispatchAll(); + + assertThat(mPowerManager.isInteractive()).isTrue(); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(MONITORING_INTERVAL_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages().contains(givePowerStatus)).isTrue(); + mNativeWrapper.onCecMessage(reportPowerStatusTvStandby); + mTestLooper.dispatchAll(); + + assertThat(mPowerManager.isInteractive()).isFalse(); + } + private void skipActiveSourceLostUi(long idleDuration) { mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING index 58f5bb3eb7d0e203e78f3bf41b45b07a7b9f55cb..9b23b4908a7870f3f52b67f3bd097fcd48675855 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING @@ -6,23 +6,7 @@ ], "postsubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.location.contexthub." - }, - { - // I believe this include annotation is preventing tests from being run - // as there are no matching tests with the Postsubmit annotation. - "include-annotation": "android.platform.test.annotations.Postsubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksServicesTests_com_android_server_location_contexthub" } ] } diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 689b241f0faa8021c0db3c123e2f481d9f5b3f46..abc9ce3fdc366e8bd3a23404e722deead3e64bb9 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -50,11 +50,12 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; +import android.app.AppOpsManager; import android.app.KeyguardManager; import android.content.Context; -import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ApplicationInfoFlags; @@ -72,6 +73,7 @@ import android.os.test.TestLooper; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; +import android.testing.TestableContext; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; @@ -99,13 +101,14 @@ import java.util.concurrent.TimeUnit; /** * Tests for the {@link MediaProjectionManagerService} class. - * + *

* Build/Install/Run: * atest FrameworksServicesTests:MediaProjectionManagerServiceTest */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) +@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"}) public class MediaProjectionManagerServiceTest { private static final int UID = 10; private static final String PACKAGE_NAME = "test.package"; @@ -151,7 +154,10 @@ public class MediaProjectionManagerServiceTest { } }; - private Context mContext; + @Rule + public final TestableContext mContext = spy( + new TestableContext(InstrumentationRegistry.getInstrumentation().getContext())); + private MediaProjectionManagerService mService; private OffsettableClock mClock; private ContentRecordingSession mWaitingDisplaySession = @@ -169,6 +175,8 @@ public class MediaProjectionManagerServiceTest { @Mock private KeyguardManager mKeyguardManager; @Mock + AppOpsManager mAppOpsManager; + @Mock private IMediaProjectionWatcherCallback mWatcherCallback; @Mock private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @@ -185,10 +193,9 @@ public class MediaProjectionManagerServiceTest { LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); - mContext = spy(new ContextWrapper( - InstrumentationRegistry.getInstrumentation().getTargetContext())); - doReturn(mPackageManager).when(mContext).getPackageManager(); - doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE)); + mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager); + mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager); + mContext.setMockPackageManager(mPackageManager); mClock = new OffsettableClock.Stopped(); mWaitingDisplaySession.setWaitingForConsent(true); @@ -291,6 +298,27 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.getActiveProjectionInfo()).isNotNull(); } + @SuppressLint("MissingPermission") + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test + public void testCreateProjection_keyguardLocked_AppOpMediaProjection() + throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + doReturn(true).when(mAppOpsManager).isOperationActive(eq(AppOpsManager.OP_PROJECT_MEDIA), + eq(projection.uid), eq(projection.packageName)); + doReturn(true).when(mKeyguardManager).isKeyguardLocked(); + + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + RECORD_SENSITIVE_CONTENT, projection.packageName); + + projection.start(mIMediaProjectionCallback); + projection.notifyVirtualDisplayCreated(10); + + // The projection was started because it was allowed to capture the keyguard. + assertThat(mService.getActiveProjectionInfo()).isNotNull(); + } + @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING index 944c1df94b9217383142276eb9510258fedfd587..dc3b1447c13e756505dc126c903a99d3023c388e 100644 --- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING @@ -4,12 +4,7 @@ "name": "FrameworksServicesTests_om" }, { - "name": "PackageManagerServiceHostTests", - "options": [ - { - "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest" - } - ] + "name": "PackageManagerServiceHostTests_test_overlayactorvisibilitytest" } ] } diff --git a/services/tests/servicestests/src/com/android/server/supervision/OWNERS b/services/tests/servicestests/src/com/android/server/supervision/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..a5de8007cfdab3ba062d3c081b63d0e4ffb0607a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/supervision/OWNERS @@ -0,0 +1 @@ +include /services/supervision/OWNERS \ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index b97a2684576c7a80e4e11491823f0f28d439ef1b..585df84f7f90c67e7b3f3cbe2612e771903891c9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -2465,6 +2465,7 @@ public class GroupHelperTest extends UiServiceTestCase { final String pkg = "package"; final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg, AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier()); + String expectedTriggeringKey = null; // Post singleton groups, above forced group limit for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) { NotificationRecord summary = getNotificationRecord(pkg, i, @@ -2473,15 +2474,67 @@ public class GroupHelperTest extends UiServiceTestCase { NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp "+i, false); notificationList.add(child); + expectedTriggeringKey = child.getKey(); summaryByGroup.put(summary.getGroupKey(), summary); mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup); summary.isCanceled = true; // simulate removing the app summary mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); + } + // Check that notifications are forced grouped + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), + eq(expectedTriggeringKey), eq(expectedGroupKey), anyInt(), + eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(), + eq(expectedGroupKey), eq(true)); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(), + any()); + // Check that summaries are canceled + verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).removeAppProvidedSummary( + anyString()); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS}) + public void testAddAggregateSummary_summaryTriggers_singletonGroups() { + final List notificationList = new ArrayList<>(); + final ArrayMap summaryByGroup = new ArrayMap<>(); + final String pkg = "package"; + final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier()); + final int firstChildIdx = 1; + // Post singleton groups, below forced group limit + for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT - 1; i++) { + NotificationRecord summary = getNotificationRecord(pkg, i, + String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true); + notificationList.add(summary); + NotificationRecord child = getNotificationRecord(pkg, i + 42, + String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false); + notificationList.add(child); + summaryByGroup.put(summary.getGroupKey(), summary); + mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); + mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup); } + + // Post triggering group summary + final String expectedTriggeringKey = notificationList.get(firstChildIdx).getKey(); + final int triggerIdx = AUTOGROUP_SINGLETONS_AT_COUNT - 1; + NotificationRecord summary = getNotificationRecord(pkg, triggerIdx, + String.valueOf(triggerIdx), UserHandle.SYSTEM, "testGrp " + triggerIdx, true); + notificationList.add(summary); + NotificationRecord child = getNotificationRecord(pkg, triggerIdx + 42, + String.valueOf(triggerIdx + 42), UserHandle.SYSTEM, "testGrp " + triggerIdx, false); + notificationList.add(child); + summaryByGroup.put(summary.getGroupKey(), summary); + mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); + // Check that notifications are forced grouped - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), - eq(expectedGroupKey), anyInt(), eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), + eq(expectedTriggeringKey), eq(expectedGroupKey), anyInt(), + eq(getNotificationAttributes(BASE_FLAGS))); verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(), eq(expectedGroupKey), eq(true)); verify(mCallback, never()).removeAutoGroup(anyString()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 96ddf8079e172c2b305f205fa89008e5ef0c8640..b8f9767b5512289954338de5fc66e3f83734d622 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -2815,7 +2815,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST}) public void testOnlyForceGroupIfNeeded_newNotification_notAutogrouped() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(false); @@ -2834,7 +2835,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST}) public void testOnlyForceGroupIfNeeded_newNotification_wasAutogrouped() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(true); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java index 4d2396c78d16e8c607a0638bc9325053a3c50528..65b4ac116b34b77a0fee4df99cf3de5e4c1dcc06 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -103,6 +103,12 @@ public class VibratorHelperTest extends UiServiceTestCase { assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(Uri.EMPTY)); } + @Test + public void createVibrationEffectFromSoundUri_opaqueUri() { + Uri uri = Uri.parse("a:b#c"); + assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri)); + } + @Test public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() { Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index efcf027a0b90e6482224e15fb81b623724e633e8..84c4f620f3948621ca8a35abce5597c8da9618d5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -32,6 +32,7 @@ import static android.service.notification.Condition.STATE_FALSE; import static android.service.notification.Condition.STATE_TRUE; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON; import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_API; +import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_UI; import static android.service.notification.ZenModeConfig.ZEN_TAG; import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE; import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE; @@ -1169,6 +1170,23 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertThat(suppressedEffectsOf(result)).isEqualTo(suppressedEffectsOf(policy)); } + @Test + public void readXml_fixesWronglyDisabledManualRule() throws Exception { + ZenModeConfig config = getCustomConfig(); + if (!Flags.modesUi()) { + config.manualRule = new ZenModeConfig.ZenRule(); + config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + } + config.manualRule.enabled = false; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeConfigXml(config, XML_VERSION_MODES_UI, /* forBackup= */ false, baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ZenModeConfig fromXml = readConfigXml(bais); + + assertThat(fromXml.manualRule.enabled).isTrue(); + } + private static String suppressedEffectsOf(Policy policy) { return suppressedEffectsToString(policy.suppressedVisualEffects) + "(" + policy.suppressedVisualEffects + ")"; @@ -1274,7 +1292,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { out.setOutput(new BufferedOutputStream(os), "utf-8"); out.startDocument(null, true); out.startTag(null, tag); - ZenModeConfig.writeRuleXml(rule, out); + ZenModeConfig.writeRuleXml(rule, out, /* forBackup= */ false); out.endTag(null, tag); out.endDocument(); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 39a9d30e7a92bc04cdefb9033844a266a0ffcda2..12069284e46202a64e4d6bcdce5452e9779137dc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -6923,6 +6923,91 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(eventsRule.triggerDescription).isNotEmpty(); } + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_withManualActivation_activeOnReboot() + throws Exception { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + assertThat(zenRule.condition).isNull(); + + ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule).isNull(); + + // Now simulate a reboot -> reload the configuration after purging. + TypedXmlPullParser parser = getParserForByteStream(xmlBytes); + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + if (Flags.modesUi()) { + assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + assertThat(zenRule.condition).isNull(); + } else { + assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + } + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_withManualDeactivation_clearedOnReboot() + throws Exception { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "snooze", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE); + ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + assertThat(zenRule.condition).isNotNull(); + + ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule).isNull(); + + // Now simulate a reboot -> reload the configuration after purging. + TypedXmlPullParser parser = getParserForByteStream(xmlBytes); + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isNotNull(); + } + + @Test + @EnableFlags(FLAG_MODES_API) + public void addAutomaticZenRule_withoutPolicy_getsItsOwnInstanceOfDefaultPolicy() { + // Add a rule without policy -> uses default config + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, azr, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + + ZenRule zenRule = checkNotNull(mZenModeHelper.mConfig.automaticRules.get(ruleId)); + + assertThat(zenRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy()); + assertThat(zenRule.zenPolicy).isNotSameInstanceAs(mZenModeHelper.getDefaultZenPolicy()); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING index 39bd238fc20227abb05c7a0d9f89e905001e35df..b17b96add46668bd9963ba016edee36c5c69c04f 100644 --- a/services/tests/vibrator/TEST_MAPPING +++ b/services/tests/vibrator/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksVibratorServicesTests", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "FrameworksVibratorServicesTests" } ], "postsubmit": [ diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java index 6076d3318c40a69149fa41dd78d74dd987bd72ef..f7127df0ee338fd4ea8ba8fea2ae32d584e33a3c 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -18,6 +18,7 @@ package com.android.server.vibrator; import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; +import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK; import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK; import static android.os.VibrationAttributes.USAGE_TOUCH; import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; @@ -105,7 +106,7 @@ public class HapticFeedbackVibrationProviderTest { @Before public void setUp() { - mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); + mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); } @Test @@ -398,7 +399,7 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) { - VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback( + VibrationAttributes attrs = provider.getVibrationAttributes( effectId, /* flags */ 0, /* privFlags */ 0); assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST); } @@ -408,7 +409,7 @@ public class HapticFeedbackVibrationProviderTest { public void testVibrationAttribute_forNotBypassingIntensitySettings() { HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); - VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback( + VibrationAttributes attrs = provider.getVibrationAttributes( SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0); assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse(); @@ -418,7 +419,7 @@ public class HapticFeedbackVibrationProviderTest { public void testVibrationAttribute_forByassingIntensitySettings() { HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); - VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback( + VibrationAttributes attrs = provider.getVibrationAttributes( SAFE_MODE_ENABLED, /* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0); @@ -431,7 +432,7 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { - VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback( + VibrationAttributes attrs = provider.getVibrationAttributes( effectId, /* flags */ 0, /* privFlags */ 0); assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId) .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue(); @@ -444,19 +445,72 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { - VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback( + VibrationAttributes attrs = provider.getVibrationAttributes( effectId, /* flags */ 0, /* privFlags */ 0); assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId) .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse(); } } + @Test + public void testVibrationAttribute_scrollFeedback_inputCustomizedFlag_useTouchUsage() { + mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); + HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); + + for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = provider.getVibrationAttributes(effectId, /* flags */ + 0, /* privFlags */ 0); + assertWithMessage("Expected USAGE_TOUCH for scroll effect " + effectId + + ", if no input customization").that(attrs.getUsage()).isEqualTo(USAGE_TOUCH); + } + } + + @Test + public void testVibrationAttribute_scrollFeedback_noInputCustomizedFlag_useHardwareFeedback() { + HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); + + for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = provider.getVibrationAttributes(effectId, /* flags */ + 0, /* privFlags */ 0); + assertWithMessage("Expected USAGE_HARDWARE_FEEDBACK for scroll effect " + effectId + + ", if no input customization").that(attrs.getUsage()).isEqualTo( + USAGE_HARDWARE_FEEDBACK); + } + } + + @Test + public void testVibrationAttribute_scrollFeedback_rotaryInputSource_useHardwareFeedback() { + mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); + HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); + + for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = provider.getVibrationAttributes( + effectId, InputDevice.SOURCE_ROTARY_ENCODER, /* flags */ 0, /* privFlags */ 0); + assertWithMessage( + "Expected USAGE_HARDWARE_FEEDBACK for input source SOURCE_ROTARY_ENCODER").that( + attrs.getUsage()).isEqualTo(USAGE_HARDWARE_FEEDBACK); + } + } + + @Test + public void testVibrationAttribute_scrollFeedback_touchInputSource_useTouchUsage() { + mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); + HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); + + for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = provider.getVibrationAttributes( + effectId, InputDevice.SOURCE_TOUCHSCREEN, /* flags */ 0, /* privFlags */ 0); + assertWithMessage("Expected USAGE_TOUCH for input source SOURCE_TOUCHSCREEN").that( + attrs.getUsage()).isEqualTo(USAGE_TOUCH); + } + } + @Test public void testVibrationAttribute_notIme_useTouchUsage() { HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { - VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback( + VibrationAttributes attrs = provider.getVibrationAttributes( effectId, /* flags */ 0, /* privFlags */ 0); assertWithMessage("Expected USAGE_TOUCH for effect " + effectId) .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH); @@ -468,7 +522,7 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { - VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback( + VibrationAttributes attrs = provider.getVibrationAttributes( effectId, /* flags */ 0, HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS); assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId) diff --git a/services/tests/voiceinteractiontests/TEST_MAPPING b/services/tests/voiceinteractiontests/TEST_MAPPING index 6cbc49a2a7e131a3aa27bd73dfad08c0d11399ee..466ba54fc8a4c680049a8374ed8c8342f093946a 100644 --- a/services/tests/voiceinteractiontests/TEST_MAPPING +++ b/services/tests/voiceinteractiontests/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksVoiceInteractionTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksVoiceInteractionTests" } ], "postsubmit": [ diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 8f3adba81be4e27c7b12da14d3ebc40270501d5e..3d978e424375d9326b5b8c3b831a56e5e60a89aa 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -16,6 +16,8 @@ package com.android.server.policy; +import static android.view.Display.DEFAULT_DISPLAY; + import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST; @@ -23,21 +25,21 @@ import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIF import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL; import android.hardware.input.KeyGestureEvent; +import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; -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 androidx.test.filters.MediumTest; import com.android.internal.annotations.Keep; +import junit.framework.Assert; + import junitparams.JUnitParamsRunner; import junitparams.Parameters; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,10 +48,6 @@ import org.junit.runner.RunWith; @RunWith(JUnitParamsRunner.class) public class KeyGestureEventTests extends ShortcutKeyTestBase { - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT; private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT); private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT; @@ -149,9 +147,9 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE}, KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE, KeyEvent.KEYCODE_VOLUME_MUTE, 0}, - {"ALL_APPS key -> Open App Drawer in Accessibility mode", + {"ALL_APPS key -> Open App Drawer", new int[]{KeyEvent.KEYCODE_ALL_APPS}, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, KeyEvent.KEYCODE_ALL_APPS, 0}, {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH}, KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, @@ -160,8 +158,8 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH}, KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0}, - {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY}, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, META_KEY, + {"META key -> Open App Drawer", new int[]{META_KEY}, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, META_KEY, META_ON}, {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY}, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, ALT_KEY, @@ -182,12 +180,12 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { META_ON | CTRL_ON}, {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT}, - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, META_ON | CTRL_ON}, {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT}, - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, META_ON | CTRL_ON}, {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L}, @@ -320,18 +318,18 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST, KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H, META_ON}, - {"Long press HOME key -> Open App Drawer in Accessibility mode", + {"Long press HOME key -> Open App Drawer", new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, KeyEvent.KEYCODE_HOME, 0}, - {"Long press META + ENTER -> Open App Drawer in Accessibility mode", + {"Long press META + ENTER -> Open App Drawer", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, KeyEvent.KEYCODE_ENTER, META_ON}, - {"Long press META + H -> Open App Drawer in Accessibility mode", + {"Long press META + H -> Open App Drawer", new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ALL_APPS, - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, KeyEvent.KEYCODE_H, META_ON}}; } @@ -428,7 +426,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) + @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) public void testBugreportShortcutPress() { testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL}, @@ -444,4 +442,161 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType, "Failed while executing " + testName); } + + @Test + public void testKeyGestureRecentApps() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS)); + mPhoneWindowManager.assertShowRecentApps(); + } + + @Test + public void testKeyGestureAppSwitch() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH)); + mPhoneWindowManager.assertToggleRecentApps(); + } + + @Test + public void testKeyGestureLaunchAssistant() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT)); + mPhoneWindowManager.assertSearchManagerLaunchAssist(); + } + + @Test + public void testKeyGestureGoHome() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)); + mPhoneWindowManager.assertGoToHomescreen(); + } + + @Test + public void testKeyGestureLaunchSystemSettings() { + Assert.assertTrue( + sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS)); + mPhoneWindowManager.assertLaunchSystemSettings(); + } + + @Test + public void testKeyGestureLock() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN)); + mPhoneWindowManager.assertLockedAfterAppTransitionFinished(); + } + + @Test + public void testKeyGestureToggleNotificationPanel() throws RemoteException { + Assert.assertTrue( + sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL)); + mPhoneWindowManager.assertTogglePanel(); + } + + @Test + public void testKeyGestureScreenshot() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT)); + mPhoneWindowManager.assertTakeScreenshotCalled(); + } + + @Test + public void testKeyGestureTriggerBugReport() throws RemoteException { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT)); + mPhoneWindowManager.assertTakeBugreport(true); + } + + @Test + public void testKeyGestureBack() { + Assert.assertTrue(sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)); + mPhoneWindowManager.assertBackEventInjected(); + } + + @Test + public void testKeyGestureMultiWindowNavigation() { + Assert.assertTrue(sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION)); + mPhoneWindowManager.assertMoveFocusedTaskToFullscreen(); + } + + @Test + public void testKeyGestureDesktopMode() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE)); + mPhoneWindowManager.assertMoveFocusedTaskToDesktop(); + } + + @Test + public void testKeyGestureSplitscreenNavigation() { + Assert.assertTrue(sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT)); + mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(true); + + Assert.assertTrue(sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT)); + mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(false); + } + + @Test + public void testKeyGestureSplitscreenFocus() { + Assert.assertTrue(sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT)); + mPhoneWindowManager.assertSetSplitscreenFocus(true); + + Assert.assertTrue(sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT)); + mPhoneWindowManager.assertSetSplitscreenFocus(false); + } + + @Test + public void testKeyGestureShortcutHelper() { + Assert.assertTrue(sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER)); + mPhoneWindowManager.assertToggleShortcutsMenu(); + } + + @Test + public void testKeyGestureBrightnessChange() { + float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f}; + float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f}; + + for (int i = 0; i < currentBrightness.length; i++) { + mPhoneWindowManager.prepareBrightnessDecrease(currentBrightness[i]); + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN)); + mPhoneWindowManager.verifyNewBrightness(newBrightness[i]); + } + } + + @Test + public void testKeyGestureRecentAppSwitcher() { + Assert.assertTrue(sendKeyGestureEventStart( + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER)); + mPhoneWindowManager.assertShowRecentApps(); + + Assert.assertTrue(sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER)); + mPhoneWindowManager.assertHideRecentApps(); + } + + @Test + public void testKeyGestureLanguageSwitch() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH)); + mPhoneWindowManager.assertSwitchKeyboardLayout(1, DEFAULT_DISPLAY); + + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + KeyEvent.META_SHIFT_ON)); + mPhoneWindowManager.assertSwitchKeyboardLayout(-1, DEFAULT_DISPLAY); + } + + @Test + public void testKeyGestureLaunchSearch() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH)); + mPhoneWindowManager.assertLaunchSearch(); + } } diff --git a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java deleted file mode 100644 index b979335233e3da55611922bf6e11e4e04c4843e9..0000000000000000000000000000000000000000 --- a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java +++ /dev/null @@ -1,96 +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.server.policy; - -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS; - -import static com.google.common.truth.Truth.assertThat; - -import android.view.KeyEvent; -import android.view.WindowManager; - -import androidx.test.filters.SmallTest; - -import com.android.internal.policy.KeyInterceptionInfo; - -import org.junit.Before; -import org.junit.Test; - -import java.util.Arrays; -import java.util.List; - -/** - * Testing {@link PhoneWindowManager} functionality of letting app intercepting key events - * containing META. - */ -@SmallTest -public class MetaKeyEventsInterceptionTests extends ShortcutKeyTestBase { - - private static final List META_KEY_EVENTS = Arrays.asList( - new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT), - new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT), - new KeyEvent(/* downTime= */ 0, /* eventTime= */ - 0, /* action= */ 0, /* code= */ 0, /* repeat= */ 0, - /* metaState= */ KeyEvent.META_META_ON)); - - @Before - public void setUp() { - setUpPhoneWindowManager(); - } - - @Test - public void doesntInterceptMetaKeyEvents_whenWindowAskedForIt() { - mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true); - setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS); - - META_KEY_EVENTS.forEach(keyEvent -> { - assertKeyInterceptionResult(keyEvent, /* intercepted= */ false); - }); - } - - @Test - public void interceptsMetaKeyEvents_whenWindowDoesntHaveFlagSet() { - mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true); - setWindowKeyInterceptionWithPrivateFlags(0); - - META_KEY_EVENTS.forEach(keyEvent -> { - assertKeyInterceptionResult(keyEvent, /* intercepted= */ true); - }); - } - - @Test - public void interceptsMetaKeyEvents_whenWindowDoesntHavePermission() { - mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ false); - setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS); - - META_KEY_EVENTS.forEach(keyEvent -> { - assertKeyInterceptionResult(keyEvent, /* intercepted= */ true); - }); - } - - private void setWindowKeyInterceptionWithPrivateFlags(int privateFlags) { - KeyInterceptionInfo info = new KeyInterceptionInfo( - WindowManager.LayoutParams.TYPE_APPLICATION, privateFlags, "title", 0); - mPhoneWindowManager.overrideWindowKeyInterceptionInfo(info); - } - - private void assertKeyInterceptionResult(KeyEvent keyEvent, boolean intercepted) { - long result = mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent); - int expected = intercepted ? -1 : 0; - assertThat(result).isEqualTo(expected); - } -} diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index 536dcfb3579c886123b9e9cf9c1c23eb4654abe1..af3dc1da4dcc844b562d8f6d7dcd69dfc8022797 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -46,6 +46,7 @@ import static org.mockito.ArgumentMatchers.eq; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; +import android.hardware.input.InputManager; import android.os.PowerManager; import android.platform.test.flag.junit.SetFlagsRule; @@ -95,6 +96,9 @@ public class PhoneWindowManagerTests { mStatusBarManagerInternal = mock(StatusBarManagerInternal.class); LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); mPhoneWindowManager.mKeyguardDelegate = mock(KeyguardServiceDelegate.class); + final InputManager im = mock(InputManager.class); + doNothing().when(im).registerKeyGestureEventHandler(any()); + doReturn(im).when(mContext).getSystemService(eq(Context.INPUT_SERVICE)); } @After diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index 37e4fd6165fd09e48ad38da7eaa76a5097098808..50b7db434267adfec5e95c1e19daaa2f3963ea67 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -56,6 +56,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.hardware.input.KeyGestureEvent; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.view.InputDevice; @@ -228,6 +229,31 @@ class ShortcutKeyTestBase { sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY); } + boolean sendKeyGestureEventStart(int gestureType) { + return mPhoneWindowManager.sendKeyGestureEvent( + new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( + KeyGestureEvent.ACTION_GESTURE_START).build()); + } + + boolean sendKeyGestureEventComplete(int gestureType) { + return mPhoneWindowManager.sendKeyGestureEvent( + new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( + KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); + } + + boolean sendKeyGestureEventComplete(int gestureType, int modifierState) { + return mPhoneWindowManager.sendKeyGestureEvent( + new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType( + gestureType).setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); + } + + boolean sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType) { + return mPhoneWindowManager.sendKeyGestureEvent( + new KeyGestureEvent.Builder().setKeycodes(new int[]{keycode}).setModifierState( + modifierState).setKeyGestureType(gestureType).setAction( + KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); + } + /** * Since we use SettingsProviderRule to mock the ContentResolver in these * tests, the settings observer registered by PhoneWindowManager will not diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index 7ea5010976eec2ad9ccfd528f04d06f1c1bdffb2..ff8b6d3c1962390f7e06fbbd88bee101ae7c0fd0 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -141,7 +141,8 @@ public class SingleKeyGestureTests { } @Override - void onKeyUp(long eventTime, int multiPressCount, int displayId) { + void onKeyUp(long eventTime, int multiPressCount, int displayId, int deviceId, + int metaState) { mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount)); } }); @@ -177,7 +178,8 @@ public class SingleKeyGestureTests { } @Override - void onKeyUp(long eventTime, int multiPressCount, int displayId) { + void onKeyUp(long eventTime, int multiPressCount, int displayId, int deviceId, + int metaState) { mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount)); } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 79c7ac193b408de17e9cc534b5015e90885888d9..98401b33840f42ab4aa8feaa7e7c6010fb3f43f7 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -52,7 +52,6 @@ import static org.mockito.Mockito.after; import static org.mockito.Mockito.description; import static org.mockito.Mockito.mockingDetails; import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import android.app.ActivityManagerInternal; @@ -71,6 +70,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; +import android.hardware.input.KeyGestureEvent; import android.media.AudioManagerInternal; import android.os.Handler; import android.os.HandlerThread; @@ -83,9 +83,11 @@ import android.os.UserHandle; import android.os.Vibrator; import android.os.VibratorInfo; import android.os.test.TestLooper; +import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.telecom.TelecomManager; import android.view.Display; +import android.view.InputEvent; import android.view.KeyEvent; import android.view.accessibility.AccessibilityManager; import android.view.autofill.AutofillManagerInternal; @@ -118,6 +120,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.quality.Strictness; +import java.util.List; import java.util.function.Supplier; class TestPhoneWindowManager { @@ -298,6 +301,8 @@ class TestPhoneWindowManager { doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class)); doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class)); doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class)); + doNothing().when(mInputManager).registerKeyGestureEventHandler(any()); + doNothing().when(mInputManager).unregisterKeyGestureEventHandler(any()); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mSensorPrivacyManager).when(mContext).getSystemService( eq(SensorPrivacyManager.class)); @@ -417,6 +422,10 @@ class TestPhoneWindowManager { mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE); } + boolean sendKeyGestureEvent(KeyGestureEvent event) { + return mPhoneWindowManager.handleKeyGestureEvent(event, mInputToken); + } + /** * Provide access to the SettingsObserver so that tests can manually trigger Settings changes. */ @@ -584,6 +593,16 @@ class TestPhoneWindowManager { doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt()); } + void assertBackEventInjected() { + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(InputEvent.class); + verify(mInputManager, times(2)).injectInputEvent(intentCaptor.capture(), anyInt()); + List inputEvents = intentCaptor.getAllValues(); + Assert.assertEquals(KeyEvent.KEYCODE_BACK, ((KeyEvent) inputEvents.get(0)).getKeyCode()); + Assert.assertEquals(KeyEvent.KEYCODE_BACK, ((KeyEvent) inputEvents.get(1)).getKeyCode()); + // Reset verifier for next call. + Mockito.clearInvocations(mContext); + } + void overrideSearchKeyBehavior(int behavior) { mPhoneWindowManager.mSearchKeyBehavior = behavior; } @@ -614,10 +633,6 @@ class TestPhoneWindowManager { .when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt()); } - void overrideWindowKeyInterceptionInfo(KeyInterceptionInfo info) { - when(mWindowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info); - } - void overrideKeyEventPolicyFlags(int flags) { mKeyEventPolicyFlags = flags; } @@ -685,6 +700,24 @@ class TestPhoneWindowManager { verify(mSearchManager).launchAssist(any()); } + void assertLaunchSystemSettings() { + mTestLooper.dispatchAll(); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).startActivityAsUser(intentCaptor.capture(), any(), any()); + Assert.assertEquals(Settings.ACTION_SETTINGS, intentCaptor.getValue().getAction()); + // Reset verifier for next call. + Mockito.clearInvocations(mContext); + } + + void assertLaunchSearch() { + mTestLooper.dispatchAll(); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).startActivityAsUser(intentCaptor.capture(), any(), any()); + Assert.assertEquals(Intent.ACTION_WEB_SEARCH, intentCaptor.getValue().getAction()); + // Reset verifier for next call. + Mockito.clearInvocations(mContext); + } + void assertLaunchCategory(String category) { mTestLooper.dispatchAll(); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -725,6 +758,36 @@ class TestPhoneWindowManager { verify(mStatusBarManagerInternal).showRecentApps(anyBoolean()); } + void assertHideRecentApps() { + mTestLooper.dispatchAll(); + verify(mStatusBarManagerInternal).hideRecentApps(anyBoolean(), anyBoolean()); + } + + void assertToggleRecentApps() { + mTestLooper.dispatchAll(); + verify(mStatusBarManagerInternal).toggleRecentApps(); + } + + void assertMoveFocusedTaskToDesktop() { + mTestLooper.dispatchAll(); + verify(mStatusBarManagerInternal).moveFocusedTaskToDesktop(anyInt()); + } + + void assertMoveFocusedTaskToFullscreen() { + mTestLooper.dispatchAll(); + verify(mStatusBarManagerInternal).moveFocusedTaskToFullscreen(anyInt()); + } + + void assertMoveFocusedTaskToStageSplit(boolean leftOrTop) { + mTestLooper.dispatchAll(); + verify(mStatusBarManagerInternal).moveFocusedTaskToStageSplit(anyInt(), eq(leftOrTop)); + } + + void assertSetSplitscreenFocus(boolean leftOrTop) { + mTestLooper.dispatchAll(); + verify(mStatusBarManagerInternal).setSplitscreenFocus(eq(leftOrTop)); + } + void assertStatusBarStartAssist() { mTestLooper.dispatchAll(); verify(mStatusBarManagerInternal).startAssist(any()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index e2e76d6ef4e5d67bf8c91ebb41c55bb77b65f11f..577b02a4ff7a9ede7d59ea27385dfb7ae0e9dd7d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -39,6 +39,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL; +import static android.content.pm.ApplicationInfo.CATEGORY_GAME; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.UI_MODE_TYPE_DESK; @@ -137,6 +139,7 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.provider.DeviceConfig; import android.util.MutableBoolean; import android.view.DisplayInfo; @@ -2655,21 +2658,43 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testSetOrientation() { + assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, true); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT) + public void testSetOrientation_restrictedByTargetSdk() { + assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_SOCIAL, false); + assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true); + + // Blanket application, also ignoring Target SDK + mWm.mConstants.mIgnoreActivityOrientationRequest = true; + assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, false); + } + + private void assertSetOrientation(int targetSdk, int category, boolean expectRotate) { final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + activity.mTargetSdk = targetSdk; + activity.info.applicationInfo.category = category; + activity.setVisible(true); // Assert orientation is unspecified to start. assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation()); + // Request orientation and see if it can be applied. activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation()); + if (expectRotate) { + assertEquals("targetSdk=" + targetSdk + " should be able to enter landscape", + SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation()); + } else { + assertEquals("targetSdk=" + targetSdk + " should not be able to enter landscape", + SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation()); + } mDisplayContent.removeAppToken(activity.token); // Assert orientation is unset to after container is removed. assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation()); - - // Reset display frozen state - mWm.mDisplayFrozen = false; } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index eca4d21a974e57e8a79a7689b438603f707a9613..85cb1bcc01fb0480fb80e740df403c96aa9492cc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -369,8 +369,10 @@ public class DisplayContentTests extends WindowTestsBase { "startingWin"); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); + final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class); - doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent(); + doReturn(imeSurfaceParent).when(imeSurfaceParentWindow).getSurfaceControl(); + doReturn(imeSurfaceParentWindow).when(mDisplayContent).computeImeParent(); spyOn(imeContainer); mDisplayContent.setImeInputTarget(startingWin); @@ -406,8 +408,11 @@ public class DisplayContentTests extends WindowTestsBase { "startingWin"); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); + final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class); - doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent(); + doReturn(imeSurfaceParent).when(imeSurfaceParentWindow).getSurfaceControl(); + doReturn(imeSurfaceParentWindow).when(mDisplayContent).computeImeParent(); + // Main precondition for this test: organize the ImeContainer. final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class); @@ -639,10 +644,11 @@ public class DisplayContentTests extends WindowTestsBase { ws.matchesDisplayAreaBounds()); assertTrue("IME shouldn't be attached to app", - dc.computeImeParent() != dc.getImeTarget(IME_TARGET_LAYERING).getWindow() - .mActivityRecord.getSurfaceControl()); + dc.computeImeParent().getSurfaceControl() != dc.getImeTarget( + IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl()); assertEquals("IME should be attached to display", - dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent()); + dc.getImeContainer().getParent().getSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } private WindowState[] createNotDrawnWindowsOn(DisplayContent displayContent, int... types) { @@ -1191,8 +1197,9 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeLayeringTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); - assertEquals(dc.getImeTarget(IME_TARGET_LAYERING).getWindow() - .mActivityRecord.getSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeTarget( + IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @Test @@ -1202,7 +1209,8 @@ public class DisplayContentTests extends WindowTestsBase { dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode( WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); - assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeContainer().getParentSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @SetupWindows(addWindows = W_ACTIVITY) @@ -1213,7 +1221,7 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(mAppWindow); // The surface parent of IME should be the display instead of app window. assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(), - mDisplayContent.computeImeParent()); + mDisplayContent.computeImeParent().getSurfaceControl()); } @Test @@ -1221,7 +1229,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "statusBar")); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); - assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeContainer().getParentSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @SetupWindows(addWindows = W_ACTIVITY) @@ -1232,7 +1241,8 @@ public class DisplayContentTests extends WindowTestsBase { doReturn(true).when(mDisplayContent).shouldImeAttachedToApp(); mDisplayContent.setImeLayeringTarget(app1); mDisplayContent.setImeInputTarget(app1); - assertEquals(app1.mActivityRecord.getSurfaceControl(), mDisplayContent.computeImeParent()); + assertEquals(app1.mActivityRecord.getSurfaceControl(), + mDisplayContent.computeImeParent().getSurfaceControl()); mDisplayContent.setImeLayeringTarget(app2); // Expect null means no change IME parent when the IME layering target not yet // request IME to be the input target. @@ -1250,7 +1260,7 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.setImeInputTarget(app); assertFalse(mDisplayContent.shouldImeAttachedToApp()); assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(), - mDisplayContent.computeImeParent()); + mDisplayContent.computeImeParent().getSurfaceControl()); } @Test @@ -1275,7 +1285,8 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(dc.getImeTarget(IME_TARGET_LAYERING), dc.getImeInputTarget()); // The ImeParent should be the display. - assertEquals(dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeContainer().getParent().getSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 957b5e04fef6301f90ff0f46835d0b07e7d268d9..ae0c6e5512465bae857574c1e5fcd90a368894aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -331,6 +331,7 @@ public class RootWindowContainerTests extends WindowTestsBase { final WindowProcessController proc = mSystemServicesTestRule.addProcess( activity.packageName, activity.processName, 6789 /* pid */, activity.info.applicationInfo.uid); + assertFalse(proc.mHasEverAttached); try { mRootWindowContainer.attachApplication(proc); verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc), @@ -338,6 +339,15 @@ public class RootWindowContainerTests extends WindowTestsBase { } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + + // Verify that onProcessRemoved won't clear the launching activities if an attached process + // is died. Because in real case, it should be handled from WindowProcessController's + // and ActivityRecord's handleAppDied to decide whether to remove the activities. + assertTrue(proc.mHasEverAttached); + assertTrue(mAtm.mStartingProcessActivities.isEmpty()); + mAtm.mStartingProcessActivities.add(activity); + mAtm.mInternal.onProcessRemoved(proc.mName, proc.mUid); + assertFalse(mAtm.mStartingProcessActivities.isEmpty()); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index f74340113a04cfd34b99123492bfe0f0123ae265..7bce8285972cc23bca7fbb720df0714214bd439a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1640,7 +1640,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); - mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); assertFalse(mActivity.inSizeCompatMode()); // Resize app to make original app bounds larger than parent bounds. @@ -1667,7 +1667,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); - mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); assertFalse(mActivity.inSizeCompatMode()); // Resize app to make original app bounds smaller than parent bounds. @@ -1692,7 +1692,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); - mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); assertFalse(mActivity.inSizeCompatMode()); final Rect originalAppBounds = mActivity.getBounds(); @@ -1705,6 +1705,38 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(originalAppBounds, mActivity.getBounds()); } + /** + * Test that when desktop mode is enabled, a freeform unresizeable activity is not up-scaled + * when exiting freeform despite its larger parent bounds. + */ + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void testCompatScaling_freeformUnresizeableApp_exitFreeform_notScaled() { + doReturn(true).when(() -> + DesktopModeHelper.canEnterDesktopMode(any())); + final int dw = 600; + final int dh = 800; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); + final Rect originalAppBounds = mActivity.getBounds(); + + assertFalse(mActivity.inSizeCompatMode()); + + // Resize app to make original app bounds smaller than parent bounds. + mTask.getWindowConfiguration().setAppBounds( + new Rect(0, 0, dw + 300, dh + 400)); + // Change windowing mode from freeform to fullscreen + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + // App should enter size compat mode but remain its original size. + assertTrue(mActivity.inSizeCompatMode()); + assertEquals(originalAppBounds, mActivity.getBounds()); + } + @Test public void testGetLetterboxInnerBounds_noScalingApplied() { // Set up a display in portrait and ignoring orientation request. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index fd959b950e16c0cd8751f1eca510eb680778d692..65a6a69fc45ee464944cbd86cd114b649618e2f7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -884,7 +884,7 @@ public class TaskFragmentTest extends WindowTestsBase { // The ImeParent should be the display. assertEquals(mDisplayContent.getImeContainer().getParent().getSurfaceControl(), - mDisplayContent.computeImeParent()); + mDisplayContent.computeImeParent().getSurfaceControl()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 7ff2e50926a59597c669d1837af1c74467f350bb..4b03483d43b9199541045b167dca101f6a140795 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -29,6 +29,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -75,6 +77,7 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.TaskInfo; import android.app.WindowConfiguration; +import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -97,9 +100,13 @@ import androidx.test.filters.MediumTest; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import libcore.junit.util.compat.CoreCompatChangeRule; + import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.xmlpull.v1.XmlPullParser; @@ -122,6 +129,9 @@ import java.io.Reader; @RunWith(WindowTestRunner.class) public class TaskTests extends WindowTestsBase { + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + private static final String TASK_TAG = "task"; private Rect mParentBounds; @@ -403,6 +413,85 @@ public class TaskTests extends WindowTestsBase { assertTrue(activity1.isVisibleRequested()); } + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP}) + public void testIsResizeable_nonResizeable_forceResize_overridesEnabled_Resizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_UNRESIZEABLE); + // Override should take effect and task should be resizeable. + assertTrue(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP}) + public void testIsResizeable_nonResizeable_forceResize_overridesDisabled_nonResizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_UNRESIZEABLE); + + // Disallow resize overrides. + task.mAllowForceResizeOverride = false; + + // Override should not take effect and task should be un-resizeable. + assertFalse(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) + public void testIsResizeable_resizeable_forceNonResize_overridesEnabled_nonResizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_RESIZEABLE); + + // Override should take effect and task should be un-resizeable. + assertFalse(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) + public void testIsResizeable_resizeable_forceNonResize_overridesDisabled_Resizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_RESIZEABLE); + + // Disallow resize overrides. + task.mAllowForceResizeOverride = false; + + // Override should not take effect and task should be resizeable. + assertTrue(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) + public void testIsResizeable_systemWideForceResize_compatForceNonResize__Resizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_RESIZEABLE); + + // Set system-wide force resizeable override. + task.mAtmService.mForceResizableActivities = true; + + // System wide override should tak priority over app compat override so the task should + // remain resizeable. + assertTrue(task.getTaskInfo().isResizeable); + } + @Test public void testResolveNonResizableTaskWindowingMode() { // Test with no support non-resizable in multi window regardless the screen size. diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java index 410499916be9499a463ed7b8e5d6493a5b090958..12b744546f5e4490132c69c3c87a8fdd3498b4fa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.tools.traces.Utils.busyWaitForDataSourceRegistration; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions; @@ -28,7 +30,6 @@ import static org.mockito.ArgumentMatchers.eq; import static java.io.File.createTempFile; import static java.nio.file.Files.createTempDirectory; -import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; import android.tools.ScenarioBuilder; import android.tools.traces.io.ResultWriter; @@ -36,9 +37,6 @@ import android.tools.traces.monitors.PerfettoTraceMonitor; import android.view.Choreographer; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.google.protobuf.InvalidProtocolBufferException; import org.junit.After; import org.junit.Before; @@ -46,12 +44,9 @@ import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; -import perfetto.protos.PerfettoConfig.TracingServiceState; import perfetto.protos.PerfettoConfig.WindowManagerConfig.LogFrequency; -import java.io.FileInputStream; import java.io.IOException; -import java.util.Optional; /** * Test class for {@link WindowTracingPerfetto}. @@ -74,7 +69,7 @@ public class WindowTracingPerfettoTest { sChoreographer = Mockito.mock(Choreographer.class); sWindowTracing = new WindowTracingPerfetto(sWmMock, sChoreographer, new WindowManagerGlobalLock(), TEST_DATA_SOURCE_NAME); - waitDataSourceIsAvailable(); + busyWaitForDataSourceRegistration(TEST_DATA_SOURCE_NAME); } @Before @@ -156,67 +151,4 @@ public class WindowTracingPerfettoTest { mTraceMonitor.stop(writer); } - - private static void waitDataSourceIsAvailable() { - final int timeoutMs = 10000; - final int busyWaitIntervalMs = 100; - - int elapsedMs = 0; - - while (!isDataSourceAvailable()) { - try { - Thread.sleep(busyWaitIntervalMs); - elapsedMs += busyWaitIntervalMs; - if (elapsedMs >= timeoutMs) { - throw new RuntimeException("Data source didn't become available." - + " Waited for: " + timeoutMs + " ms"); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - private static boolean isDataSourceAvailable() { - byte[] proto = executeShellCommand("perfetto --query-raw"); - - try { - TracingServiceState state = TracingServiceState.parseFrom(proto); - - Optional producerId = Optional.empty(); - - for (TracingServiceState.Producer producer : state.getProducersList()) { - if (producer.getPid() == android.os.Process.myPid()) { - producerId = Optional.of(producer.getId()); - break; - } - } - - if (producerId.isEmpty()) { - return false; - } - - for (TracingServiceState.DataSource ds : state.getDataSourcesList()) { - if (ds.getDsDescriptor().getName().equals(TEST_DATA_SOURCE_NAME) - && ds.getProducerId() == producerId.get()) { - return true; - } - } - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - - return false; - } - - private static byte[] executeShellCommand(String command) { - try { - ParcelFileDescriptor fd = InstrumentationRegistry.getInstrumentation().getUiAutomation() - .executeShellCommand(command); - FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(fd); - return is.readAllBytes(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } } diff --git a/services/translation/java/com/android/server/translation/TEST_MAPPING b/services/translation/java/com/android/server/translation/TEST_MAPPING index 4090b4ab2c7529a94ed59711bb772d7d16b231e0..0b97358d430d9857bba1146b1481d85f22af4b47 100644 --- a/services/translation/java/com/android/server/translation/TEST_MAPPING +++ b/services/translation/java/com/android/server/translation/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "CtsTranslationTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTranslationTestCases" } ] } diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING index c8780546865e4d2e32abfbf2836c5a35e715b3e0..79b294c00262dd32465d899aa7f212792246eaf4 100644 --- a/services/usage/java/com/android/server/usage/TEST_MAPPING +++ b/services/usage/java/com/android/server/usage/TEST_MAPPING @@ -7,33 +7,15 @@ "name": "FrameworksServicesTests_android_server_usage" }, { - "name": "CtsBRSTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "CtsBRSTestCases" } ], "postsubmit": [ { - "name": "CtsUsageStatsTestCases", - "options": [ - { - "include-filter": "android.app.usage.cts.UsageStatsTest" - } - ] + "name": "CtsUsageStatsTestCases_cts_usagestatstest_ExcludeMediumAndLarge" }, { - "name": "CtsShortcutManagerTestCases", - "options": [ - { - "include-filter": "android.content.pm.cts.shortcutmanager.ShortcutManagerUsageTest" - } - ] + "name": "CtsShortcutManagerTestCases_shortcutmanager_shortcutmanagerusagetest" } ] } diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING index e3d254948f8de1b050cfb8c576e261781e4bd3e8..3a68b3327bbd31c6439f2432b420eee4b3c79775 100644 --- a/services/voiceinteraction/TEST_MAPPING +++ b/services/voiceinteraction/TEST_MAPPING @@ -12,44 +12,19 @@ ] }, { - "name": "CtsAssistTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsAssistTestCases" }, { - "name": "CtsVoiceInteractionHostTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsVoiceInteractionHostTestCases" }, { - "name": "CtsLocalVoiceInteraction", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsLocalVoiceInteraction" }, { - "name": "FrameworksVoiceInteractionTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "FrameworksVoiceInteractionTests" }, { - "name": "CtsSoundTriggerTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsSoundTriggerTestCases" } ] } diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING index 775f1b83af94337f4f9286d9757b1d90026fb52a..4f6e55858b8d87c34be494d717eaf6f79c597db3 100644 --- a/telecomm/TEST_MAPPING +++ b/telecomm/TEST_MAPPING @@ -1,70 +1,30 @@ { "presubmit": [ { - "name": "TeleServiceTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TeleServiceTests" }, { - "name": "TelecomUnitTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TelecomUnitTests" }, { - "name": "TelephonyProviderTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TelephonyProviderTests" }, { - "name": "CtsTelephony2TestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTelephony2TestCases" }, { - "name": "CtsTelephony3TestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTelephony3TestCases" }, { - "name": "CtsSimRestrictedApisTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsSimRestrictedApisTestCases" }, { - "name": "CtsTelephonyProviderTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTelephonyProviderTestCases" } ], "presubmit-large": [ { - "name": "CtsTelecomTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTelecomTestCases" } ] } diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING index 73e3dcdb8dd040cf6a97f2b44548aa593cebfe64..4a4bae32ed8de23ac75d684835548698e83ea531 100644 --- a/telephony/TEST_MAPPING +++ b/telephony/TEST_MAPPING @@ -1,60 +1,25 @@ { "presubmit": [ { - "name": "TeleServiceTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TeleServiceTests" }, { - "name": "TelecomUnitTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TelecomUnitTests" }, { - "name": "TelephonyProviderTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "TelephonyProviderTests" }, { - "name": "CtsTelephony2TestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTelephony2TestCases" }, { - "name": "CtsTelephony3TestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTelephony3TestCases" }, { - "name": "CtsSimRestrictedApisTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsSimRestrictedApisTestCases" }, { - "name": "CtsTelephonyProviderTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsTelephonyProviderTestCases" } ] } diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index b80610a76cc8281c878ff25c2796ce449e3fe2f7..4ccbc32c4b548670157354be31f6c56f7adc2d7c 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -816,7 +816,8 @@ public final class TelephonyPermissions { * @param callingUid pass Binder.callingUid(). */ public static void enforceShellOnly(int callingUid, String message) { - if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { + if (UserHandle.isSameApp(callingUid, Process.SHELL_UID) + || UserHandle.isSameApp(callingUid, Process.ROOT_UID)) { return; // okay } diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index e20e4d2002519f718fc46ac5769c030da47e7b10..e42b41f90f4ac0c8a094fdfead75b335df41b6d2 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -159,7 +159,7 @@ public final class BarringInfo implements Parcelable { /** * @return the conditional barring factor as a percentage 0-100, which is the probability of - * a random device being barred for the service type. + * a random device being allowed for a conditionally barred service. */ public int getConditionalBarringFactor() { return mConditionalBarringFactor; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 41223db750c08ce8e4a27ef509177666e1091950..2ef05735003361ebe17c87320c1f5b2714da2263 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5038,7 +5038,7 @@ public class CarrierConfigManager { * {@code true} - Enable NI SUPL message injection. */ @FlaggedApi(android.location.flags.Flags - .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG) + .FLAG_ENABLE_NI_SUPL_MESSAGE_INJECTION_BY_CARRIER_CONFIG_BUGFIX) public static final String KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL = KEY_PREFIX + "enable_ni_supl_message_injection_bool"; @@ -5059,7 +5059,7 @@ public class CarrierConfigManager { defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, SUPL_EMERGENCY_MODE_TYPE_CP_ONLY); defaults.putStringArray(KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, null); - if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfig()) { + if (android.location.flags.Flags.enableNiSuplMessageInjectionByCarrierConfigBugfix()) { defaults.putBoolean(KEY_ENABLE_NI_SUPL_MESSAGE_INJECTION_BOOL, false); } return defaults; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 51e0c33ff70523f50672d8349f19129f175e7c4e..6faef7ecfa1bc4107df0ca005229a7861c12f415 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3785,7 +3785,7 @@ public class SubscriptionManager { } private boolean isSystemProcess() { - return Process.myUid() == Process.SYSTEM_UID; + return UserHandle.isSameApp(Process.myUid(), Process.SYSTEM_UID); } /** diff --git a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl index 66a20ae28f39f254a07f869a0607ff01b730667a..50e3a0e4a79d4c83d5791567cc375bba37ffa1be 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl @@ -34,4 +34,12 @@ oneway interface ISatelliteModemStateCallback { * @param isEmergency True means satellite enabled for emergency mode, false otherwise. */ void onEmergencyModeChanged(in boolean isEmergency); + + /** + * Indicates that the satellite registration failed with following failure code + * + * @param causeCode the primary failure cause code of the procedure. + * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 + */ + void onRegistrationFailure(in int causeCode); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 90dae3be058c7b96779a274f0a1b321dedd79dab..4eefaaca71f4662b9759d075ced65fdaebf6d1c2 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -19,6 +19,7 @@ package android.telephony.satellite; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1579,6 +1580,13 @@ public final class SatelliteManager { executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onEmergencyModeChanged(isEmergency))); } + + @Hide + @Override + public void onRegistrationFailure(int causeCode) { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onRegistrationFailure(causeCode))); + } }; sSatelliteModemStateCallbackMap.put(callback, internalCallback); return telephony.registerForSatelliteModemStateChanged(internalCallback); diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java index 423a7859dd6b6a47b67b27d6f924ce686037646c..13af4694389b96fba5596201c0269e4a0bc9ccc8 100644 --- a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java @@ -45,4 +45,13 @@ public interface SatelliteModemStateCallback { */ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) default void onEmergencyModeChanged(boolean isEmergency) {}; + + /** + * Indicates that the satellite registration failed with following failure code + * + * @param causeCode the primary failure cause code of the procedure. + * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 + * @hide + */ + default void onRegistrationFailure(int causeCode) {}; } diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt index 92b6b934874f33216580c4606809228cb19de470..82e53c81daaaa22862f9662e4c6373426df804ec 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt @@ -54,7 +54,7 @@ class ShowImeOnUnlockScreenTest(flicker: LegacyFlickerTest) : BaseTest(flicker) } transitions { device.sleep() - wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() + wmHelper.StateSyncBuilder().withKeyguardShowing().waitForAndVerify() UnlockScreenRule.unlockScreen(device) wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt index 3f6a0bf49eb4885606c9d763ff331e57ef1447f7..c77413b6a55a28acf2123cd26e94d38e1ec5f53c 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.Insets import android.graphics.Rect import android.graphics.Region +import android.os.SystemClock import android.platform.uiautomator_helpers.DeviceHelpers import android.tools.device.apphelpers.IStandardAppHelper import android.tools.helpers.SYSTEMUI_PACKAGE @@ -27,11 +28,14 @@ import android.tools.traces.parsers.WindowManagerStateHelper import android.tools.traces.wm.WindowingMode import android.view.WindowInsets import android.view.WindowManager +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.MotionEventHelper.InputMethod.TOUCH +import com.android.window.flags.Flags import java.time.Duration /** @@ -69,13 +73,22 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : fun enterDesktopWithDrag( wmHelper: WindowManagerStateHelper, device: UiDevice, + motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH) ) { innerHelper.launchViaIntent(wmHelper) - dragToDesktop(wmHelper, device) + dragToDesktop( + wmHelper = wmHelper, + device = device, + motionEventHelper = motionEventHelper + ) waitForAppToMoveToDesktop(wmHelper) } - private fun dragToDesktop(wmHelper: WindowManagerStateHelper, device: UiDevice) { + private fun dragToDesktop( + wmHelper: WindowManagerStateHelper, + device: UiDevice, + motionEventHelper: MotionEventHelper + ) { val windowRect = wmHelper.getWindowRegion(innerHelper).bounds val startX = windowRect.centerX() @@ -88,7 +101,17 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : val endY = displayRect.centerY() / 2 // drag the window to move to desktop - device.drag(startX, startY, startX, endY, 100) + if (motionEventHelper.inputMethod == TOUCH + && Flags.enableHoldToDragAppHandle()) { + // Touch requires hold-to-drag. + val downTime = SystemClock.uptimeMillis() + motionEventHelper.actionDown(startX, startY, time = downTime) + SystemClock.sleep(100L) // hold for 100ns before starting the move. + motionEventHelper.actionMove(startX, startY, startX, endY, 100, downTime = downTime) + motionEventHelper.actionUp(startX, endY, downTime = downTime) + } else { + device.drag(startX, startY, startX, endY, 100) + } } private fun getMaximizeButtonForTheApp(caption: UiObject2?): UiObject2 { @@ -220,9 +243,10 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : val endY = startY + verticalChange val endX = startX + horizontalChange - motionEvent.actionDown(startX, startY) - motionEvent.actionMove(startX, startY, endX, endY, /* steps= */100) - motionEvent.actionUp(endX, endY) + val downTime = SystemClock.uptimeMillis() + motionEvent.actionDown(startX, startY, time = downTime) + motionEvent.actionMove(startX, startY, endX, endY, /* steps= */100, downTime = downTime) + motionEvent.actionUp(endX, endY, downTime = downTime) wmHelper .StateSyncBuilder() .withAppTransitionIdle() diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt index 08353989090697a1a65051e4f416fcce54ee6d7d..86a0b0f8c66e59c7d202c658c1673d3b0af53f89 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt @@ -21,6 +21,7 @@ import android.os.SystemClock import android.view.ContentInfo.Source import android.view.InputDevice.SOURCE_MOUSE import android.view.InputDevice.SOURCE_STYLUS +import android.view.InputDevice.SOURCE_TOUCHSCREEN import android.view.MotionEvent import android.view.MotionEvent.ACTION_DOWN import android.view.MotionEvent.ACTION_MOVE @@ -36,23 +37,24 @@ import android.view.MotionEvent.ToolType */ class MotionEventHelper( private val instr: Instrumentation, - private val inputMethod: InputMethod + val inputMethod: InputMethod ) { enum class InputMethod(@ToolType val toolType: Int, @Source val source: Int) { STYLUS(TOOL_TYPE_STYLUS, SOURCE_STYLUS), MOUSE(TOOL_TYPE_MOUSE, SOURCE_MOUSE), - TOUCHPAD(TOOL_TYPE_FINGER, SOURCE_MOUSE) + TOUCHPAD(TOOL_TYPE_FINGER, SOURCE_MOUSE), + TOUCH(TOOL_TYPE_FINGER, SOURCE_TOUCHSCREEN) } - fun actionDown(x: Int, y: Int) { - injectMotionEvent(ACTION_DOWN, x, y) + fun actionDown(x: Int, y: Int, time: Long = SystemClock.uptimeMillis()) { + injectMotionEvent(ACTION_DOWN, x, y, downTime = time, eventTime = time) } - fun actionUp(x: Int, y: Int) { - injectMotionEvent(ACTION_UP, x, y) + fun actionUp(x: Int, y: Int, downTime: Long) { + injectMotionEvent(ACTION_UP, x, y, downTime = downTime) } - fun actionMove(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int) { + fun actionMove(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int, downTime: Long) { val incrementX = (endX - startX).toFloat() / (steps - 1) val incrementY = (endY - startY).toFloat() / (steps - 1) @@ -61,14 +63,19 @@ class MotionEventHelper( val x = startX + incrementX * i val y = startY + incrementY * i - val moveEvent = getMotionEvent(time, time, ACTION_MOVE, x, y) + val moveEvent = getMotionEvent(downTime, time, ACTION_MOVE, x, y) injectMotionEvent(moveEvent) } } - private fun injectMotionEvent(action: Int, x: Int, y: Int): MotionEvent { - val eventTime = SystemClock.uptimeMillis() - val event = getMotionEvent(eventTime, eventTime, action, x.toFloat(), y.toFloat()) + private fun injectMotionEvent( + action: Int, + x: Int, + y: Int, + downTime: Long = SystemClock.uptimeMillis(), + eventTime: Long = SystemClock.uptimeMillis() + ): MotionEvent { + val event = getMotionEvent(downTime, eventTime, action, x.toFloat(), y.toFloat()) injectMotionEvent(event) return event } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..69fde0168b14269fbe7ea2dda74e3ace2cc8378e --- /dev/null +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt @@ -0,0 +1,126 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.helpers.SYSTEMUI_PACKAGE +import android.tools.traces.component.ComponentNameMatcher +import android.tools.traces.parsers.WindowManagerStateHelper +import android.tools.traces.parsers.toFlickerComponent +import android.util.Log +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.UiObjectNotFoundException +import androidx.test.uiautomator.UiScrollable +import androidx.test.uiautomator.UiSelector +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import java.util.regex.Pattern + +class StartMediaProjectionAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.StartMediaProjectionActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.StartMediaProjectionActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { + private val packageManager = instr.context.packageManager + + fun startEntireScreenMediaProjection(wmHelper: WindowManagerStateHelper) { + clickStartMediaProjectionButton() + chooseEntireScreenOption() + startScreenSharing() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } + + fun startSingleAppMediaProjection( + wmHelper: WindowManagerStateHelper, + targetApp: StandardAppHelper + ) { + clickStartMediaProjectionButton() + chooseSingleAppOption() + startScreenSharing() + selectTargetApp(targetApp.appName) + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceAppeared(targetApp) + .waitForAndVerify() + } + + private fun clickStartMediaProjectionButton() { + findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() } + } + + private fun chooseEntireScreenOption() { + findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } + + val entireScreenString = getSysUiResourceString(ENTIRE_SCREEN_STRING_RES_NAME) + findObject(By.text(entireScreenString)).also { it.click() } + } + + private fun selectTargetApp(targetAppName: String) { + // Scroll to to find target app to launch then click app icon it to start capture + val scrollable = UiScrollable(UiSelector().scrollable(true)) + try { + scrollable.scrollForward() + if (!scrollable.scrollIntoView(UiSelector().text(targetAppName))) { + Log.e(TAG, "Didn't find target app when scrolling") + return + } + } catch (e: UiObjectNotFoundException) { + Log.d(TAG, "There was no scrolling (UI may not be scrollable") + } + + findObject(By.text(targetAppName)).also { it.click() } + } + + private fun chooseSingleAppOption() { + findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } + + val singleAppString = getSysUiResourceString(SINGLE_APP_STRING_RES_NAME) + findObject(By.text(singleAppString)).also { it.click() } + } + + private fun startScreenSharing() { + findObject(By.res(ACCEPT_RESOURCE_ID)).also { it.click() } + } + + private fun findObject(selector: BySelector): UiObject2 = + uiDevice.wait(Until.findObject(selector), TIMEOUT) ?: error("Can't find object $selector") + + private fun getSysUiResourceString(resName: String): String = + with(packageManager.getResourcesForApplication(SYSTEMUI_PACKAGE)) { + getString(getIdentifier(resName, "string", SYSTEMUI_PACKAGE)) + } + + companion object { + const val TAG: String = "StartMediaProjectionAppHelper" + const val TIMEOUT: Long = 5000L + const val ACCEPT_RESOURCE_ID: String = "android:id/button1" + const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp" + val SCREEN_SHARE_OPTIONS_PATTERN: Pattern = + Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)") + const val ENTIRE_SCREEN_STRING_RES_NAME: String = + "screen_share_permission_dialog_option_entire_screen" + const val SINGLE_APP_STRING_RES_NAME: String = + "screen_share_permission_dialog_option_single_app" + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp index e3b23b986c8308c4aa83ebf2692754873dda905b..c55df8604362ea6e7f63ae2be2ff56bf50edff5c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/Android.bp +++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp @@ -19,6 +19,7 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_windowing_tools", } android_test { @@ -46,6 +47,7 @@ android_test { "wm-flicker-common-app-helpers", "wm-flicker-common-assertions", "wm-flicker-window-extensions", + "wm-shell-flicker-utils", ], } diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 45260bddd355ec1180f9266915ee38a7a8bdda7c..f891606f00665e03a0f539c4102cbd77e698ebb5 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -21,6 +21,15 @@ android:targetSdkVersion="35"/> + + + + + + + + + @@ -106,6 +115,17 @@ + + + + + + + + diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml new file mode 100644 index 0000000000000000000000000000000000000000..46f01e6c97527cde9b8efecb4b4c30af3e590b39 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml @@ -0,0 +1,32 @@ + + + + +