diff options
1134 files changed, 26301 insertions, 11406 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index da86a2630e62..fd9915751695 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -14,6 +14,7 @@ aconfig_srcjars = [ ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + ":android.companion.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", @@ -38,12 +39,14 @@ aconfig_srcjars = [ ":hwui_flags_java_lib{.generated_srcjars}", ":framework_graphics_flags_java_lib{.generated_srcjars}", ":display_flags_lib{.generated_srcjars}", + ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}", ":android.multiuser.flags-aconfig-java{.generated_srcjars}", ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", + ":com.android.net.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -244,6 +247,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "aconfig_view_flags_c_lib", + aconfig_declarations: "android.view.flags-aconfig", +} + // View.accessibility aconfig_declarations { name: "android.view.accessibility.flags-aconfig", @@ -288,6 +296,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.content.pm.flags-aconfig-java-host", + aconfig_declarations: "android.content.pm.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media BetterTogether aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", @@ -312,6 +327,11 @@ java_aconfig_library { name: "android.permission.flags-aconfig-java", aconfig_declarations: "android.permission.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.permission", + ], + } // Biometrics @@ -347,6 +367,12 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "com.android.internal.foldables.flags-aconfig-java", + aconfig_declarations: "fold_lock_setting_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Multi user aconfig_declarations { name: "android.multiuser.flags-aconfig", @@ -440,3 +466,23 @@ java_aconfig_library { aconfig_declarations: "android.service.autofill.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Companion +aconfig_declarations { + name: "android.companion.flags-aconfig", + package: "android.companion", + srcs: ["core/java/android/companion/*.aconfig"], +} + +java_aconfig_library { + name: "android.companion.flags-aconfig-java", + aconfig_declarations: "android.companion.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + +// CoreNetworking +java_aconfig_library { + name: "com.android.net.flags-aconfig-java", + aconfig_declarations: "com.android.net.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index b1b332a9a2b5..a507465aa419 100644 --- a/Android.bp +++ b/Android.bp @@ -64,6 +64,7 @@ filegroup { srcs: [ // Java/AIDL sources under frameworks/base ":framework-annotations", + ":ravenwood-annotations", ":framework-blobstore-sources", ":framework-core-sources", ":framework-drm-sources", @@ -284,6 +285,7 @@ java_defaults { enforce_permissions_exceptions: [ // Do not add entries to this list. ":framework-annotations", + ":ravenwood-annotations", ":framework-blobstore-sources", ":framework-core-sources", ":framework-drm-sources", @@ -409,7 +411,6 @@ java_defaults { "audiopolicy-aidl-java", "sounddose-aidl-java", "modules-utils-expresslog", - "hoststubgen-annotations", ], } @@ -838,4 +839,5 @@ build = [ "AconfigFlags.bp", "ProtoLibraries.bp", "TestProtoLibraries.bp", + "Ravenwood.bp", ] diff --git a/Android.mk b/Android.mk index d9e202c9305c..e2c1ed8e9ddb 100644 --- a/Android.mk +++ b/Android.mk @@ -69,9 +69,6 @@ $(SDK_METADATA): $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/framework-doc-stub .PHONY: framework-doc-stubs framework-doc-stubs: $(SDK_METADATA) -# Run this for checkbuild -checkbuild: doc-comment-check-docs - # Include subdirectory makefiles # ============================================================ @@ -34,3 +34,6 @@ per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS per-file ZYGOTE_OWNERS = file:/ZYGOTE_OWNERS per-file SQLITE_OWNERS = file:/SQLITE_OWNERS + +per-file *ravenwood* = file:ravenwood/OWNERS +per-file *Ravenwood* = file:ravenwood/OWNERS diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index bded26a8748f..015487d20f8d 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -25,6 +25,6 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} -ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES} +ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES} ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} diff --git a/Ravenwood.bp b/Ravenwood.bp new file mode 100644 index 000000000000..9218cc9bc3f8 --- /dev/null +++ b/Ravenwood.bp @@ -0,0 +1,70 @@ +// 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. + +// We need this "trampoline" rule to force soong to give a host-side jar to +// framework-minus-apex.ravenwood. Otherwise, soong would mix up the arch (?) and we'd get +// a dex jar. +java_library { + name: "framework-minus-apex-for-hoststubgen", + installable: false, // host only jar. + static_libs: [ + "framework-minus-apex", + ], + sdk_version: "core_platform", + visibility: ["//visibility:private"], +} + +// Generate the stub/impl from framework-all, with hidden APIs. +java_genrule_host { + name: "framework-minus-apex.ravenwood-base", + tools: ["hoststubgen"], + cmd: "$(location hoststubgen) " + + "@$(location :ravenwood-standard-options) " + + + "--out-stub-jar $(location ravenwood_stub.jar) " + + "--out-impl-jar $(location ravenwood.jar) " + + + "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " + + "--gen-input-dump-file $(location hoststubgen_dump.txt) " + + + "--in-jar $(location :framework-minus-apex-for-hoststubgen) " + + "--policy-override-file $(location framework-minus-apex-ravenwood-policies.txt) ", + srcs: [ + ":framework-minus-apex-for-hoststubgen", + "framework-minus-apex-ravenwood-policies.txt", + ":ravenwood-standard-options", + ], + out: [ + "ravenwood.jar", + "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional. + + // Following files are created just as FYI. + "hoststubgen_keep_all.txt", + "hoststubgen_dump.txt", + ], + visibility: ["//visibility:private"], +} + +// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules. +java_genrule_host { + name: "framework-minus-apex.ravenwood", + cmd: "cp $(in) $(out)", + srcs: [ + ":framework-minus-apex.ravenwood-base{ravenwood.jar}", + ], + out: [ + "framework-minus-apex.ravenwood.jar", + ], + visibility: ["//visibility:public"], +} diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 4e3cb7d83451..3e835b8ad429 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -2,7 +2,7 @@ package: "com.android.server.job" flag { name: "relax_prefetch_connectivity_constraint_only_on_charger" - namespace: "backstagepower" + namespace: "backstage_power" description: "Only relax a prefetch job's connectivity constraint when the device is charging" bug: "299329948" }
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 2b7438c862bd..fdeb072cacb3 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -536,10 +536,13 @@ public class PrefetchController extends StateController { static final String KEY_LAUNCH_TIME_ALLOWANCE_MS = PC_CONSTANT_PREFIX + "launch_time_allowance_ms"; - private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS; - private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 20 * MINUTE_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = HOUR_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 30 * MINUTE_IN_MILLIS; - /** How much time each app will have to run jobs within their standby bucket window. */ + /** + * The earliest amount of time before the next estimated app launch time that we may choose + * to run a prefetch job for the app. + */ public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS; /** diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 8d8fc12ebc4d..30b442336148 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -20,41 +20,6 @@ // The API doc generation is done by the various droiddoc modules each of which // is for different format. -///////////////////////////////////////////////////////////////////// -// stub source files are generated using metalava -///////////////////////////////////////////////////////////////////// - -framework_docs_only_libs = [ - "voip-common", - "android.test.mock", - "android-support-annotations", - "android-support-compat", - "android-support-core-ui", - "android-support-core-utils", - "android-support-design", - "android-support-dynamic-animation", - "android-support-exifinterface", - "android-support-fragment", - "android-support-media-compat", - "android-support-percent", - "android-support-transition", - "android-support-v7-cardview", - "android-support-v7-gridlayout", - "android-support-v7-mediarouter", - "android-support-v7-palette", - "android-support-v7-preference", - "android-support-v13", - "android-support-v14-preference", - "android-support-v17-leanback", - "android-support-vectordrawable", - "android-support-animatedvectordrawable", - "android-support-v7-appcompat", - "android-support-v7-recyclerview", - "android-support-v8-renderscript", - "android-support-multidex", - "android-support-multidex-instrumentation", -] - // These defaults enable doc-stub generation, api lint database generation and sdk value generation. stubs_defaults { name: "android-non-updatable-doc-stubs-defaults", @@ -65,7 +30,6 @@ stubs_defaults { ":android-test-mock-sources", ":android-test-runner-sources", ], - libs: framework_docs_only_libs, create_doc_stubs: true, write_sdk_values: true, } @@ -197,7 +161,7 @@ doc_defaults { name: "framework-docs-default", sdk_version: "none", system_modules: "none", - libs: framework_docs_only_libs + [ + libs: [ "stub-annotations", "unsupportedappusage", ], @@ -236,20 +200,6 @@ doc_defaults { }, } -doc_defaults { - name: "framework-dokka-docs-default", -} - -droiddoc { - name: "doc-comment-check-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - args: framework_docs_only_args + " -referenceonly -parsecomments", - installable: false, -} - droiddoc { name: "offline-sdk-docs", defaults: ["framework-docs-default"], @@ -303,70 +253,6 @@ droiddoc { } droiddoc { - name: "online-sdk-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-sdk-docs-proofread.txt", - args: framework_docs_only_args + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", -} - -droiddoc { - name: "online-system-api-sdk-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-system-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-system-api-sdk-docs-proofread.txt", - args: framework_docs_only_args + - " -referenceonly " + - " -title \"Android SDK - Including system APIs.\" " + - " -hide 101 " + - " -hide 104 " + - " -hide 108 " + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", - installable: false, -} - -droiddoc { name: "ds-docs-java", defaults: ["framework-docs-default"], srcs: [ @@ -397,7 +283,6 @@ droiddoc { droiddoc { name: "ds-docs-kt", - defaults: ["framework-dokka-docs-default"], srcs: [ ":framework-doc-stubs", ], @@ -476,44 +361,3 @@ droiddoc { " -atLinksNavtree " + " -navtreeonly ", } - -droiddoc { - name: "online-sdk-dev-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-sdk-dev-docs-proofread.txt", - args: framework_docs_only_args + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", -} - -droiddoc { - name: "hidden-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - proofread_file: "hidden-docs-proofread.txt", - args: framework_docs_only_args + - " -referenceonly " + - " -title \"Android SDK - Including hidden APIs.\"", -} diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 2d9c988556ec..fa4bc0f98fa5 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -438,6 +438,26 @@ java_api_library { full_api_surface_stub: "android_module_lib_stubs_current_full.from-text", } +// This module generates a stub jar that is a union of the test and module lib +// non-updatable api contributions. Modules should not depend on the stub jar +// generated from this module, as this module is strictly used for hiddenapi only. +java_api_library { + name: "android-non-updatable.stubs.test_module_lib", + api_surface: "module_lib", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "test-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + ], + defaults: ["android-non-updatable_from_text_defaults"], + full_api_surface_stub: "android_test_module_lib_stubs_current.from-text", + + // This module is only used for hiddenapi, and other modules should not + // depend on this module. + visibility: ["//visibility:private"], +} + java_defaults { name: "android_stubs_dists_default", dist: { @@ -757,6 +777,30 @@ java_api_library { } java_api_library { + name: "android_test_module_lib_stubs_current.from-text", + api_surface: "module-lib", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "android_test_stubs_current_contributions", + "android_module_lib_stubs_current_contributions", + ], + libs: [ + "android_module_lib_stubs_current_full.from-text", + "stub-annotations", + ], + api_contributions: [ + "test-api-stubs-docs-non-updatable.api.contribution", + ], + + // This module is only used to build android-non-updatable.stubs.test_module_lib + // and other modules should not depend on this module. + visibility: [ + "//visibility:private", + ], +} + +java_api_library { name: "android_system_server_stubs_current.from-text", api_surface: "system-server", api_contributions: [ diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 762d9d1c3da1..c28480e4f11a 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -8,9 +8,12 @@ android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolv android/adservices/ondevicepersonalization/ExecuteOutput.java:31: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101] android/adservices/ondevicepersonalization/ExecuteOutput.java:93: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput.Builder [101] android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.IsolatedService [101] +android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedService [101] android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onWebViewEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101] +android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101] android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "WebView" in android.adservices.ondevicepersonalization.IsolatedService [101] android/adservices/ondevicepersonalization/IsolatedWorker.java:9: lint: Unresolved link/see tag "RunTimeException" in android.adservices.ondevicepersonalization.IsolatedWorker [101] +android/adservices/ondevicepersonalization/IsolatedWorker.java:24: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedWorker [101] android/adservices/ondevicepersonalization/IsolatedWorker.java:57: lint: Unresolved link/see tag "#onExecute()" in android.adservices.ondevicepersonalization.IsolatedWorker [101] android/adservices/ondevicepersonalization/IsolatedWorker.java:74: lint: Unresolved link/see tag "#onRender()" in android.adservices.ondevicepersonalization.IsolatedWorker [101] android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:-11: lint: Unresolved link/see tag "requestSurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] @@ -83,35 +86,12 @@ android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Featur android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101] android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101] android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101] -android/companion/CompanionDeviceService.java:273: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101] -android/companion/CompanionDeviceService.java:282: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101] -android/companion/virtual/VirtualDevice.java:15: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceManager.VirtualDevice VirtualDeviceManager.VirtualDevice" in android.companion.virtual.VirtualDevice [101] -android/companion/virtual/VirtualDevice.java:70: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceParams.Builder#setName(String)" in android.companion.virtual.VirtualDevice [101] android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101] android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101] android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] -android/content/om/OverlayIdentifier.java:20: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayIdentifier [101] -android/content/om/OverlayInfo.java:78: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayInfo [101] -android/content/om/OverlayManager.java:9: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction#commit()" in android.content.om.OverlayManager [101] -android/content/pm/PackageInstaller.java:2232: lint: Unresolved link/see tag "android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS INSTALL_GRANT_RUNTIME_PERMISSIONS" in android.content.pm.PackageInstaller.SessionParams [101] -android/content/pm/ServiceInfo.java:176: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setDataTransfer" in android.content.pm.ServiceInfo [101] -android/content/pm/verify/domain/DomainVerificationUserState.java:82: lint: Unresolved link/see tag "android.content.pm.verify.domain.DomainVerificationUserState.DomainState DomainState" in android.content.pm.verify.domain.DomainVerificationUserState [101] -android/content/res/Resources.java:958: lint: Unresolved link/see tag "android.annotation.UiContext" in android.content.res.Resources [101] -android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] -android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] -android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [101] android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101] android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101] android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101] @@ -162,8 +142,6 @@ android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.medi android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN}, {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN}, {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG}, {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY}, {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY}, {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101] -android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101] -android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101] android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101] android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101] android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101] @@ -209,90 +187,18 @@ android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app. android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101] android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101] android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101] -android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101] -android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101] -android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101] -android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101] -android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [101] android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101] android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101] android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101] android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101] -android/telecom/Call.java:94: lint: unable to parse link/see tag: #playDtmfTone(char [101] -android/telecom/CallControl.java:163: lint: Unresolved link/see tag "android.telecom.CallStreamingService CallStreamingService" in android.telecom.CallControl [101] -android/telecom/CallControlCallback.java:63: lint: Unresolved link/see tag "android.telecom.CallAttributes.CallType" in android.telecom.CallControlCallback [101] -android/telecom/PhoneAccountSuggestion.java:17: lint: Unresolved link/see tag "android.telecom.PhoneAccountSuggestionService PhoneAccountSuggestionService" in android.telecom.PhoneAccountSuggestion [101] -android/telephony/CarrierConfigManager.java:2934: lint: Unresolved link/see tag "android.telephony.TelephonyManager.PremiumCapability TelephonyManager.PremiumCapability" in android.telephony.CarrierConfigManager [101] -android/telephony/CarrierConfigManager.java:4020: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101] -android/telephony/CarrierConfigManager.java:4108: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101] -android/telephony/CarrierConfigManager.java:3920: lint: Unresolved link/see tag "android.telephony.ims.SipDelegateManager" in android.telephony.CarrierConfigManager.Ims [101] -android/telephony/CarrierConfigManager.java:4001: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101] -android/telephony/CarrierConfigManager.java:4089: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101] -android/telephony/NetworkRegistrationInfo.java:131: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.NetworkRegistrationInfo [101] -android/telephony/PhoneStateListener.java:293: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.PhoneStateListener [101] -android/telephony/PhoneStateListener.java:544: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.PhoneStateListener [101] -android/telephony/SubscriptionManager.java:1385: lint: Unresolved link/see tag "Build.VERSION_CODES.P" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101] -android/telephony/SubscriptionManager.java:1385: lint: Unresolved link/see tag "Build.VERSION_CODES.V" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101] -android/telephony/SubscriptionPlan.java:134: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.SubscriptionPlan [101] -android/telephony/SubscriptionPlan.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.SubscriptionPlan.Builder [101] -android/telephony/TelephonyCallback.java:113: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.TelephonyCallback.CallDisconnectCauseListener [101] -android/telephony/TelephonyManager.java:2544: lint: Unresolved link/see tag "android.telephony.TelephonyManager.CarrierRestrictionStatus CarrierRestrictionStatus" in android.telephony.TelephonyManager [101] -android/telephony/TelephonyManager.java:2841: lint: Unresolved link/see tag "android.telephony.TelephonyManager.SetOpportunisticSubscriptionResult TelephonyManager.SetOpportunisticSubscriptionResult" in android.telephony.TelephonyManager [101] -android/telephony/TelephonyManager.java:3273: lint: Unresolved link/see tag "android.telephony.TelephonyManager.PurchasePremiumCapabilityResult PurchasePremiumCapabilityResult" in android.telephony.TelephonyManager [101] -android/telephony/TelephonyManager.java:3989: lint: Unresolved link/see tag "android.telephony.TelephonyManager.MobileDataPolicy MobileDataPolicy" in android.telephony.TelephonyManager [101] -android/telephony/data/ApnSetting.java:39: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV4() DataCallResponse#getMtuV4()" in android.telephony.data.ApnSetting [101] -android/telephony/data/ApnSetting.java:50: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV6() DataCallResponse#getMtuV6()" in android.telephony.data.ApnSetting [101] -android/telephony/data/ApnSetting.java:597: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV4() DataCallResponse#getMtuV4()" in android.telephony.data.ApnSetting.Builder [101] -android/telephony/data/ApnSetting.java:611: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV6() DataCallResponse#getMtuV6()" in android.telephony.data.ApnSetting.Builder [101] -android/telephony/euicc/EuiccManager.java:331: lint: Unresolved link/see tag "android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE" in android.telephony.euicc.EuiccManager [101] -android/telephony/ims/ImsMmTelManager.java:117: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.ImsMmTelManager [101] -android/telephony/ims/ImsRcsManager.java:43: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.ImsRcsManager [101] -android/telephony/ims/ProvisioningManager.java:102: lint: Unresolved link/see tag "android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability MmTelFeature.MmTelCapabilities.MmTelCapability" in android.telephony.ims.ProvisioningManager [101] -android/telephony/ims/ProvisioningManager.java:102: lint: Unresolved link/see tag "android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.ims.ProvisioningManager [101] -android/telephony/ims/ProvisioningManager.java:136: lint: Unresolved link/see tag "android.telephony.ims.ImsRcsManager.RcsImsCapabilityFlag ImsRcsManager.RcsImsCapabilityFlag" in android.telephony.ims.ProvisioningManager [101] -android/telephony/ims/RegistrationManager.java:21: lint: Unresolved link/see tag "android.telephony.ims.feature.ImsFeature ImsFeature" in android.telephony.ims.RegistrationManager [101] -android/telephony/ims/RegistrationManager.java:24: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.RegistrationManager [101] android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101] android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101] -android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101] android/view/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101] -android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager" in android.view.ScrollFeedbackProvider [101] -android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager#getInputDeviceIds()" in android.view.ScrollFeedbackProvider [101] -android/view/SurfaceControl.java:823: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/SurfaceControl.java:900: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/SurfaceControl.java:908: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/View.java:1647: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)" in android.view.View [101] -android/view/View.java:4669: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)" in android.view.View [101] -android/view/View.java:4712: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)" in android.view.View [101] -android/view/WindowManager.java:230: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101] -android/view/WindowManager.java:247: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101] -android/view/WindowManager.java:822: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowManager.java:832: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowMetrics.java:22: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:57: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:114: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:127: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/accessibility/AccessibilityNodeInfo.java:368: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo [101] -android/view/accessibility/AccessibilityNodeInfo.java:3246: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction [101] -android/view/displayhash/DisplayHashResultCallback.java:38: lint: Unresolved link/see tag "android.view.displayhash.DisplayHashResultCallback.DisplayHashErrorCode DisplayHashErrorCode" in android.view.displayhash.DisplayHashResultCallback [101] -android/view/inputmethod/EditorInfo.java:107: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101] -android/view/inputmethod/EditorInfo.java:122: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101] -android/view/inputmethod/InputMethodManager.java:423: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inputmethod/InputMethodManager.java:447: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inputmethod/InputMethodManager.java:456: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101] android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101] android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101] -android/content/pm/ActivityInfo.java:1197: lint: Unresolved link/see tag "android.view.ViewRootImpl" in android.content.pm.ActivityInfo [101] android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101] android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101] @@ -300,22 +206,11 @@ android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/se android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101] android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101] android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101] -android/telephony/TelephonyRegistryManager.java:242: lint: Unresolved link/see tag "#listenFromListener" in android [101] -android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101] -android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101] com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101] -com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "DisplayManager" in android [101] -com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "VirtualDeviceManager.VirtualDevice" in android [101] com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101] com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101] com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101] com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101] com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101] com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101] @@ -323,14 +218,8 @@ com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/se com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101] com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101] com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101] -android/telephony/SubscriptionManager.java:1370: lint: Unresolved link/see tag "Build.VERSION_CODES.Q" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:2: lint: Invalid tag: @Override [131] android/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131] android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131] -android/view/WindowManager.java:906: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowManager.java:916: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103] diff --git a/config/preloaded-classes b/config/preloaded-classes index aa34bad29b26..7f8f5e3f362b 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6519,12 +6519,6 @@ android.security.Scrypt android.security.attestationverification.AttestationVerificationManager android.security.keymaster.ExportResult$1 android.security.keymaster.ExportResult -android.security.keymaster.IKeyAttestationApplicationIdProvider$Stub -android.security.keymaster.IKeyAttestationApplicationIdProvider -android.security.keymaster.KeyAttestationApplicationId$1 -android.security.keymaster.KeyAttestationApplicationId -android.security.keymaster.KeyAttestationPackageInfo$1 -android.security.keymaster.KeyAttestationPackageInfo android.security.keymaster.KeyCharacteristics$1 android.security.keymaster.KeyCharacteristics android.security.keymaster.KeymasterArgument$1 @@ -6549,7 +6543,13 @@ android.security.keystore.AttestationUtils android.security.keystore.BackendBusyException android.security.keystore.DelegatingX509Certificate android.security.keystore.DeviceIdAttestationException +android.security.keystore.IKeyAttestationApplicationIdProvider$Stub +android.security.keystore.IKeyAttestationApplicationIdProvider +android.security.keystore.KeyAttestationApplicationId$Stub +android.security.keystore.KeyAttestationApplicationId android.security.keystore.KeyAttestationException +android.security.keystore.KeyAttestationPackageInfo$Stub +android.security.keystore.KeyAttestationPackageInfo android.security.keystore.KeyExpiredException android.security.keystore.KeyGenParameterSpec$Builder android.security.keystore.KeyGenParameterSpec @@ -6572,6 +6572,8 @@ android.security.keystore.KeystoreResponse$1 android.security.keystore.KeystoreResponse android.security.keystore.ParcelableKeyGenParameterSpec$1 android.security.keystore.ParcelableKeyGenParameterSpec +android.security.keystore.Signature$Stub +android.security.keystore.Signature android.security.keystore.SecureKeyImportUnavailableException android.security.keystore.StrongBoxUnavailableException android.security.keystore.UserAuthArgs diff --git a/core/api/current.txt b/core/api/current.txt index a0aa09dcec55..6bab30c60b19 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public final class Manifest { @@ -9683,7 +9685,7 @@ package android.companion.virtual { method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName(); method @Nullable public String getName(); - method @Nullable public String getPersistentDeviceId(); + method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDevice> CREATOR; @@ -10992,6 +10994,7 @@ package android.content { field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED"; field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED"; field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED"; + field @FlaggedApi("android.content.pm.stay_stopped") public static final String ACTION_PACKAGE_UNSTOPPED = "android.intent.action.PACKAGE_UNSTOPPED"; field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED"; field public static final String ACTION_PASTE = "android.intent.action.PASTE"; field public static final String ACTION_PICK = "android.intent.action.PICK"; @@ -12673,6 +12676,7 @@ package android.content.pm { method public boolean isDeviceUpgrading(); method public abstract boolean isInstantApp(); method public abstract boolean isInstantApp(@NonNull String); + method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isPackageSuspended(); method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String); @@ -16086,10 +16090,12 @@ package android.graphics { method public String getFontFeatureSettings(); method public float getFontMetrics(android.graphics.Paint.FontMetrics); method public android.graphics.Paint.FontMetrics getFontMetrics(); + method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsForLocale(@NonNull android.graphics.Paint.FontMetrics); method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt); method public android.graphics.Paint.FontMetricsInt getFontMetricsInt(); + method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsIntForLocale(@NonNull android.graphics.Paint.FontMetricsInt); method public float getFontSpacing(); method public String getFontVariationSettings(); method public int getHinting(); @@ -17666,6 +17672,7 @@ package android.graphics.text { field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1 field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2 + field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_NO_BREAK = 4; // 0x4 field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3 field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0 @@ -28562,6 +28569,8 @@ package android.nfc { method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); method public boolean isSecureNfcSupported(); field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED"; @@ -31971,6 +31980,7 @@ package android.os { field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3 field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2 field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5 + field @FlaggedApi("android.os.state_of_health_public") public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6 field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2 field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3 @@ -41554,7 +41564,7 @@ package android.telecom { method public android.telecom.GatewayInfo getGatewayInfo(); method public android.net.Uri getHandle(); method public int getHandlePresentation(); - method @NonNull public String getId(); + method @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") @NonNull public String getId(); method public android.os.Bundle getIntentExtras(); method public final int getState(); method public android.telecom.StatusHints getStatusHints(); @@ -44213,7 +44223,7 @@ package android.telephony { field public static final int OUT_OF_NETWORK = 11; // 0xb field public static final int OUT_OF_SERVICE = 18; // 0x12 field public static final int POWER_OFF = 17; // 0x11 - field public static final int SATELLITE_ENABLED = 82; // 0x52 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_ENABLED = 82; // 0x52 field public static final int SERVER_ERROR = 12; // 0xc field public static final int SERVER_UNREACHABLE = 9; // 0x9 field public static final int TIMED_OUT = 13; // 0xd @@ -44322,7 +44332,7 @@ package android.telephony { method public boolean isNetworkRegistered(); method public boolean isNetworkRoaming(); method public boolean isNetworkSearching(); - method public boolean isNonTerrestrialNetwork(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isNonTerrestrialNetwork(); method @Deprecated public boolean isRegistered(); method @Deprecated public boolean isRoaming(); method @Deprecated public boolean isSearching(); @@ -44338,7 +44348,7 @@ package android.telephony { field public static final int NR_STATE_RESTRICTED = 1; // 0x1 field public static final int SERVICE_TYPE_DATA = 2; // 0x2 field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5 - field public static final int SERVICE_TYPE_MMS = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SERVICE_TYPE_MMS = 6; // 0x6 field public static final int SERVICE_TYPE_SMS = 3; // 0x3 field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4 @@ -44553,7 +44563,7 @@ package android.telephony { method public boolean getRoaming(); method public int getState(); method public boolean isSearching(); - method public boolean isUsingNonTerrestrialNetwork(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isUsingNonTerrestrialNetwork(); method public void setIsManualSelection(boolean); method public void setOperatorName(String, String, String); method public void setRoaming(boolean); @@ -45339,7 +45349,7 @@ package android.telephony { field public static final int ERI_FLASH = 2; // 0x2 field public static final int ERI_OFF = 1; // 0x1 field public static final int ERI_ON = 0; // 0x0 - field public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT"; field public static final String EXTRA_APN_PROTOCOL = "android.telephony.extra.APN_PROTOCOL"; field public static final String EXTRA_APN_TYPE = "android.telephony.extra.APN_TYPE"; @@ -46672,10 +46682,10 @@ package android.text { method @NonNull public android.text.DynamicLayout.Builder setHyphenationFrequency(int); method @NonNull public android.text.DynamicLayout.Builder setIncludePad(boolean); method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int); - method @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); + method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float); method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic); - method @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean); method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean); } @@ -47199,7 +47209,7 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int); method public android.text.StaticLayout.Builder setText(CharSequence); method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic); - method @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean); method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } @@ -47912,6 +47922,10 @@ package android.text.style { method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig(); } + @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final class LineBreakConfigSpan.NoBreakSpan extends android.text.style.LineBreakConfigSpan { + ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan.NoBreakSpan(); + } + @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final class LineBreakConfigSpan.NoHyphenationSpan extends android.text.style.LineBreakConfigSpan { ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan.NoHyphenationSpan(); } @@ -53115,6 +53129,7 @@ package android.view { method @Nullable public abstract android.view.View getCurrentFocus(); method @NonNull public abstract android.view.View getDecorView(); method public static int getDefaultFeatures(android.content.Context); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public android.transition.Transition getEnterTransition(); method public android.transition.Transition getExitTransition(); method protected final int getFeatures(); @@ -53184,6 +53199,7 @@ package android.view { method public abstract void setDecorCaptionShade(int); method public void setDecorFitsSystemWindows(boolean); method protected void setDefaultWindowFormat(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float); method public void setDimAmount(float); method public void setElevation(float); method public void setEnterTransition(android.transition.Transition); @@ -53533,6 +53549,7 @@ package android.view { method public int describeContents(); method public int getBlurBehindRadius(); method public int getColorMode(); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public int getFitInsetsSides(); method public int getFitInsetsTypes(); method public final CharSequence getTitle(); @@ -53542,6 +53559,7 @@ package android.view { method public void setBlurBehindRadius(@IntRange(from=0) int); method public void setCanPlayMoveAnimation(boolean); method public void setColorMode(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0f) float); method public void setFitInsetsIgnoringVisibility(boolean); method public void setFitInsetsSides(int); method public void setFitInsetsTypes(int); @@ -54652,7 +54670,6 @@ package android.view.autofill { public final class AutofillManager { method public void cancel(); - method public void clearAutofillRequestCallback(); method public void commit(); method public void disableAutofillServices(); method @Nullable public android.content.ComponentName getAutofillServiceComponentName(); @@ -54679,7 +54696,6 @@ package android.view.autofill { method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback); method public void requestAutofill(@NonNull android.view.View); method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect); - method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback); method public void setUserData(@Nullable android.service.autofill.UserData); method public boolean showAutofillDialog(@NonNull android.view.View); method public boolean showAutofillDialog(@NonNull android.view.View, int); @@ -54700,10 +54716,6 @@ package android.view.autofill { field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3 } - public interface AutofillRequestCallback { - method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback); - } - public final class AutofillValue implements android.os.Parcelable { method public int describeContents(); method public static android.view.autofill.AutofillValue forDate(long); @@ -55155,12 +55167,10 @@ package android.view.inputmethod { ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build(); - method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int); - method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList); } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 052d614fa5fc..500a12cacc3b 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -6,9 +8,7 @@ package android { field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS"; field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT"; field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; } } diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt index d802177e249b..14191ebcb080 100644 --- a/core/api/module-lib-removed.txt +++ b/core/api/module-lib-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/core/api/removed.txt b/core/api/removed.txt index 5a4be65ef559..e2b4e4dfc6c4 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.app { public class Notification implements android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a99eeb0c36d8..7dcc7b2cab13 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -297,6 +299,7 @@ package android { field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"; field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"; field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST"; + field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"; field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE"; field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String RECOVERY = "android.permission.RECOVERY"; @@ -652,7 +655,6 @@ package android.app { field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio"; - field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; @@ -3209,7 +3211,7 @@ package android.companion.virtual { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method public int getDeviceId(); - method @Nullable public String getPersistentDeviceId(); + method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId(); method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList(); method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); @@ -4529,7 +4531,7 @@ package android.hardware.display { method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); - field public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80 + field @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80 field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000 field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } @@ -9605,6 +9607,7 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn(); @@ -9704,7 +9707,6 @@ package android.os { field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7 - field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3 field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2 field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4 @@ -9772,8 +9774,8 @@ package android.os { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int); @@ -13228,7 +13230,7 @@ package android.telecom { method public void requestStreamingState(int); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR; - field public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID"; + field @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID"; field public static final int STATE_DISCONNECTED = 3; // 0x3 field public static final int STATE_HOLDING = 2; // 0x2 field public static final int STATE_STREAMING = 1; // 0x1 @@ -13709,7 +13711,7 @@ package android.telephony { method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean); - method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int); @@ -16679,157 +16681,157 @@ package android.telephony.mbms.vendor { package android.telephony.satellite { - public final class AntennaDirection implements android.os.Parcelable { - method public int describeContents(); - method public float getX(); - method public float getY(); - method public float getZ(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaDirection implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getX(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getY(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getZ(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR; } - public final class AntennaPosition implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection(); - method public int getSuggestedHoldPosition(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaPosition implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getSuggestedHoldPosition(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR; } - public final class PointingInfo implements android.os.Parcelable { - method public int describeContents(); - method public float getSatelliteAzimuthDegrees(); - method public float getSatelliteElevationDegrees(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteElevationDegrees(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR; } - public final class SatelliteCapabilities implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap(); - method public int getMaxBytesPerOutgoingDatagram(); - method @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies(); - method public boolean isPointingRequired(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteCapabilities implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getMaxBytesPerOutgoingDatagram(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isPointingRequired(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR; } - public final class SatelliteDatagram implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public byte[] getSatelliteDatagram(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public byte[] getSatelliteDatagram(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR; } - public interface SatelliteDatagramCallback { - method public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteDatagramCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); } - public final class SatelliteManager { + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager { method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); - field public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2 - field public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1 - field public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0 - field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2 - field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3 - field public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1 - field public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0 - field public static final int DISPLAY_MODE_CLOSED = 3; // 0x3 - field public static final int DISPLAY_MODE_FIXED = 1; // 0x1 - field public static final int DISPLAY_MODE_OPENED = 2; // 0x2 - field public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0 - field public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3 - field public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1 - field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 - field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 - field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_CLOSED = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_FIXED = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_OPENED = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7 - field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 - field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 - field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 - field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6 - field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 - field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 - field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff - field public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 - field public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 - field public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 - field public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 - field public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 - field public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 - field public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 - field public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5 - field public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11 - field public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13 - field public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12 - field public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14 - field public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc - field public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa - field public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf - field public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9 - field public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15 - field public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb - field public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2 - field public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3 - field public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd - field public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe - field public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0 - } - - public static class SatelliteManager.SatelliteException extends java.lang.Exception { - ctor public SatelliteManager.SatelliteException(int); - method public int getErrorCode(); - } - - public interface SatelliteProvisionStateCallback { - method public void onSatelliteProvisionStateChanged(boolean); - } - - public interface SatelliteStateCallback { - method public void onSatelliteModemStateChanged(int); - } - - public interface SatelliteTransmissionUpdateCallback { - method public void onReceiveDatagramStateChanged(int, int, int); - method public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo); - method public void onSendDatagramStateChanged(int, int, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0 + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static class SatelliteManager.SatelliteException extends java.lang.Exception { + ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public SatelliteManager.SatelliteException(int); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode(); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteStateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onReceiveDatagramStateChanged(int, int, int); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSendDatagramStateChanged(int, int, int); } } diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index aa17df3471d7..1fa2718dc6d2 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.app { public class AppOpsManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index aa48451fd24c..eeddeb21aa9d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -28,7 +30,6 @@ package android { field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES"; field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING"; field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE"; @@ -56,7 +57,6 @@ package android { field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; @@ -840,7 +840,7 @@ package android.appwidget { package android.companion { - public static final class AssociationInfo.Builder { + @FlaggedApi("android.companion.new_association_builder") public static final class AssociationInfo.Builder { ctor public AssociationInfo.Builder(int, int, @NonNull String); ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo); method @NonNull public android.companion.AssociationInfo build(); @@ -1181,6 +1181,7 @@ package android.credentials { method @Nullable public CharSequence getLabel(@NonNull android.content.Context); method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context); method @NonNull public android.content.pm.ServiceInfo getServiceInfo(); + method @FlaggedApi("android.credentials.flags.settings_activity_enabled") @Nullable public CharSequence getSettingsActivity(); method @Nullable public CharSequence getSettingsSubtitle(); method @NonNull public boolean hasCapability(@NonNull String); method public boolean isEnabled(); @@ -2957,10 +2958,6 @@ package android.service.notification { method @Deprecated public boolean isBound(); } - public class NotificationRankingUpdate implements android.os.Parcelable { - method public final boolean isFdNotNullAndClosed(); - } - } package android.service.quickaccesswallet { @@ -3190,7 +3187,7 @@ package android.telephony { field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2 field public static final int HAL_SERVICE_MODEM = 3; // 0x3 field public static final int HAL_SERVICE_NETWORK = 4; // 0x4 - field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8 field public static final int HAL_SERVICE_SIM = 5; // 0x5 field public static final int HAL_SERVICE_VOICE = 6; // 0x6 field public static final android.util.Pair HAL_VERSION_UNKNOWN; @@ -3283,12 +3280,12 @@ package android.text { } public class MeasuredParagraph { - method @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback); + method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback); } - public static interface MeasuredParagraph.StyleRunCallback { - method public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float); - method public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean); + @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static interface MeasuredParagraph.StyleRunCallback { + method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float); + method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean); } public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher { @@ -3410,7 +3407,7 @@ package android.view { public final class Choreographer { method public static long getFrameDelay(); - method public long getFrameTimeNanos(); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public long getFrameTimeNanos(); method public void postCallback(int, Runnable, Object); method public void postCallbackDelayed(int, Runnable, Object, long); method public void removeCallbacks(int, Runnable, Object); @@ -3571,8 +3568,8 @@ package android.view { method public default void holdLock(android.os.IBinder, int); method public default boolean isGlobalKey(int); method public default boolean isTaskSnapshotSupported(); - method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window); - method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window); + method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl); method public default void setDisplayImePolicy(int, int); method public default void setShouldShowSystemDecors(int, boolean); method public default void setShouldShowWithInsecureKeyguard(int, boolean); @@ -3629,7 +3626,7 @@ package android.view.accessibility { package android.view.animation { public class AnimationUtils { - method public static void lockAnimationClock(long, long); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static void lockAnimationClock(long, long); method public static void unlockAnimationClock(); } diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt index d802177e249b..14191ebcb080 100644 --- a/core/api/test-removed.txt +++ b/core/api/test-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/core/java/Android.bp b/core/java/Android.bp index 13a1bd6ca176..0293f66061c1 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -23,11 +23,6 @@ filegroup { visibility: ["//frameworks/base"], } -filegroup { - name: "IKeyAttestationApplicationIdProvider.aidl", - srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"], -} - aidl_library { name: "IDropBoxManagerService_aidl", srcs: [ @@ -431,6 +426,16 @@ aidl_interface { }, } +aidl_interface { + name: "android.companion.virtual.virtualdevice_aidl", + unstable: true, + host_supported: true, + srcs: [ + "android/companion/virtualnative/IVirtualDeviceManagerNative.aidl", + ], + local_include_dir: ".", +} + filegroup { name: "frameworks-base-java-overview", srcs: ["overview.html"], diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 3bf2ccaf9923..f68681b54e48 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4288,7 +4288,7 @@ public class ActivityManager { } /** - * Start monitoring changes to the imoportance of uids running in the system. + * Start monitoring changes to the importance of uids running in the system. * @param listener The listener callback that will receive change reports. * @param importanceCutpoint The level of importance in which the caller is interested * in differences. For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 9a90df93b2cd..e12181a08db3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -101,6 +101,7 @@ import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.ResourcesImpl; import android.content.res.loader.ResourcesLoader; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; @@ -297,6 +298,7 @@ public final class ActivityThread extends ClientTransactionHandler public static final boolean DEBUG_MEMORY_TRIM = false; private static final boolean DEBUG_PROVIDER = false; public static final boolean DEBUG_ORDER = false; + private static final boolean DEBUG_APP_INFO = true; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; /** * The delay to release the provider when it has no more references. It reduces the number of @@ -6473,10 +6475,35 @@ public final class ActivityThread extends ClientTransactionHandler resApk.updateApplicationInfo(ai, oldPaths); } + ResourcesImpl beforeImpl = getApplication().getResources().getImpl(); + synchronized (mResourcesManager) { // Update all affected Resources objects to use new ResourcesImpl mResourcesManager.applyAllPendingAppInfoUpdates(); } + + ResourcesImpl afterImpl = getApplication().getResources().getImpl(); + + if ((beforeImpl != afterImpl) && !Arrays.equals(beforeImpl.getAssets().getApkAssets(), + afterImpl.getAssets().getApkAssets())) { + List<String> beforeAssets = Arrays.asList(beforeImpl.getAssets().getApkPaths()); + List<String> afterAssets = Arrays.asList(afterImpl.getAssets().getApkPaths()); + + List<String> onlyBefore = new ArrayList<>(beforeAssets); + onlyBefore.removeAll(afterAssets); + List<String> onlyAfter = new ArrayList<>(afterAssets); + onlyAfter.removeAll(beforeAssets); + + Slog.i(TAG, "ApplicationInfo updating for " + ai.packageName + ", new timestamp: " + + ai.createTimestamp + "\nassets removed: " + onlyBefore + "\nassets added: " + + onlyAfter); + + if (DEBUG_APP_INFO) { + Slog.v(TAG, "ApplicationInfo updating for " + ai.packageName + + ", assets before change: " + beforeAssets + "\n assets after change: " + + afterAssets); + } + } } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1aa8ebedea3b..17637df90b99 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1478,7 +1478,8 @@ public class AppOpsManager { AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED; /** - * Allows the assistant app to receive the PCC-validated hotword and be voice-triggered. + * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection + * service. * * @hide */ @@ -2252,11 +2253,11 @@ public class AppOpsManager { public static final String OPSTR_USE_FULL_SCREEN_INTENT = "android:use_full_screen_intent"; /** - * Allows the assistant app to receive the PCC-validated hotword and be voice-triggered. + * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection + * service. * * @hide */ - @SystemApi public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; @@ -2379,7 +2380,8 @@ public class AppOpsManager { OP_RUN_USER_INITIATED_JOBS, OP_FOREGROUND_SERVICE_SPECIAL_USE, OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, - OP_USE_FULL_SCREEN_INTENT + OP_USE_FULL_SCREEN_INTENT, + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2810,7 +2812,8 @@ public class AppOpsManager { new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, "RECEIVE_SANDBOX_TRIGGER_AUDIO") - .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO) + .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(), new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA, OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA, "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build() diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index e5a73be5023a..21ed098f448a 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2954,6 +2954,17 @@ public class ApplicationPackageManager extends PackageManager { } } + @Override + public boolean isPackageStopped(@NonNull String packageName) throws NameNotFoundException { + try { + return mPM.isPackageStoppedForUser(packageName, getUserId()); + } catch (IllegalArgumentException ie) { + throw new NameNotFoundException(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ @Override public void setApplicationCategoryHint(String packageName, int categoryHint) { diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 7ee1332ea76a..15d692ab1673 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -272,6 +272,10 @@ public final class NotificationChannel implements Parcelable { private boolean mDemoted = false; private boolean mImportantConvo = false; private long mDeletedTime = DEFAULT_DELETION_TIME_MS; + /** Do not (de)serialize this value: it only affects logic in system_server and that logic + * is reset on each boot {@link NotificationAttentionHelper#buzzBeepBlinkLocked}. + */ + private long mLastNotificationUpdateTimeMs = 0; /** * Creates a notification channel. @@ -932,6 +936,23 @@ public final class NotificationChannel implements Parcelable { } /** + * Returns the time of the notification post or last update for this channel. + * @return time of post / last update + * @hide + */ + public long getLastNotificationUpdateTimeMs() { + return mLastNotificationUpdateTimeMs; + } + + /** + * Sets the time of the notification post or last update for this channel. + * @hide + */ + public void setLastNotificationUpdateTimeMs(long updateTimeMs) { + mLastNotificationUpdateTimeMs = updateTimeMs; + } + + /** * @hide */ public void populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled, @@ -1408,7 +1429,8 @@ public final class NotificationChannel implements Parcelable { + ", mParent=" + mParentId + ", mConversationId=" + mConversationId + ", mDemoted=" + mDemoted - + ", mImportantConvo=" + mImportantConvo; + + ", mImportantConvo=" + mImportantConvo + + ", mLastNotificationUpdateTimeMs=" + mLastNotificationUpdateTimeMs; } /** @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 79b68c1456c7..b8bea9d102e1 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -25,8 +25,12 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserHandleAware; import android.annotation.WorkerThread; import android.app.Notification.Builder; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -1659,23 +1663,42 @@ public class NotificationManager { } /** + * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the + * {@code setNotificationListenerAccessGranted} method will use the user contained within the + * context. + * For apps targeting an SDK version <em>below</em> this, the user of the calling process will + * be used (Process.myUserHandle()). + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE = 302563478L; + + /** * Grants/revokes Notification Listener access to the given component for current user. * To grant access for a particular user, obtain this service by using the {@link Context} * provided by {@link Context#createPackageContextAsUser} * * @param listener Name of component to grant/revoke access - * @param granted Grant/revoke access - * @param userSet Whether the action was triggered explicitly by user + * @param granted Grant/revoke access + * @param userSet Whether the action was triggered explicitly by user * @hide */ @SystemApi @TestApi + @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted( @NonNull ComponentName listener, boolean granted, boolean userSet) { INotificationManager service = getService(); try { - service.setNotificationListenerAccessGranted(listener, granted, userSet); + if (CompatChanges.isChangeEnabled(SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE)) { + service.setNotificationListenerAccessGrantedForUser(listener, mContext.getUserId(), + granted, userSet); + } else { + service.setNotificationListenerAccessGranted(listener, granted, userSet); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 456c6af134cf..7c69d376817c 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -1172,6 +1172,12 @@ public final class DevicePolicyResources { public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER"; /** + * Header for items under the private user + */ + public static final String PRIVATE_CATEGORY_HEADER = + PREFIX + "PRIVATE_CATEGORY_HEADER"; + + /** * Header for items under the personal user */ public static final String PERSONAL_CATEGORY_HEADER = @@ -1208,6 +1214,12 @@ public final class DevicePolicyResources { public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA"; /** + * Text for toggle to enable auto-sycing private data + */ + public static final String AUTO_SYNC_PRIVATE_DATA = PREFIX + + "AUTO_SYNC_PRIVATE_DATA"; + + /** * Summary for "More security settings" section when a work profile is on the device. */ public static final String MORE_SECURITY_SETTINGS_WORK_PROFILE_SUMMARY = PREFIX diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java new file mode 100644 index 000000000000..98281338872b --- /dev/null +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -0,0 +1,193 @@ +/* + * 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 android.app.servertransaction; + +import static android.view.Display.INVALID_DISPLAY; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import java.util.Objects; + +/** + * Message to deliver window resize info. + * @hide + */ +public class WindowStateResizeItem extends ClientTransactionItem { + + private IWindow mWindow; + private ClientWindowFrames mFrames; + private boolean mReportDraw; + private MergedConfiguration mConfiguration; + private InsetsState mInsetsState; + private boolean mForceLayout; + private boolean mAlwaysConsumeSystemBars; + private int mDisplayId; + private int mSyncSeqId; + private boolean mDragResizing; + + @Override + public void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { + try { + mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, + mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing); + } catch (RemoteException e) { + // Should be a local call. + throw new RuntimeException(e); + } + } + + // ObjectPoolItem implementation + + private WindowStateResizeItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowStateResizeItem obtain(@NonNull IWindow window, + @NonNull ClientWindowFrames frames, boolean reportDraw, + @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState, + boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, + boolean dragResizing) { + WindowStateResizeItem instance = + ObjectPool.obtain(WindowStateResizeItem.class); + if (instance == null) { + instance = new WindowStateResizeItem(); + } + instance.mWindow = requireNonNull(window); + instance.mFrames = requireNonNull(frames); + instance.mReportDraw = reportDraw; + instance.mConfiguration = requireNonNull(configuration); + instance.mInsetsState = requireNonNull(insetsState); + instance.mForceLayout = forceLayout; + instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + instance.mDisplayId = displayId; + instance.mSyncSeqId = syncSeqId; + instance.mDragResizing = dragResizing; + + return instance; + } + + @Override + public void recycle() { + mWindow = null; + mFrames = null; + mReportDraw = false; + mConfiguration = null; + mInsetsState = null; + mForceLayout = false; + mAlwaysConsumeSystemBars = false; + mDisplayId = INVALID_DISPLAY; + mSyncSeqId = -1; + mDragResizing = false; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mWindow.asBinder()); + dest.writeTypedObject(mFrames, flags); + dest.writeBoolean(mReportDraw); + dest.writeTypedObject(mConfiguration, flags); + dest.writeTypedObject(mInsetsState, flags); + dest.writeBoolean(mForceLayout); + dest.writeBoolean(mAlwaysConsumeSystemBars); + dest.writeInt(mDisplayId); + dest.writeInt(mSyncSeqId); + dest.writeBoolean(mDragResizing); + } + + /** Reads from Parcel. */ + private WindowStateResizeItem(@NonNull Parcel in) { + mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + mFrames = in.readTypedObject(ClientWindowFrames.CREATOR); + mReportDraw = in.readBoolean(); + mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR); + mInsetsState = in.readTypedObject(InsetsState.CREATOR); + mForceLayout = in.readBoolean(); + mAlwaysConsumeSystemBars = in.readBoolean(); + mDisplayId = in.readInt(); + mSyncSeqId = in.readInt(); + mDragResizing = in.readBoolean(); + } + + public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() { + public WindowStateResizeItem createFromParcel(@NonNull Parcel in) { + return new WindowStateResizeItem(in); + } + + public WindowStateResizeItem[] newArray(int size) { + return new WindowStateResizeItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowStateResizeItem other = (WindowStateResizeItem) o; + return Objects.equals(mWindow, other.mWindow) + && Objects.equals(mFrames, other.mFrames) + && mReportDraw == other.mReportDraw + && Objects.equals(mConfiguration, other.mConfiguration) + && Objects.equals(mInsetsState, other.mInsetsState) + && mForceLayout == other.mForceLayout + && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars + && mDisplayId == other.mDisplayId + && mSyncSeqId == other.mSyncSeqId + && mDragResizing == other.mDragResizing; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + Objects.hashCode(mFrames); + result = 31 * result + (mReportDraw ? 1 : 0); + result = 31 * result + Objects.hashCode(mConfiguration); + result = 31 * result + Objects.hashCode(mInsetsState); + result = 31 * result + (mForceLayout ? 1 : 0); + result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0); + result = 31 * result + mDisplayId; + result = 31 * result + mSyncSeqId; + result = 31 * result + (mDragResizing ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "WindowStateResizeItem{window=" + mWindow + + ", reportDrawn=" + mReportDraw + + ", configuration=" + mConfiguration + + "}"; + } +} diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 083fa0041b26..6393c456bdcd 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -15,6 +15,7 @@ */ package android.companion; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -412,6 +413,7 @@ public final class AssociationInfo implements Parcelable { * * @hide */ + @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER) @TestApi public static final class Builder { private final int mId; diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 03e75e9fc483..570ecaa47b4e 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -161,16 +161,16 @@ public abstract class CompanionDeviceService extends Service { public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; /** - * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will - * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a - * device has appeared on its own. + * A companion app for a self-managed device will receive the callback + * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its + * own. */ public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; /** - * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will - * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a - * device has disappeared on its own. + * A companion app for a self-managed device will receive the callback + * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on + * its own. */ public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig new file mode 100644 index 000000000000..b9e5609171c3 --- /dev/null +++ b/core/java/android/companion/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.companion" + +flag { + name: "new_association_builder" + namespace: "companion" + description: "Controls if the new Builder is exposed to test apis." + bug: "296251481" +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index ce883cddc952..93a3e7822888 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -33,9 +33,6 @@ import android.os.RemoteException; * * <p>Read-only device representation exposing the properties of an existing virtual device. * - * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used - * by the virtual device creator and allows them to manage the device. - * * @see VirtualDeviceManager#registerVirtualDeviceListener */ public final class VirtualDevice implements Parcelable { @@ -113,14 +110,13 @@ public final class VirtualDevice implements Parcelable { * <p class="note">This identifier may not be unique across virtual devices, in case there are * more than one virtual devices corresponding to the same physical device. */ + @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) public @Nullable String getPersistentDeviceId() { return mPersistentId; } /** * Returns the name of the virtual device (optionally) provided during its creation. - * - * @see VirtualDeviceParams.Builder#setName(String) */ public @Nullable String getName() { return mName; diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 39800f73058b..25693660e1ab 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -476,6 +476,7 @@ public final class VirtualDeviceManager { /** * Returns the persistent ID of this virtual device. */ + @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) public @Nullable String getPersistentDeviceId() { return mVirtualDeviceInternal.getPersistentDeviceId(); } diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index d0e13cd977ef..cf274f5cbbb9 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -8,6 +8,14 @@ flag { } flag { + name: "enable_native_vdm" + namespace: "virtual_devices" + description: "Enable native VDM service" + bug: "303535376" + is_fixed_read_only: true +} + +flag { name: "dynamic_policy" namespace: "virtual_devices" description: "Enable dynamic policy API" diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java index bf78dd09e7c2..b9451a74f9e9 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java @@ -46,15 +46,15 @@ import java.util.concurrent.atomic.AtomicLong; * <pre> * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter(); * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() { - * @Override + * {@literal @}Override * public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) { * writer.addChannel(channelHandle, sharedMemory); * } - * @Override + * {@literal @}Override * public void onDirectChannelDestroyed(int channelHandle); * writer.removeChannel(channelHandle); * } - * @Override + * {@literal @}Override * public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel, * int reportToken) * if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) { diff --git a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl new file mode 100644 index 000000000000..9f09d043a89b --- /dev/null +++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl @@ -0,0 +1,65 @@ +/* + * 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 android.companion.virtualnative; + +/** + * Parallel implementation of certain VirtualDeviceManager APIs that need to be exposed to native + * code. + * + * <p>These APIs are a parallel definition to the APIs in VirtualDeviceManager and/or + * VirtualDeviceManagerInternal, so they can technically diverge. However, it's good practice to + * keep these APIs in sync with each other.</p> + * + * <p>Even though the name implies otherwise, the implementation is actually in Java. The 'native' + * suffix comes from the intended usage - native framework backends that need to communicate with + * VDM for some reason.</p> + * + * <p>Because these APIs are exposed to native code that runs in the app process, they may be + * accessed by apps directly, even though they're hidden. Care should be taken to avoid exposing + * sensitive data or potential security holes.</p> + * + * @hide + */ +interface IVirtualDeviceManagerNative { + /** + * Counterpart to VirtualDeviceParams#DevicePolicy. + */ + const int DEVICE_POLICY_DEFAULT = 0; + const int DEVICE_POLICY_CUSTOM = 1; + + /** + * Counterpart to VirtualDeviceParams#PolicyType. + */ + const int POLICY_TYPE_SENSORS = 0; + const int POLICY_TYPE_AUDIO = 1; + const int POLICY_TYPE_RECENTS = 2; + const int POLICY_TYPE_ACTIVITY = 3; + + /** + * Returns the IDs for all VirtualDevices where an app with the given is running. + * + * Note that this returns only VirtualDevice IDs: if the app is not running on any virtual + * device, then an an empty array is returned. This does not include information about whether + * the app is running on the default device or not. + */ + int[] getDeviceIdsForUid(int uid); + + /** + * Returns the device policy for the given virtual device and policy type. + */ + int getDevicePolicy(int deviceId, int policyType); +}
\ No newline at end of file diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java index 36e0529e3566..3fbcd704308b 100644 --- a/core/java/android/content/ContentCaptureOptions.java +++ b/core/java/android/content/ContentCaptureOptions.java @@ -30,6 +30,11 @@ import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Content capture options for a given package. @@ -119,7 +124,10 @@ public final class ContentCaptureOptions implements Parcelable { /* enableReceiver= */ false, new ContentProtectionOptions( /* enableReceiver= */ false, - /* bufferSize= */ 0), + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0), /* whitelistedComponents= */ null); } @@ -141,9 +149,7 @@ public final class ContentCaptureOptions implements Parcelable { logHistorySize, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -183,9 +189,7 @@ public final class ContentCaptureOptions implements Parcelable { ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -386,9 +390,58 @@ public final class ContentCaptureOptions implements Parcelable { */ public final int bufferSize; - public ContentProtectionOptions(boolean enableReceiver, int bufferSize) { + /** + * The list of required groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> requiredGroups; + + /** + * The list of optional groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> optionalGroups; + + /** + * The minimal number of optional groups that have to be matched. This is the threshold + * value and comparison is done with greater than or equals. + * + * @hide + */ + public final int optionalGroupsThreshold; + + /** + * Empty constructor with default values. + * + * @hide + */ + public ContentProtectionOptions() { + this( + ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); + } + + /** + * Full primary constructor. + * + * @hide + */ + public ContentProtectionOptions( + boolean enableReceiver, + int bufferSize, + @NonNull List<List<String>> requiredGroups, + @NonNull List<List<String>> optionalGroups, + int optionalGroupsThreshold) { this.enableReceiver = enableReceiver; this.bufferSize = bufferSize; + this.requiredGroups = requiredGroups; + this.optionalGroups = optionalGroups; + this.optionalGroupsThreshold = optionalGroupsThreshold; } @Override @@ -398,7 +451,14 @@ public final class ContentCaptureOptions implements Parcelable { .append("enableReceiver=") .append(enableReceiver) .append(", bufferSize=") - .append(bufferSize); + .append(bufferSize) + .append(", requiredGroupsSize=") + .append(requiredGroups.size()) + .append(", optionalGroupsSize=") + .append(optionalGroups.size()) + .append(", optionalGroupsThreshold=") + .append(optionalGroupsThreshold); + return stringBuilder.append(']').toString(); } @@ -407,17 +467,50 @@ public final class ContentCaptureOptions implements Parcelable { pw.print(enableReceiver); pw.print(", bufferSize="); pw.print(bufferSize); + pw.print(", requiredGroupsSize="); + pw.print(requiredGroups.size()); + pw.print(", optionalGroupsSize="); + pw.print(optionalGroups.size()); + pw.print(", optionalGroupsThreshold="); + pw.print(optionalGroupsThreshold); } - private void writeToParcel(Parcel parcel) { + private void writeToParcel(@NonNull Parcel parcel) { parcel.writeBoolean(enableReceiver); parcel.writeInt(bufferSize); + writeGroupsToParcel(requiredGroups, parcel); + writeGroupsToParcel(optionalGroups, parcel); + parcel.writeInt(optionalGroupsThreshold); } - private static ContentProtectionOptions createFromParcel(Parcel parcel) { + @NonNull + private static ContentProtectionOptions createFromParcel(@NonNull Parcel parcel) { boolean enableReceiver = parcel.readBoolean(); int bufferSize = parcel.readInt(); - return new ContentProtectionOptions(enableReceiver, bufferSize); + List<List<String>> requiredGroups = createGroupsFromParcel(parcel); + List<List<String>> optionalGroups = createGroupsFromParcel(parcel); + int optionalGroupsThreshold = parcel.readInt(); + return new ContentProtectionOptions( + enableReceiver, + bufferSize, + requiredGroups, + optionalGroups, + optionalGroupsThreshold); + } + + private static void writeGroupsToParcel( + @NonNull List<List<String>> groups, @NonNull Parcel parcel) { + parcel.writeInt(groups.size()); + groups.forEach(parcel::writeStringList); + } + + @NonNull + private static List<List<String>> createGroupsFromParcel(@NonNull Parcel parcel) { + int size = parcel.readInt(); + return IntStream.range(0, size) + .mapToObj(i -> new ArrayList<String>()) + .peek(parcel::readStringList) + .collect(Collectors.toUnmodifiableList()); } } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 44a9acd6ba2f..5f4c05f001ff 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2790,6 +2790,20 @@ public class Intent implements Parcelable, Cloneable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED"; + + /** + * Broadcast Action: An application package that was previously in the stopped state has been + * started and is no longer considered stopped. + * <ul> + * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. + * </ul> + * + * <p class="note">This is a protected intent that can only be sent by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED) + public static final String ACTION_PACKAGE_UNSTOPPED = "android.intent.action.PACKAGE_UNSTOPPED"; + /** * Broadcast Action: Sent to the system rollback manager when a package * needs to have rollback enabled. diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java index f256372964f2..30875aa6a100 100644 --- a/core/java/android/content/om/OverlayIdentifier.java +++ b/core/java/android/content/om/OverlayIdentifier.java @@ -39,7 +39,6 @@ import java.util.Objects; * --> * * @see OverlayInfo#getOverlayIdentifier() - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false, genEqualsHashCode = true, genToString = false) diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index ff1c08801dd7..2e898562655b 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -385,7 +385,6 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { * <p>The return value of this function can be used to unregister the related overlay. * * @return an identifier representing the current overlay. - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @Override @NonNull diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 0fcc72a1f688..ed965b3d1777 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -50,7 +50,7 @@ import java.util.List; * <li>register overlays * <li>unregister overlays * <li>execute multiple operations in one commitment by calling {@link - * OverlayManagerTransaction#commit()} + * #commit(OverlayManagerTransaction)} * </ul> * * @see OverlayManagerTransaction diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 036a4eb22ba6..aefa55f30826 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1185,8 +1185,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly * through - * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame, - * {@link android.view.ViewRootImpl}#getDisplayFrame respectively. + * {@code android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, + * {@code android.view.ViewRootImpl#getDisplayFrame} respectively. * * <p>Some applications assume that they occupy the whole screen and therefore use the display * coordinates in their calculations as if an activity is positioned in the top-left corner of diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index aca88d6af033..99264150f7d0 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -308,6 +308,8 @@ interface IPackageManager { boolean isPackageQuarantinedForUser(String packageName, int userId); + boolean isPackageStoppedForUser(String packageName, int userId); + Bundle getSuspendedPackageAppExtras(String packageName, int userId); /** diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 3a9e9bf01f04..d837aae35096 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2730,8 +2730,8 @@ public class PackageInstaller { * Sets the state of permissions for the package at installation. * <p/> * Granting any runtime permissions require the - * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS - * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime + * {@code android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} + * permission to be held by the caller. Revoking runtime * permissions is not allowed, even during app update sessions. * <p/> * Holders without the permission are allowed to change the following special permissions: diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8fbe50c32881..45338bb2c0a2 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -9878,6 +9878,18 @@ public abstract class PackageManager { } /** + * Query if an app is currently stopped. + * + * @return {@code true} if the given package is stopped, {@code false} otherwise + * @throws NameNotFoundException if the package could not be found. + * @see ApplicationInfo#FLAG_STOPPED + */ + @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED) + public boolean isPackageStopped(@NonNull String packageName) throws NameNotFoundException { + throw new UnsupportedOperationException("isPackageStopped not implemented"); + } + + /** * Query if an app is currently quarantined. * * @return {@code true} if the given package is quarantined, {@code false} otherwise @@ -9888,7 +9900,6 @@ public abstract class PackageManager { public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException { throw new UnsupportedOperationException("isPackageQuarantined not implemented"); } - /** * Provide a hint of what the {@link ApplicationInfo#category} value should * be for the given package. diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 65f56f68ed3f..9869179d9686 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -132,11 +132,6 @@ public class ServiceInfo extends ComponentInfo * the {@link android.R.attr#foregroundServiceType} attribute. * Data(photo, file, account) upload/download, backup/restore, import/export, fetch, * transfer over network between device and cloud. - * - * <p class="note"> - * Use the {@link android.app.job.JobInfo.Builder#setDataTransfer} API for data transfers - * that can be deferred until conditions are ideal for the app or device. - * </p> */ @RequiresPermission( value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, diff --git a/core/java/android/content/pm/Signature.aidl b/core/java/android/content/pm/Signature.aidl deleted file mode 100644 index 36c127ad0384..000000000000 --- a/core/java/android/content/pm/Signature.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* //device/java/android/android/view/WindowManager.aidl -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES 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.content.pm; - -/* For the key attestation application id provider service we needed a native implementation - * of the Signature parcelable because the service is used by the native keystore. - * The native implementation is now located at - * system/security/keystore/Signature.cpp - * and - * system/security/keystore/include/keystore/Signature.h. - * and can be used by linking against libkeystore_binder. - * - * This is not the best arrangement. If you, dear reader, happen to implement native implementations - * for the package manager's parcelables, consider moving Signature.cpp/.h to your library and - * adjust keystore's dependencies accordingly. Thank you. - */ -parcelable Signature cpp_header "keystore/Signature.h"; diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 96af2b6caf9f..884d463e929d 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -49,6 +49,7 @@ public final class UserProperties implements Parcelable { private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher"; private static final String ATTR_START_WITH_PARENT = "startWithParent"; private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings"; + private static final String ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE = "hideInSettingsInQuietMode"; private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy"; private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts"; private static final String ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA = @@ -78,6 +79,7 @@ public final class UserProperties implements Parcelable { INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT, INDEX_DELETE_APP_WITH_PARENT, INDEX_ALWAYS_VISIBLE, + INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE, }) @Retention(RetentionPolicy.SOURCE) private @interface PropertyIndex { @@ -94,6 +96,7 @@ public final class UserProperties implements Parcelable { private static final int INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT = 9; private static final int INDEX_DELETE_APP_WITH_PARENT = 10; private static final int INDEX_ALWAYS_VISIBLE = 11; + private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12; /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */ private long mPropertiesPresent = 0; @@ -324,6 +327,7 @@ public final class UserProperties implements Parcelable { if (hasManagePermission) { // Add items that require MANAGE_USERS or stronger. setShowInSettings(orig.getShowInSettings()); + setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode()); setUseParentsContacts(orig.getUseParentsContacts()); } if (hasQueryOrManagePermission) { @@ -409,6 +413,42 @@ public final class UserProperties implements Parcelable { private @ShowInSettings int mShowInSettings; /** + * Returns whether a user should be shown in the Settings app depending on the quiet mode. + * This is generally inapplicable for non-profile users. + * + * <p> {@link #getShowInSettings()} returns whether / how a user should be shown in Settings. + * However, if this behaviour should be changed based on the quiet mode of the user, then this + * property can be used. If the property is not set then the user is shown in the Settings app + * irrespective of whether the user is in quiet mode or not. If the property is set, then the + * user is shown in the Settings app only if the user is not in the quiet mode. Please note that + * this property takes effect only if {@link #getShowInSettings()} does not return + * {@link #SHOW_IN_SETTINGS_NO}. + * + * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this + * property. + * + * @return true if a profile should be shown in the Settings only when the user is not in the + * quiet mode. + * + * See also {@link #getShowInSettings()}, {@link #setShowInSettings(int)}, + * {@link ShowInSettings} + * + * @hide + */ + public boolean getHideInSettingsInQuietMode() { + if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) return mHideInSettingsInQuietMode; + if (mDefaultProperties != null) return mDefaultProperties.mHideInSettingsInQuietMode; + throw new SecurityException( + "You don't have permission to query HideInSettingsInQuietMode"); + } + /** @hide */ + public void setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) { + this.mHideInSettingsInQuietMode = hideInSettingsInQuietMode; + setPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE); + } + private boolean mHideInSettingsInQuietMode; + + /** * Returns whether a profile should be started when its parent starts (unless in quiet mode). * This only applies for users that have parents (i.e. for profiles). * @hide @@ -724,6 +764,9 @@ public final class UserProperties implements Parcelable { case ATTR_SHOW_IN_SETTINGS: setShowInSettings(parser.getAttributeInt(i)); break; + case ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE: + setHideInSettingsInQuietMode(parser.getAttributeBoolean(i)); + break; case ATTR_INHERIT_DEVICE_POLICY: setInheritDevicePolicy(parser.getAttributeInt(i)); break; @@ -777,6 +820,10 @@ public final class UserProperties implements Parcelable { if (isPresent(INDEX_SHOW_IN_SETTINGS)) { serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings); } + if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) { + serializer.attributeBoolean(null, ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE, + mHideInSettingsInQuietMode); + } if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) { serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY, mInheritDevicePolicy); @@ -823,6 +870,7 @@ public final class UserProperties implements Parcelable { dest.writeInt(mShowInLauncher); dest.writeBoolean(mStartWithParent); dest.writeInt(mShowInSettings); + dest.writeBoolean(mHideInSettingsInQuietMode); dest.writeInt(mInheritDevicePolicy); dest.writeBoolean(mUseParentsContacts); dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA); @@ -845,6 +893,7 @@ public final class UserProperties implements Parcelable { mShowInLauncher = source.readInt(); mStartWithParent = source.readBoolean(); mShowInSettings = source.readInt(); + mHideInSettingsInQuietMode = source.readBoolean(); mInheritDevicePolicy = source.readInt(); mUseParentsContacts = source.readBoolean(); mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean(); @@ -881,6 +930,7 @@ public final class UserProperties implements Parcelable { private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT; private boolean mStartWithParent = false; private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT; + private boolean mHideInSettingsInQuietMode = false; private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO; private boolean mUseParentsContacts = false; private boolean mUpdateCrossProfileIntentFiltersOnOTA = false; @@ -910,6 +960,12 @@ public final class UserProperties implements Parcelable { return this; } + /** Sets the value for {@link #mHideInSettingsInQuietMode} */ + public Builder setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) { + mHideInSettingsInQuietMode = hideInSettingsInQuietMode; + return this; + } + /** Sets the value for {@link #mInheritDevicePolicy}*/ public Builder setInheritDevicePolicy( @InheritDevicePolicy int inheritRestrictionsDevicePolicy) { @@ -972,6 +1028,7 @@ public final class UserProperties implements Parcelable { mShowInLauncher, mStartWithParent, mShowInSettings, + mHideInSettingsInQuietMode, mInheritDevicePolicy, mUseParentsContacts, mUpdateCrossProfileIntentFiltersOnOTA, @@ -989,6 +1046,7 @@ public final class UserProperties implements Parcelable { @ShowInLauncher int showInLauncher, boolean startWithParent, @ShowInSettings int showInSettings, + boolean hideInSettingsInQuietMode, @InheritDevicePolicy int inheritDevicePolicy, boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA, @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl, @@ -1001,6 +1059,7 @@ public final class UserProperties implements Parcelable { setShowInLauncher(showInLauncher); setStartWithParent(startWithParent); setShowInSettings(showInSettings); + setHideInSettingsInQuietMode(hideInSettingsInQuietMode); setInheritDevicePolicy(inheritDevicePolicy); setUseParentsContacts(useParentsContacts); setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA); diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 89ca915cb995..b2cc070228b7 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -19,6 +19,7 @@ flag { namespace: "package_manager_service" description: "Feature flag to enable the prevent sdk-library be an application." bug: "295843617" + is_fixed_read_only: true } flag { @@ -42,3 +43,10 @@ flag { description: "Feature flag to enable the feature to retrieve package info without installation." bug: "269149275" } + +flag { + name: "use_art_service_v2" + namespace: "package_manager_service" + description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module." + bug: "304741685" +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 3e12a1a66791..3ec239c7125e 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -8,6 +8,14 @@ flag { } flag { + name: "save_global_and_guest_restrictions_on_system_user_xml_read_only" + namespace: "multiuser" + description: "Save guest and device policy global restrictions on the SYSTEM user's XML file. (Read only flag)" + bug: "301067944" + is_fixed_read_only: true +} + +flag { name: "bind_wallpaper_service_on_its_own_thread_during_a_user_switch" namespace: "multiuser" description: "Bind wallpaper service on its own thread instead of system_server's main handler during a user switch." diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java index 1e60abb30011..7ada9469726b 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java @@ -231,7 +231,7 @@ public final class DomainVerificationUserState implements Parcelable { } /** - * Mapping of domain host to state, as defined by {@link DomainState}. + * Mapping of domain host to state, as defined by the {@code DOMAIN_STATE_*} constants */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ed22284ae23d..1b37092f77b6 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2210,8 +2210,7 @@ public class Resources { * * <p>The best practices is to obtain metrics from * {@link WindowManager#getCurrentWindowMetrics()} for window bounds. The value obtained from - * this API may be wrong if {@link Context#getResources()} is from - * non-{@link android.annotation.UiContext}. + * this API may be wrong if {@link Context#getResources()} is not from a {@code UiContext}. * For example, use the {@link DisplayMetrics} obtained from {@link Application#getResources()} * to build {@link android.app.Activity} UI elements especially when the * {@link android.app.Activity} is in the multi-window mode or on the secondary {@link Display}. diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java index c3440043a4c0..8f07d19a8592 100644 --- a/core/java/android/credentials/CreateCredentialException.java +++ b/core/java/android/credentials/CreateCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#createCredential(CreateCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#createCredential(Context, CreateCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class CreateCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class CreateCredentialException extends Exception { /** * The error type value for when no create options are available from any provider(s), - * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity, + * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest, * CancellationSignal, Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java index db71624cbe89..755e6590a934 100644 --- a/core/java/android/credentials/CredentialDescription.java +++ b/core/java/android/credentials/CredentialDescription.java @@ -155,8 +155,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor * enforces {@link CredentialEntry} to have the same type and * {@link android.app.slice.Slice} contained by the entry can not be hashed. */ @@ -166,8 +165,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check. + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check. */ @Override public boolean equals(Object obj) { diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index 95ca0112b8b3..8503072838d1 100644 --- a/core/java/android/credentials/CredentialProviderInfo.java +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -16,12 +16,14 @@ package android.credentials; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; +import android.credentials.flags.Flags; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -42,6 +44,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private final List<String> mCapabilities = new ArrayList<>(); @Nullable private final CharSequence mOverrideLabel; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private final boolean mIsSystemProvider; private final boolean mIsEnabled; private final boolean mIsPrimary; @@ -59,6 +62,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsEnabled = builder.mIsEnabled; mIsPrimary = builder.mIsPrimary; mOverrideLabel = builder.mOverrideLabel; + mSettingsActivity = builder.mSettingsActivity; } /** Returns true if the service supports the given {@code credentialType}, false otherwise. */ @@ -104,10 +108,7 @@ public final class CredentialProviderInfo implements Parcelable { return mIsEnabled; } - /** - * Returns whether the provider is set as primary by the user. - * - */ + /** Returns whether the provider is set as primary by the user. */ public boolean isPrimary() { return mIsPrimary; } @@ -118,6 +119,18 @@ public final class CredentialProviderInfo implements Parcelable { return mSettingsSubtitle; } + /** + * Returns the settings activity. + * + * @hide + */ + @Nullable + @TestApi + @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED) + public CharSequence getSettingsActivity() { + return mSettingsActivity; + } + /** Returns the component name for the service. */ @NonNull public ComponentName getComponentName() { @@ -133,6 +146,7 @@ public final class CredentialProviderInfo implements Parcelable { dest.writeBoolean(mIsPrimary); TextUtils.writeToParcel(mOverrideLabel, dest, flags); TextUtils.writeToParcel(mSettingsSubtitle, dest, flags); + TextUtils.writeToParcel(mSettingsActivity, dest, flags); } @Override @@ -161,6 +175,9 @@ public final class CredentialProviderInfo implements Parcelable { + "settingsSubtitle=" + mSettingsSubtitle + ", " + + "settingsActivity=" + + mSettingsActivity + + ", " + "capabilities=" + String.join(",", mCapabilities) + "}"; @@ -174,6 +191,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsPrimary = in.readBoolean(); mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSettingsActivity = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR = @@ -196,6 +214,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private List<String> mCapabilities = new ArrayList<>(); private boolean mIsSystemProvider = false; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private boolean mIsEnabled = false; private boolean mIsPrimary = false; @Nullable private CharSequence mOverrideLabel = null; @@ -231,6 +250,16 @@ public final class CredentialProviderInfo implements Parcelable { return this; } + /** + * Sets the settings activity. + * + * @hide + */ + public @NonNull Builder setSettingsActivity(@Nullable CharSequence settingsActivity) { + mSettingsActivity = settingsActivity; + return this; + } + /** Sets a list of capabilities this provider service can support. */ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) { mCapabilities.addAll(capabilities); diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java index 720c53ba51cd..0421d1f5aba0 100644 --- a/core/java/android/credentials/GetCredentialException.java +++ b/core/java/android/credentials/GetCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#getCredential(GetCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#getCredential(Context, GetCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class GetCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class GetCredentialException extends Exception { /** * The error type value for when no credential is found available for the given {@link - * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal, * Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java index 056b18a51b6d..212f5716d041 100644 --- a/core/java/android/credentials/PrepareGetCredentialResponse.java +++ b/core/java/android/credentials/PrepareGetCredentialResponse.java @@ -22,7 +22,6 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.IntentSender; @@ -36,7 +35,7 @@ import java.util.concurrent.Executor; /** * A response object that prefetches user app credentials and provides metadata about them. It can * then be used to issue the full credential retrieval flow via the - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection * and credential selection, to officially retrieve a credential. */ @@ -44,7 +43,7 @@ public final class PrepareGetCredentialResponse { /** * A handle that represents a pending get-credential operation. Pass this handle to {@link - * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a * credential. */ @@ -144,7 +143,7 @@ public final class PrepareGetCredentialResponse { /** * Returns a handle that represents this pending get-credential operation. Pass this handle to - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially * retrieve a credential. */ diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 0d0305b5b626..9b819a792f8f 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -5,4 +5,11 @@ flag { name: "settings_activity_enabled" description: "Enable the Credential Manager Settings Activity APIs" bug: "300014059" -}
\ No newline at end of file +} + +flag { + namespace: "credential_manager" + name: "instant_apps_enabled" + description: "Enables Credential Manager to work with Instant Apps" + bug: "302190269" +} diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 82694ee3463b..9c05dfc94ad4 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -537,6 +537,24 @@ public class BiometricManager { } /** + * Listens for biometric prompt status, i.e., if it is being shown or idle. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener callback) { + if (mService != null) { + try { + mService.registerBiometricPromptStatusListener(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerBiometricPromptOnKeyguardCallback(): Service not connected"); + } + } + + /** * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their * authenticatorId invalidated for the specified user. This happens when enrollments have been * added on devices with multiple biometric sensors. diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index 151f819329c9..6ac1efb49839 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -114,8 +114,8 @@ public class CryptoObject { } /** - * Get {@link PresentationSession} object. - * @return {@link PresentationSession} object or null if this doesn't contain one. + * Get {@link KeyAgreement} object. + * @return {@link KeyAgreement} object or null if this doesn't contain one. */ @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT) public KeyAgreement getKeyAgreement() { diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index c2e5c0b6d519..8eede472bec5 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; @@ -63,6 +64,9 @@ interface IAuthService { // Register callback for when keyguard biometric eligibility changes. void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register callback to check biometric prompt status. + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the // specified user. This happens when enrollments have been added on devices with multiple // biometric sensors. diff --git a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl new file mode 100644 index 000000000000..7a0f4389ed60 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl @@ -0,0 +1,27 @@ +/* + * 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 android.hardware.biometrics; + +/** + * Communication channel to propagate biometric prompt status. Implementation of this interface + * should be registered in BiometricService#registerBiometricPromptStatusListener. + * @hide + */ +oneway interface IBiometricPromptStatusListener { + void onBiometricPromptShowing(); + void onBiometricPromptIdle(); +}
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 18c8d1bd3a1e..36606a135f3e 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IInvalidationCallback; @@ -68,6 +69,10 @@ interface IBiometricService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register a callback for biometric prompt status on keyguard. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Notify BiometricService when <Biometric>Service is ready to start the prepared client. // Client lifecycle is still managed in <Biometric>Service. @EnforcePermission("USE_BIOMETRIC_INTERNAL") diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 990ebc5fdbcd..f347909348c8 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.HdrCapabilities.HdrType; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; @@ -27,7 +28,6 @@ import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -377,7 +377,7 @@ public final class DisplayManager { * @see #createVirtualDisplay * @hide */ - @SuppressLint("UnflaggedApi") + @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS) @SystemApi public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7; diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index a6d8cafe8263..0c95c2ec7a7a 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -79,4 +79,9 @@ interface INfcAdapter Map getTagIntentAppPreferenceForUser(int userId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") int setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow); + + boolean isReaderOptionEnabled(); + boolean isReaderOptionSupported(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") + boolean enableReaderOption(boolean enable); } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 1307dfc2665e..46586308e3cf 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -17,6 +17,7 @@ package android.nfc; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1826,6 +1827,97 @@ public final class NfcAdapter { } /** + * Sets NFC Reader option feature. + * <p>This API is for the Settings application. + * @return True if successful + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public boolean enableReaderOption(boolean enable) { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.enableReaderOption(enable); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.enableReaderOption(enable); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** + * Checks if the device supports NFC Reader option functionality. + * + * @return True if device supports NFC Reader option, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + public boolean isReaderOptionSupported() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.isReaderOptionSupported(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.isReaderOptionSupported(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** + * Checks NFC Reader option feature is enabled. + * + * @return True if NFC Reader option is enabled, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + * @throws UnsupportedOperationException if device doesn't support + * NFC Reader option functionality. {@link #isReaderOptionSupported} + */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + public boolean isReaderOptionEnabled() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.isReaderOptionEnabled(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.isReaderOptionEnabled(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** * Enable NDEF Push feature. * <p>This API is for the Settings application. * @hide diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index e3faf3978856..55b0b4261763 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "Flag for NFC mainline changes" bug: "292140387" } + +flag { + name: "enable_nfc_reader_option" + namespace: "nfc" + description: "Flag for NFC reader option API changes" + bug: "291187960" +} diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 092923e927a3..6a4ec9b7605a 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -16,7 +16,10 @@ package android.os; +import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC; + import android.Manifest.permission; +import android.annotation.FlaggedApi; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -354,17 +357,11 @@ public class BatteryManager { public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; /** - * - * Percentage representing the measured battery state of health (remaining - * estimated full charge capacity relative to the rated capacity in %). - * - * <p class="note"> - * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission. - * - * @hide + * Percentage representing the measured battery state of health. + * This is the remaining estimated full charge capacity relative + * to the rated capacity in %. */ - @RequiresPermission(permission.BATTERY_STATS) - @SystemApi + @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; private final Context mContext; diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 955fad3d1a48..3abe9a0e38f5 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -520,8 +520,9 @@ public final class BatteryStatsManager { * @param uid calling package uid * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change - * @Deprecated Bluetooth self report its state and no longer call this + * @deprecated Bluetooth self report its state and no longer call this */ + @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) { } @@ -532,8 +533,9 @@ public final class BatteryStatsManager { * @param uid calling package uid * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change - * @Deprecated Bluetooth self report its state and no longer call this + * @deprecated Bluetooth self report its state and no longer call this */ + @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) { } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index a95e66d9eb04..37559b32e841 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -1,6 +1,13 @@ package: "android.os" flag { + name: "state_of_health_public" + namespace: "system_sw_battery" + description: "Feature flag for making state_of_health a public api." + bug: "288842045" +} + +flag { name: "disallow_cellular_null_ciphers_restriction" namespace: "cellular_security" description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices." diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 88f62f327fdd..66ad12c7559a 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -27,3 +27,13 @@ flag { description: "Enables the APIs for vibration serialization/deserialization." bug: "245129509" } + +flag { + namespace: "haptics" + name: "haptic_feedback_vibration_oem_customization_enabled" + description: "Enables OEMs/devices to customize vibrations for haptic feedback" + # Make read only. This is because the flag is used only once, and this could happen before + # the read-write flag values propagate to the device. + is_fixed_read_only: true + bug: "291128479" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 36d318008560..b19a034d4628 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4866,7 +4866,8 @@ public final class Settings { "display_color_mode_vendor_hint"; /** - * The user selected min refresh rate in frames per second. + * The user selected min refresh rate in frames per second. If infinite, the user wants + * the highest possible refresh rate. * * If this isn't set, 0 will be used. * @hide @@ -4875,7 +4876,8 @@ public final class Settings { public static final String MIN_REFRESH_RATE = "min_refresh_rate"; /** - * The user selected peak refresh rate in frames per second. + * The user selected peak refresh rate in frames per second. If infinite, the user wants + * the highest possible refresh rate. * * If this isn't set, the system falls back to a device specific default. * @hide @@ -5352,6 +5354,37 @@ public final class Settings { public static final Uri NOTIFICATION_SOUND_CACHE_URI = getUriFor(NOTIFICATION_SOUND_CACHE); /** + * When enabled, notifications attention effects: sound, vibration, flashing + * will have a cooldown timer. + * + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String NOTIFICATION_COOLDOWN_ENABLED = + "notification_cooldown_enabled"; + + /** + * When enabled, notification cooldown will apply to all notifications. + * Otherwise cooldown will only apply to conversations. + * + * The value 1 - enable, 0 - disable + * Only valid if {@code NOTIFICATION_COOLDOWN_ENABLED} is enabled. + * @hide + */ + public static final String NOTIFICATION_COOLDOWN_ALL = + "notification_cooldown_all"; + + /** + * When enabled, notification attention effects will be restricted to vibration only + * as long as the screen is unlocked. + * + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = + "notification_cooldown_vibrate_unlocked"; + + /** * Persistent store for the system-wide default alarm alert. * * @see #RINGTONE @@ -11178,6 +11211,12 @@ public final class Settings { public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving"; /** + * Volume dialog timeout in ms. + * @hide + */ + public static final String VOLUME_DIALOG_DISMISS_TIMEOUT = "volume_dialog_dismiss_timeout"; + + /** * What behavior should be invoked when the volume hush gesture is triggered * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE. * @@ -12463,6 +12502,13 @@ public final class Settings { "wireless_charging_started_sound"; /** + * Whether to auto enable reverse charging once plugged-in. + * @hide + */ + public static final String REVERSE_CHARGING_AUTO_ON = + "settings_key_reverse_charging_auto_turn_on"; + + /** * URI for "wired charging started" sound. * @hide */ diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.java b/core/java/android/security/keymaster/KeyAttestationApplicationId.java deleted file mode 100644 index 670f30e1b04b..000000000000 --- a/core/java/android/security/keymaster/KeyAttestationApplicationId.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keymaster; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * @hide - * The information aggregated by this class is used by keystore to identify a caller of the - * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore - * can only determine a caller by uid granularity, and a uid can be shared by multiple packages. - * The remote party must decide if it trusts all of the packages enough to consider the - * confidentiality of the key material in question intact. - */ -public class KeyAttestationApplicationId implements Parcelable { - private final KeyAttestationPackageInfo[] mAttestationPackageInfos; - - /** - * @param mAttestationPackageInfos - */ - public KeyAttestationApplicationId(KeyAttestationPackageInfo[] mAttestationPackageInfos) { - super(); - this.mAttestationPackageInfos = mAttestationPackageInfos; - } - - /** - * @return the mAttestationPackageInfos - */ - public KeyAttestationPackageInfo[] getAttestationPackageInfos() { - return mAttestationPackageInfos; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeTypedArray(mAttestationPackageInfos, flags); - } - - public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationApplicationId> CREATOR - = new Parcelable.Creator<KeyAttestationApplicationId>() { - @Override - public KeyAttestationApplicationId createFromParcel(Parcel source) { - return new KeyAttestationApplicationId(source); - } - - @Override - public KeyAttestationApplicationId[] newArray(int size) { - return new KeyAttestationApplicationId[size]; - } - }; - - KeyAttestationApplicationId(Parcel source) { - mAttestationPackageInfos = source.createTypedArray(KeyAttestationPackageInfo.CREATOR); - } -} diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java b/core/java/android/security/keymaster/KeyAttestationPackageInfo.java deleted file mode 100644 index c0b8d8dfd4d9..000000000000 --- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keymaster; - -import android.content.pm.Signature; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * @hide - * This class constitutes and excerpt from the PackageManager's PackageInfo for the purpose of - * key attestation. It is part of the KeyAttestationApplicationId, which is used by - * keystore to identify the caller of the keystore API towards a remote party. - */ -public class KeyAttestationPackageInfo implements Parcelable { - private final String mPackageName; - private final long mPackageVersionCode; - private final Signature[] mPackageSignatures; - - /** - * @param mPackageName - * @param mPackageVersionCode - * @param mPackageSignatures - */ - public KeyAttestationPackageInfo( - String mPackageName, long mPackageVersionCode, Signature[] mPackageSignatures) { - super(); - this.mPackageName = mPackageName; - this.mPackageVersionCode = mPackageVersionCode; - this.mPackageSignatures = mPackageSignatures; - } - /** - * @return the mPackageName - */ - public String getPackageName() { - return mPackageName; - } - /** - * @return the mPackageVersionCode - */ - public long getPackageVersionCode() { - return mPackageVersionCode; - } - /** - * @return the mPackageSignatures - */ - public Signature[] getPackageSignatures() { - return mPackageSignatures; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mPackageName); - dest.writeLong(mPackageVersionCode); - dest.writeTypedArray(mPackageSignatures, flags); - } - - public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationPackageInfo> CREATOR - = new Parcelable.Creator<KeyAttestationPackageInfo>() { - @Override - public KeyAttestationPackageInfo createFromParcel(Parcel source) { - return new KeyAttestationPackageInfo(source); - } - - @Override - public KeyAttestationPackageInfo[] newArray(int size) { - return new KeyAttestationPackageInfo[size]; - } - }; - - private KeyAttestationPackageInfo(Parcel source) { - mPackageName = source.readString(); - mPackageVersionCode = source.readLong(); - mPackageSignatures = source.createTypedArray(Signature.CREATOR); - } -} diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 115894f586f2..a29bf7a06334 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -18,6 +18,7 @@ package android.service.autofill; import static android.view.autofill.Helper.sDebug; +import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,6 +27,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.ClipData; import android.content.IntentSender; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; @@ -187,6 +189,9 @@ public final class Dataset implements Parcelable { @Nullable private final InlinePresentation mInlinePresentation; @Nullable private final InlinePresentation mInlineTooltipPresentation; private final IntentSender mAuthentication; + + @Nullable private final Bundle mAuthenticationExtras; + @Nullable String mId; /** @@ -224,6 +229,7 @@ public final class Dataset implements Parcelable { mInlinePresentation = inlinePresentation; mInlineTooltipPresentation = inlineTooltipPresentation; mAuthentication = authentication; + mAuthenticationExtras = null; mId = id; } @@ -246,6 +252,7 @@ public final class Dataset implements Parcelable { mInlinePresentation = dataset.mInlinePresentation; mInlineTooltipPresentation = dataset.mInlineTooltipPresentation; mAuthentication = dataset.mAuthentication; + mAuthenticationExtras = dataset.mAuthenticationExtras; mId = dataset.mId; mAutofillDatatypes = dataset.mAutofillDatatypes; } @@ -264,6 +271,7 @@ public final class Dataset implements Parcelable { mInlinePresentation = builder.mInlinePresentation; mInlineTooltipPresentation = builder.mInlineTooltipPresentation; mAuthentication = builder.mAuthentication; + mAuthenticationExtras = builder.mAuthenticationExtras; mId = builder.mId; mAutofillDatatypes = builder.mAutofillDatatypes; } @@ -345,6 +353,12 @@ public final class Dataset implements Parcelable { } /** @hide */ + @Hide + public @Nullable Bundle getAuthenticationExtras() { + return mAuthenticationExtras; + } + + /** @hide */ @TestApi public boolean isEmpty() { return mFieldIds == null || mFieldIds.isEmpty(); @@ -401,6 +415,9 @@ public final class Dataset implements Parcelable { if (mAuthentication != null) { builder.append(", hasAuthentication"); } + if (mAuthenticationExtras != null) { + builder.append(", hasAuthenticationExtras"); + } if (mAutofillDatatypes != null) { builder.append(", autofillDatatypes=").append(mAutofillDatatypes); } @@ -454,6 +471,8 @@ public final class Dataset implements Parcelable { @Nullable private InlinePresentation mInlinePresentation; @Nullable private InlinePresentation mInlineTooltipPresentation; private IntentSender mAuthentication; + + private Bundle mAuthenticationExtras; private boolean mDestroyed; @Nullable private String mId; @@ -624,6 +643,25 @@ public final class Dataset implements Parcelable { } /** + * Sets extras to be associated with the {@code authentication} intent sender, to be + * set on the intent that is fired through the intent sender. + * + * Autofill providers can set any extras they wish to receive directly on the intent + * that is used to create the {@code authentication}. This is an internal API, to be + * used by the platform to associate data with a given dataset. These extras will be + * merged with the {@code clientState} and sent as part of the fill in intent when + * the {@code authentication} intentSender is invoked. + * + * @hide + */ + @Hide + public @NonNull Builder setAuthenticationExtras(@Nullable Bundle authenticationExtra) { + throwIfDestroyed(); + mAuthenticationExtras = authenticationExtra; + return this; + } + + /** * Sets the id for the dataset so its usage can be tracked. * * <p>Dataset usage can be tracked for 2 purposes: diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index 8cf2ce49712a..7ec14830b0af 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -97,8 +97,6 @@ public final class FillRequest implements Parcelable { */ public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10; - // The flag value 0x20 has been defined in AutofillManager. - /** * Indicates the request supports fill dialog presentation for the fields, the * system will send the request when the activity just started. diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index df934335e49d..5d96f69c2929 100644 --- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java +++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java @@ -143,7 +143,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse} diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java index 5ed06ac1ade7..ae6ca25a38ad 100644 --- a/core/java/android/service/credentials/BeginGetCredentialResponse.java +++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java @@ -160,7 +160,7 @@ public final class BeginGetCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginGetCredentialResponse} diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java index e755581638f5..c3524c5c85aa 100644 --- a/core/java/android/service/credentials/CallingAppInfo.java +++ b/core/java/android/service/credentials/CallingAppInfo.java @@ -103,7 +103,7 @@ public final class CallingAppInfo implements Parcelable { * of other applications. * * Android system makes sure that only applications that poses the permission - * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on + * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on * the incoming {@link android.credentials.GetCredentialRequest} or * {@link android.credentials.CreateCredentialRequest}. */ diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 0aa6a232d416..514d72252c3d 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -45,8 +45,8 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -135,8 +135,8 @@ public final class CredentialProviderInfoFactory { } /** - * Constructs an information instance of the credential provider for testing purposes. Does - * not run any verifications and passes parameters as is. + * Constructs an information instance of the credential provider for testing purposes. Does not + * run any verifications and passes parameters as is. */ @VisibleForTesting public static CredentialProviderInfo createForTests( @@ -151,7 +151,6 @@ public final class CredentialProviderInfoFactory { .setSystemProvider(isSystemProvider) .addCapabilities(capabilities) .build(); - } private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException { @@ -267,15 +266,21 @@ public final class CredentialProviderInfoFactory { allAttributes, com.android.internal.R.styleable.CredentialProvider); builder.setSettingsSubtitle( - afsAttributes.getString( + getAfsAttributeSafe( + afsAttributes, R.styleable.CredentialProvider_settingsSubtitle)); + builder.setSettingsActivity( + getAfsAttributeSafe( + afsAttributes, + R.styleable.CredentialProvider_settingsActivity)); } catch (Exception e) { - Slog.e(TAG, "Failed to get XML attr", e); + Slog.w(TAG, "Failed to get XML attr for metadata", e); } finally { if (afsAttributes != null) { afsAttributes.recycle(); } } + builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources)); } else { Slog.w(TAG, "Meta-data does not start with credential-provider-service tag"); @@ -287,6 +292,21 @@ public final class CredentialProviderInfoFactory { return builder; } + private static @Nullable String getAfsAttributeSafe( + @Nullable TypedArray afsAttributes, int resId) { + if (afsAttributes == null) { + return null; + } + + try { + return afsAttributes.getString(resId); + } catch (Exception e) { + Slog.w(TAG, "Failed to get XML attr from afs attributes", e); + } + + return null; + } + private static List<String> parseXmlProviderOuterCapabilities( XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException { final List<String> capabilities = new ArrayList<>(); diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 07f8aac43bf5..1cfff14b2d7c 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -2007,6 +2007,20 @@ public abstract class NotificationListenerService extends Service { return mSmartActions == null ? Collections.emptyList() : mSmartActions; } + + /** + * Sets the smart {@link Notification.Action} objects. + * + * Should ONLY be used in cases where smartActions need to be removed from, then restored + * on, Ranking objects during Parceling, when they are transmitted between processes via + * Shared Memory. + * + * @hide + */ + public void setSmartActions(@Nullable ArrayList<Notification.Action> smartActions) { + mSmartActions = smartActions; + } + /** * Returns a list of smart replies that can be added by the * {@link NotificationAssistantService} @@ -2353,11 +2367,9 @@ public abstract class NotificationListenerService extends Service { /** * Get a reference to the actual Ranking object corresponding to the key. - * Used only by unit tests. * * @hide */ - @VisibleForTesting public Ranking getRawRankingObject(String key) { return mRankings.get(key); } diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index f3b4c6da4a01..c82a4cabaddd 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -17,7 +17,8 @@ package android.service.notification; import android.annotation.Nullable; import android.annotation.SuppressLint; -import android.annotation.TestApi; +import android.app.Notification; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.SharedMemory; @@ -25,17 +26,20 @@ import android.system.ErrnoException; import android.system.OsConstants; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; /** * Represents an update to notification rankings. + * * @hide */ @SuppressLint({"ParcelNotFinal", "ParcelCreator"}) -@TestApi public class NotificationRankingUpdate implements Parcelable { private final NotificationListenerService.RankingMap mRankingMap; @@ -64,6 +68,7 @@ public class NotificationRankingUpdate implements Parcelable { // The ranking map should be stored in shared memory when it is parceled, so we // unwrap the SharedMemory object. mRankingMapFd = in.readParcelable(getClass().getClassLoader(), SharedMemory.class); + Bundle smartActionsBundle = in.readBundle(getClass().getClassLoader()); // In the case that the ranking map can't be read, readParcelable may return null. // In this case, we set mRankingMap to null; @@ -82,15 +87,20 @@ public class NotificationRankingUpdate implements Parcelable { mapParcel.unmarshall(payload, 0, payload.length); mapParcel.setDataPosition(0); - mRankingMap = mapParcel.readParcelable(getClass().getClassLoader(), - android.service.notification.NotificationListenerService.RankingMap.class); + mRankingMap = + mapParcel.readParcelable( + getClass().getClassLoader(), + NotificationListenerService.RankingMap.class); + + addSmartActionsFromBundleToRankingMap(smartActionsBundle); + } catch (ErrnoException e) { // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to // avoid crashes; change to Log.wtf. throw new RuntimeException(e); } finally { mapParcel.recycle(); - if (buffer != null) { + if (buffer != null && mRankingMapFd != null) { mRankingMapFd.unmap(buffer); mRankingMapFd.close(); } @@ -102,10 +112,34 @@ public class NotificationRankingUpdate implements Parcelable { } /** + * For each key in the rankingMap, extracts lists of smart actions stored in the provided + * bundle and adds them to the corresponding Ranking object in the provided ranking + * map, then returns the rankingMap. + * + * @hide + */ + private void addSmartActionsFromBundleToRankingMap(Bundle smartActionsBundle) { + if (smartActionsBundle == null) { + return; + } + + String[] rankingMapKeys = mRankingMap.getOrderedKeys(); + for (int i = 0; i < rankingMapKeys.length; i++) { + String key = rankingMapKeys[i]; + ArrayList<Notification.Action> smartActions = + smartActionsBundle.getParcelableArrayList(key, Notification.Action.class); + // Get the ranking object from the ranking map. + NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key); + ranking.setSmartActions(smartActions); + } + } + + /** * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing. + * * @hide */ - @TestApi + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public final boolean isFdNotNullAndClosed() { return mRankingMapFd != null && mRankingMapFd.getFd() == -1; } @@ -145,9 +179,45 @@ public class NotificationRankingUpdate implements Parcelable { if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { final Parcel mapParcel = Parcel.obtain(); + ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>(); + Bundle smartActionsBundle = new Bundle(); + + // We need to separate the SmartActions from the RankingUpdate objects. + // SmartActions can contain PendingIntents, which cannot be marshalled, + // so we extract them to send separately. + String[] rankingMapKeys = mRankingMap.getOrderedKeys(); + for (int i = 0; i < rankingMapKeys.length; i++) { + String key = rankingMapKeys[i]; + NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key); + + // Removes the SmartActions and stores them in a separate map. + // Note that getSmartActions returns a Collections.emptyList() if there are no + // smart actions, and we don't want to needlessly store an empty list object, so we + // check for null before storing. + List<Notification.Action> smartActions = ranking.getSmartActions(); + if (!smartActions.isEmpty()) { + smartActionsBundle.putParcelableList(key, smartActions); + } + + // Create a copy of the ranking object that doesn't have the smart actions. + NotificationListenerService.Ranking rankingCopy = + new NotificationListenerService.Ranking(); + rankingCopy.populate(ranking); + rankingCopy.setSmartActions(null); + marshalableRankings.add(rankingCopy); + } + + // Create a new marshalable RankingMap. + NotificationListenerService.RankingMap marshalableRankingMap = + new NotificationListenerService.RankingMap( + marshalableRankings.toArray( + new NotificationListenerService.Ranking[0] + ) + ); + try { // Parcels the ranking map and measures its size. - mapParcel.writeParcelable(mRankingMap, flags); + mapParcel.writeParcelable(marshalableRankingMap, flags); int mapSize = mapParcel.dataSize(); // Creates a new SharedMemory object with enough space to hold the ranking map. @@ -158,15 +228,14 @@ public class NotificationRankingUpdate implements Parcelable { // Gets a read/write buffer mapping the entire shared memory region. final ByteBuffer buffer = mRankingMapFd.mapReadWrite(); - // Puts the ranking map into the shared memory region buffer. buffer.put(mapParcel.marshall(), 0, mapSize); - // Protects the region from being written to, by setting it to be read-only. mRankingMapFd.setProtect(OsConstants.PROT_READ); - // Puts the SharedMemory object in the parcel. out.writeParcelable(mRankingMapFd, flags); + // Writes the Parceled smartActions separately. + out.writeBundle(smartActionsBundle); } catch (ErrnoException e) { // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to // avoid crashes; change to Log.wtf. @@ -180,8 +249,8 @@ public class NotificationRankingUpdate implements Parcelable { } /** - * @hide - */ + * @hide + */ public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR = new Parcelable.Creator<NotificationRankingUpdate>() { public NotificationRankingUpdate createFromParcel(Parcel parcel) { diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index bb5dd7f0cdfe..3ed13bbeeaad 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -536,7 +536,16 @@ public class SpeechRecognizer { @MainThread public void setRecognitionListener(RecognitionListener listener) { checkIsCalledFromMainThread(); - putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + if (mListener.mInternalListener == null) { + // This shortcut is needed because otherwise, if there's an error connecting, it never + // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does + // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same + // place does not get forwarded anywhere. + // Thread-wise, this is safe as both this method and the handler are on the UI thread. + handleChangeListener(listener); + } else { + putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + } } /** diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index f6309f2c9860..cf1156db55e5 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -401,7 +401,7 @@ public class PhoneStateListener { /** * Listen for call disconnect causes which contains {@link DisconnectCause} and - * {@link PreciseDisconnectCause}. + * the precise disconnect cause. * * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} * or the calling app has carrier privileges @@ -851,8 +851,8 @@ public class PhoneStateListener { * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. * - * @param disconnectCause {@link DisconnectCause}. - * @param preciseDisconnectCause {@link PreciseDisconnectCause}. + * @param disconnectCause the disconnect cause + * @param preciseDisconnectCause the precise disconnect cause * @deprecated Use {@link TelephonyCallback.CallDisconnectCauseListener} instead. */ @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java index fb2d7714d402..7b48a16c2227 100644 --- a/core/java/android/telephony/SubscriptionPlan.java +++ b/core/java/android/telephony/SubscriptionPlan.java @@ -221,7 +221,7 @@ public final class SubscriptionPlan implements Parcelable { } /** - * Return an array containing all {@link NetworkType}s this SubscriptionPlan applies to. + * Return an array containing all network types this SubscriptionPlan applies to. * @see TelephonyManager for network types values */ public @NonNull @NetworkType int[] getNetworkTypes() { @@ -365,7 +365,7 @@ public final class SubscriptionPlan implements Parcelable { * Set the network types this SubscriptionPlan applies to. By default the plan will apply * to all network types. An empty array means this plan applies to no network types. * - * @param networkTypes an array of all {@link NetworkType}s that apply to this plan. + * @param networkTypes an array of all network types that apply to this plan. * @see TelephonyManager for network type values */ public @NonNull Builder setNetworkTypes(@NonNull @NetworkType int[] networkTypes) { diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index 7ada058e8e80..19bcf28d6b83 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -948,8 +948,8 @@ public class TelephonyCallback { * subscription ID. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. * - * @param disconnectCause {@link DisconnectCause}. - * @param preciseDisconnectCause {@link PreciseDisconnectCause}. + * @param disconnectCause the disconnect cause + * @param preciseDisconnectCause the precise disconnect cause */ @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) void onCallDisconnectCauseChanged(@Annotation.DisconnectCauses int disconnectCause, diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index e2c5539141a4..0063d13cb3ab 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -238,7 +238,7 @@ public class TelephonyRegistryManager { } /** - * To check the SDK version for {@link #listenFromListener}. + * To check the SDK version for {@code #listenFromListener}. */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P) diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 2b3a081ceff6..8862f1d74ab1 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -16,6 +16,10 @@ package android.text; +import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN; +import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; + +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; @@ -280,6 +284,7 @@ public class DynamicLayout extends Layout { * @see android.widget.TextView#setLineBreakWordStyle */ @NonNull + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { mLineBreakConfig = lineBreakConfig; return this; @@ -303,6 +308,7 @@ public class DynamicLayout extends Layout { * @see Layout.Builder#setUseBoundsForWidth(boolean) */ @NonNull + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public Builder setUseBoundsForWidth(boolean useBoundsForWidth) { mUseBoundsForWidth = useBoundsForWidth; return this; diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index c5857347fd45..94c8eaf13ccb 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -29,6 +29,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; +import android.icu.util.ULocale; import android.os.Build; import android.os.LocaleList; import android.os.Parcel; @@ -39,6 +40,7 @@ import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Objects; @@ -58,6 +60,7 @@ public final class FontConfig implements Parcelable { private final @NonNull List<FontFamily> mFamilies; private final @NonNull List<Alias> mAliases; private final @NonNull List<NamedFamilyList> mNamedFamilyLists; + private final @NonNull List<Customization.LocaleFallback> mLocaleFallbackCustomizations; private final long mLastModifiedTimeMillis; private final int mConfigVersion; @@ -71,10 +74,12 @@ public final class FontConfig implements Parcelable { */ public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases, @NonNull List<NamedFamilyList> namedFamilyLists, + @NonNull List<Customization.LocaleFallback> localeFallbackCustomizations, long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) { mFamilies = families; mAliases = aliases; mNamedFamilyLists = namedFamilyLists; + mLocaleFallbackCustomizations = localeFallbackCustomizations; mLastModifiedTimeMillis = lastModifiedTimeMillis; mConfigVersion = configVersion; } @@ -84,7 +89,8 @@ public final class FontConfig implements Parcelable { */ public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases, long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) { - this(families, aliases, Collections.emptyList(), lastModifiedTimeMillis, configVersion); + this(families, aliases, Collections.emptyList(), Collections.emptyList(), + lastModifiedTimeMillis, configVersion); } @@ -113,6 +119,18 @@ public final class FontConfig implements Parcelable { } /** + * Returns a locale fallback customizations. + * + * This field is used for creating the system fallback in the system server. This field is + * always empty in the application process. + * + * @hide + */ + public @NonNull List<Customization.LocaleFallback> getLocaleFallbackCustomizations() { + return mLocaleFallbackCustomizations; + } + + /** * Returns the last modified time in milliseconds. * * This is a value of {@link System#currentTimeMillis()} when the system font configuration was @@ -169,7 +187,9 @@ public final class FontConfig implements Parcelable { source.readTypedList(familyLists, NamedFamilyList.CREATOR); long lastModifiedDate = source.readLong(); int configVersion = source.readInt(); - return new FontConfig(families, aliases, familyLists, lastModifiedDate, configVersion); + return new FontConfig(families, aliases, familyLists, + Collections.emptyList(), // Don't need to pass customization to API caller. + lastModifiedDate, configVersion); } @Override @@ -813,4 +833,129 @@ public final class FontConfig implements Parcelable { + '}'; } } + + /** @hide */ + public static class Customization { + private Customization() {} // Singleton + + /** + * A class that represents customization of locale fallback + * + * This class represents a vendor customization of new-locale-family. + * + * <pre> + * <family customizationType="new-locale-family" operation="prepend" lang="ja-JP"> + * <font weight="400" style="normal">MyAlternativeFont.ttf + * <axis tag="wght" stylevalue="400"/> + * </font> + * </family> + * </pre> + * + * The operation can be one of prepend, replace or append. The operation prepend means that + * the new font family is inserted just before the original font family. The original font + * family is still in the fallback. The operation replace means that the original font + * family is replaced with new font family. The original font family is removed from the + * fallback. The operation append means that the new font family is inserted just after the + * original font family. The original font family is still in the fallback. + * + * The lang attribute is a BCP47 compliant language tag. The font fallback mainly uses ISO + * 15924 script code for matching. If the script code is missing, most likely script code + * will be used. + */ + public static class LocaleFallback { + private final Locale mLocale; + private final int mOperation; + private final FontFamily mFamily; + private final String mScript; + + public static final int OPERATION_PREPEND = 0; + public static final int OPERATION_APPEND = 1; + public static final int OPERATION_REPLACE = 2; + + /** @hide */ + @Retention(SOURCE) + @IntDef(prefix = { "OPERATION_" }, value = { + OPERATION_PREPEND, + OPERATION_APPEND, + OPERATION_REPLACE + }) + public @interface Operation {} + + + public LocaleFallback(@NonNull Locale locale, @Operation int operation, + @NonNull FontFamily family) { + mLocale = locale; + mOperation = operation; + mFamily = family; + mScript = resolveScript(locale); + } + + /** + * A customization target locale. + * @return a locale + */ + public @NonNull Locale getLocale() { + return mLocale; + } + + /** + * An operation to be applied to the original font family. + * + * The operation can be one of {@link #OPERATION_PREPEND}, {@link #OPERATION_REPLACE} or + * {@link #OPERATION_APPEND}. + * + * The operation prepend ({@link #OPERATION_PREPEND}) means that the new font family is + * inserted just before the original font family. The original font family is still in + * the fallback. + * + * The operation replace ({@link #OPERATION_REPLACE}) means that the original font + * family is replaced with new font family. The original font family is removed from the + * fallback. + * + * The operation append ({@link #OPERATION_APPEND}) means that the new font family is + * inserted just after the original font family. The original font family is still in + * the fallback. + * + * @return an operation. + */ + public @Operation int getOperation() { + return mOperation; + } + + /** + * Returns a family to be inserted or replaced to the fallback. + * + * @return a family + */ + public @NonNull FontFamily getFamily() { + return mFamily; + } + + /** + * Returns a script of the locale. If the script is missing in the given locale, the + * most likely locale is returned. + */ + public @NonNull String getScript() { + return mScript; + } + + @Override + public String toString() { + return "LocaleFallback{" + + "mLocale=" + mLocale + + ", mOperation=" + mOperation + + ", mFamily=" + mFamily + + '}'; + } + } + } + + /** @hide */ + public static String resolveScript(Locale locale) { + String script = locale.getScript(); + if (script != null && !script.isEmpty()) { + return script; + } + return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript(); + } } diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index c1d0e9b95b70..ac5eb3cbeeaa 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -16,6 +16,9 @@ package android.text; +import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN; + +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; @@ -416,10 +419,12 @@ public class MeasuredParagraph { * @hide */ @TestApi + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public interface StyleRunCallback { /** * Called when a single style run is identified. */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) void onAppendStyleRun(@NonNull Paint paint, @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length, boolean isRtl); @@ -427,6 +432,7 @@ public class MeasuredParagraph { /** * Called when a single replacement run is identified. */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) void onAppendReplacementRun(@NonNull Paint paint, @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width); } @@ -488,6 +494,7 @@ public class MeasuredParagraph { @SuppressLint("ExecutorRegistration") @TestApi @NonNull + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static MeasuredParagraph buildForStaticLayoutTest( @NonNull TextPaint paint, @Nullable LineBreakConfig lineBreakConfig, diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index e3c72c964d64..01279cea073f 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -16,6 +16,9 @@ package android.text; +import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; + +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; @@ -439,6 +442,7 @@ public class StaticLayout extends Layout { * @see Layout.Builder#setUseBoundsForWidth(boolean) */ @NonNull + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public Builder setUseBoundsForWidth(boolean useBoundsForWidth) { mUseBoundsForWidth = useBoundsForWidth; return this; diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 536e3ccd98f6..9edf298b1fd2 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -62,6 +62,18 @@ public final class TextFlags { }; /** + * List of the default values of the text flags. + * + * The order must be the same to the TEXT_ACONFIG_FLAGS. + */ + public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { + Flags.deprecateFontsXml(), + Flags.noBreakNoHyphenationSpan(), + Flags.phraseStrictFallback(), + Flags.useBoundsForWidth(), + }; + + /** * Get a key for the feature flag. */ public static String getKeyForFlag(@NonNull String flag) { diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig new file mode 100644 index 000000000000..52fe8834f234 --- /dev/null +++ b/core/java/android/text/flags/custom_locale_fallback.aconfig @@ -0,0 +1,9 @@ +package: "com.android.text.flags" + +flag { + name: "custom_locale_fallback" + namespace: "text" + description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font." + is_fixed_read_only: true + bug: "278768958" +} diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig index 58dc210af581..53621385dd4b 100644 --- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig +++ b/core/java/android/text/flags/deprecate_fonts_xml.aconfig @@ -4,5 +4,7 @@ flag { name: "deprecate_fonts_xml" namespace: "text" description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" + # Make read only, as it could be used before the Settings provider is initialized. + is_fixed_read_only: true bug: "281769620" } diff --git a/core/java/android/text/flags/fix_line_height_for_locale.aconfig b/core/java/android/text/flags/fix_line_height_for_locale.aconfig new file mode 100644 index 000000000000..8696bfa61e5c --- /dev/null +++ b/core/java/android/text/flags/fix_line_height_for_locale.aconfig @@ -0,0 +1,8 @@ +package: "com.android.text.flags" + +flag { + name: "fix_line_height_for_locale" + namespace: "text" + description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin" + bug: "303326708" +} diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig new file mode 100644 index 000000000000..0e4a69f90375 --- /dev/null +++ b/core/java/android/text/flags/optimized_font_loading.aconfig @@ -0,0 +1,11 @@ +package: "com.android.text.flags" + +flag { + name: "use_optimized_boottime_font_loading" + namespace: "text" + description: "Feature flag ensuring that font is loaded once and asynchronously." + # Make read only, as font loading is in the critical boot path which happens before the read-write + # flags propagate to the device. + is_fixed_read_only: true + bug: "304406888" +} diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java index 25c1db4d9804..682ffa180c0b 100644 --- a/core/java/android/text/style/LineBreakConfigSpan.java +++ b/core/java/android/text/style/LineBreakConfigSpan.java @@ -71,6 +71,10 @@ public class LineBreakConfigSpan { .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED) .build(); + private static final LineBreakConfig sNoBreakConfig = new LineBreakConfig.Builder() + .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NO_BREAK) + .build(); + /** * A specialized {@link LineBreakConfigSpan} that used for preventing hyphenation. */ @@ -84,4 +88,24 @@ public class LineBreakConfigSpan { super(sNoHyphenationConfig); } } + + /** + * A specialized {@link LineBreakConfigSpan} that used for preventing line break. + * + * This is useful when you want to preserve some words in the same line. + * Note that even if this style is specified, the grapheme based line break is still performed + * for preventing clipping text. + * + * @see LineBreakConfigSpan + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final class NoBreakSpan extends LineBreakConfigSpan { + /** + * Construct a new {@link NoBreakSpan}. + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public NoBreakSpan() { + super(sNoBreakConfig); + } + } } diff --git a/core/java/android/util/Base64.java b/core/java/android/util/Base64.java index 92abd7c15f5f..33cc5e37da62 100644 --- a/core/java/android/util/Base64.java +++ b/core/java/android/util/Base64.java @@ -26,6 +26,7 @@ import java.io.UnsupportedEncodingException; * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>. */ +@android.ravenwood.annotations.RavenwoodWholeClassKeep public class Base64 { /** * Default values for encoder/decoder flags. diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 1ab055a6a7a1..a74cbe46b404 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -16,9 +16,11 @@ package android.view; +import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -695,6 +697,7 @@ public final class Choreographer { */ @TestApi @UnsupportedAppUsage + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public long getFrameTimeNanos() { synchronized (mLock) { if (!mCallbacksRunning) { diff --git a/core/java/android/view/IDecorViewGestureListener.aidl b/core/java/android/view/IDecorViewGestureListener.aidl new file mode 100644 index 000000000000..1022dbfb70eb --- /dev/null +++ b/core/java/android/view/IDecorViewGestureListener.aidl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +/** + * Listener for changes to gesture interception detector running at DecorView. + * + * {@hide} + */ +oneway interface IDecorViewGestureListener { + /** + * Called when a DecorView has started intercepting gesture. + * + * @param windowToken Where did this gesture interception result comes from. + * @param intercepted Whether the gesture interception detector has started interception. + */ + void onInterceptionChanged(in IBinder windowToken, in boolean intercepted); +} diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index d554514349c3..11180aef4479 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -54,6 +54,10 @@ oneway interface IWindow { */ void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor); + /** + * Please dispatch through WindowStateResizeItem instead of directly calling this method from + * the system server. + */ void resized(in ClientWindowFrames frames, boolean reportDraw, in MergedConfiguration newMergedConfiguration, in InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index cccac95b9caa..c10fc9f9cb09 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -48,6 +48,7 @@ import android.view.IScrollCaptureResponseListener; import android.view.RemoteAnimationAdapter; import android.view.IRotationWatcher; import android.view.ISystemGestureExclusionListener; +import android.view.IDecorViewGestureListener; import android.view.IWallpaperVisibilityListener; import android.view.IWindow; import android.view.IWindowSession; @@ -1062,4 +1063,18 @@ interface IWindowManager @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.ACCESS_SURFACE_FLINGER)") boolean replaceContentOnDisplay(int displayId, in SurfaceControl sc); + + /** + * Registers a DecorView gesture listener for a given display. + */ + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MONITOR_INPUT)") + void registerDecorViewGestureListener(IDecorViewGestureListener listener, int displayId); + + /** + * Unregisters a DecorView gesture listener for a given display. + */ + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MONITOR_INPUT)") + void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 83de2a0fafbe..7acf2f8ce06d 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -284,6 +284,11 @@ interface IWindowSession { oneway void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects); /** + * Called when the DecorView gesture interception state has changed. + */ + oneway void reportDecorViewGestureInterceptionChanged(IWindow window, in boolean intercepted); + + /** * Called when the keep-clear areas for this window have changed. */ oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> restricted, diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index c35b6907cd9d..9f886c826174 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -778,7 +778,8 @@ public final class InputDevice implements Parcelable { * Each gamepad or joystick is given a unique, positive controller number when initially * configured by the system. This number may change due to events such as device disconnects / * reconnects or user initiated reassignment. Any change in number will trigger an event that - * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}. + * can be observed by registering an + * {@link android.hardware.input.InputManager.InputDeviceListener}. * </p> * <p> * All input devices which are not gamepads or joysticks will be assigned a controller number diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java index 78716f5646f6..0ba414817247 100644 --- a/core/java/android/view/ScrollFeedbackProvider.java +++ b/core/java/android/view/ScrollFeedbackProvider.java @@ -17,7 +17,6 @@ package android.view; import android.annotation.FlaggedApi; -import android.annotation.NonNull; import android.view.flags.Flags; /** @@ -43,7 +42,8 @@ import android.view.flags.Flags; * the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID * that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}. * Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an - * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input + * {@link android.hardware.input.InputManager} instance + * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input * device IDs). * * <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index be6fb313b230..2f3d73a36425 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -123,7 +123,8 @@ public final class SurfaceControl implements Parcelable { private static native long nativeMirrorSurface(long mirrorOfObject); private static native long nativeCreateTransaction(); private static native long nativeGetNativeTransactionFinalizer(); - private static native void nativeApplyTransaction(long transactionObj, boolean sync); + private static native void nativeApplyTransaction(long transactionObj, boolean sync, + boolean oneWay); private static native void nativeMergeTransaction(long transactionObj, long otherTransactionObj); private static native void nativeClearTransaction(long transactionObj); @@ -262,7 +263,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj, long nativeObject, int compatibility); private static native void nativeSetFrameRateCategory( - long transactionObj, long nativeObject, int category); + long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly); private static native void nativeSetFrameRateSelectionStrategy( long transactionObj, long nativeObject, int strategy); private static native long nativeGetHandle(long nativeObject); @@ -1789,7 +1790,12 @@ public final class SurfaceControl implements Parcelable { public float xDpi; public float yDpi; + // Some modes have peak refresh rate lower than the panel vsync rate. public float refreshRate; + // Fixed rate of vsync deadlines for the panel. + // This can be higher then the peak refresh rate for some panel technologies + // See: VrrConfig.aidl + public float vsyncRate; public long appVsyncOffsetNanos; public long presentationDeadlineNanos; public int[] supportedHdrTypes; @@ -1810,6 +1816,7 @@ public final class SurfaceControl implements Parcelable { + ", xDpi=" + xDpi + ", yDpi=" + yDpi + ", refreshRate=" + refreshRate + + ", vsyncRate=" + vsyncRate + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos + ", presentationDeadlineNanos=" + presentationDeadlineNanos + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes) @@ -1827,6 +1834,7 @@ public final class SurfaceControl implements Parcelable { && Float.compare(that.xDpi, xDpi) == 0 && Float.compare(that.yDpi, yDpi) == 0 && Float.compare(that.refreshRate, refreshRate) == 0 + && Float.compare(that.vsyncRate, vsyncRate) == 0 && appVsyncOffsetNanos == that.appVsyncOffsetNanos && presentationDeadlineNanos == that.presentationDeadlineNanos && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes) @@ -1835,8 +1843,9 @@ public final class SurfaceControl implements Parcelable { @Override public int hashCode() { - return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos, - presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes)); + return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate, + appVsyncOffsetNanos, presentationDeadlineNanos, group, + Arrays.hashCode(supportedHdrTypes)); } } @@ -2785,10 +2794,22 @@ public final class SurfaceControl implements Parcelable { * as a new transaction. */ public void apply() { - apply(false); + apply(/*sync*/ false); } /** + * Applies the transaction as a one way binder call. This transaction will be applied out + * of order with other transactions that are applied synchronously. This method is not + * safe. It should only be used when the order does not matter. + * + * @hide + */ + public void applyAsyncUnsafe() { + apply(/*sync*/ false, /*oneWay*/ true); + } + + + /** * Clear the transaction object, without applying it. * * @hide @@ -2817,9 +2838,13 @@ public final class SurfaceControl implements Parcelable { * @hide */ public void apply(boolean sync) { + apply(sync, /*oneWay*/ false); + } + + private void apply(boolean sync, boolean oneWay) { applyResizedSurfaces(); notifyReparentedSurfaces(); - nativeApplyTransaction(mNativeObject, sync); + nativeApplyTransaction(mNativeObject, sync, oneWay); } /** @@ -3684,6 +3709,10 @@ public final class SurfaceControl implements Parcelable { * @param sc The SurfaceControl to specify the frame rate category of. * @param category The frame rate category of this surface. The category value may influence * the system's choice of display frame rate. + * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not + * change if changing it would cause jank. Else {@code false}. + * This parameter is ignored when {@code category} is + * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}. * * @return This transaction object. * @@ -3692,10 +3721,10 @@ public final class SurfaceControl implements Parcelable { * @hide */ @NonNull - public Transaction setFrameRateCategory( - @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) { + public Transaction setFrameRateCategory(@NonNull SurfaceControl sc, + @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) { checkPreconditions(sc); - nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category); + nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly); return this; } @@ -4241,8 +4270,7 @@ public final class SurfaceControl implements Parcelable { * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in * this API as well. * <p> - * @param sc The {@link SurfaceControl} to set the - * {@link TrustedPresentationCallback} on + * @param sc The {@link SurfaceControl} to set the callback on * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to * invoke the callback. * @param executor The {@link Executor} where the callback will be invoked on. @@ -4275,10 +4303,9 @@ public final class SurfaceControl implements Parcelable { } /** - * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl} + * Clears the callback for a specific {@link SurfaceControl} * - * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be - * cleared from + * @param sc The SurfaceControl that the callback should be cleared from * @return This transaction */ @NonNull @@ -4373,7 +4400,7 @@ public final class SurfaceControl implements Parcelable { void applyGlobalTransaction(boolean sync) { applyResizedSurfaces(); notifyReparentedSurfaces(); - nativeApplyTransaction(mNativeObject, sync); + nativeApplyTransaction(mNativeObject, sync, /*oneWay*/ false); } @Override diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cfde4003da9b..16318e0a3f49 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12161,12 +12161,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * a precision touch gesture in a small area in either the X or Y dimension, such as * an edge swipe or dragging a <code>SeekBar</code> thumb.</p> * - * <p>On Wear OS, these rects control where system-level swipe-to-dismiss gesture can start. If - * the attribute {@code android:windowSwipeToDismiss} has been set to {@code false}, the system - * will create an exclusion rect with size equal to the window frame size. In order words, the - * system swipe-to-dismiss will not apply, and the app must handle gestural input within itself. - * </p> - * * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the * exclusions it takes into account. The limit does not apply while the navigation * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cf46bcccdf87..b5648cc90dcd 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1730,7 +1730,7 @@ public final class ViewRootImpl implements ViewParent, attrs.getTitle().toString()); mAttachInfo.mThreadedRenderer = renderer; renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue); - updateColorModeIfNeeded(attrs.getColorMode()); + updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom()); updateRenderHdrSdrRatio(); updateForceDarkMode(); mAttachInfo.mHardwareAccelerated = true; @@ -2232,9 +2232,7 @@ public final class ViewRootImpl implements ViewParent, mStopped = stopped; final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer; if (renderer != null) { - if (DEBUG_DRAW) { - Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped); - } + if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped); renderer.setStopped(mStopped); } if (!mStopped) { @@ -3351,7 +3349,7 @@ public final class ViewRootImpl implements ViewParent, } final boolean alwaysConsumeSystemBarsChanged = mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars; - updateColorModeIfNeeded(lp.getColorMode()); + updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom()); surfaceCreated = !hadSurface && mSurface.isValid(); surfaceDestroyed = hadSurface && !mSurface.isValid(); @@ -3879,8 +3877,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - "view not visible"); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, "view not visible"); } else if (cancelAndRedraw) { mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() @@ -3895,8 +3893,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } if (!performDraw(mActiveSurfaceSyncGroup)) { - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - mLastPerformDrawSkippedReason); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, mLastPerformDrawSkippedReason); } } @@ -4774,8 +4772,8 @@ public final class ViewRootImpl implements ViewParent, if (mSurfaceHolder != null && mSurface.isValid()) { usingAsyncReport = true; SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, - "SurfaceHolder"); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null, + pendingTransaction, "SurfaceHolder"); }); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); @@ -4789,8 +4787,8 @@ public final class ViewRootImpl implements ViewParent, } if (!usingAsyncReport) { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, - "no async report"); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null, + pendingTransaction, "no async report"); } if (mPerformContentCapture) { @@ -4800,13 +4798,14 @@ public final class ViewRootImpl implements ViewParent, } private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup, - @Nullable Transaction pendingTransaction, String logReason) { + boolean hasPendingTransaction, @Nullable Transaction pendingTransaction, + String logReason) { if (surfaceSyncGroup != null) { - if (pendingTransaction != null) { + if (hasPendingTransaction && pendingTransaction != null) { surfaceSyncGroup.addTransaction(pendingTransaction); } surfaceSyncGroup.markSyncReady(); - } else if (pendingTransaction != null) { + } else if (hasPendingTransaction && pendingTransaction != null) { Trace.instant(Trace.TRACE_TAG_VIEW, "Transaction not synced due to " + logReason + "-" + mTag); if (DEBUG_BLAST) { @@ -5320,6 +5319,29 @@ public final class ViewRootImpl implements ViewParent, } /** + * Called from DecorView when gesture interception state has changed. + * + * @param intercepted If DecorView is intercepting touch events + */ + public void updateDecorViewGestureInterception(boolean intercepted) { + mHandler.sendMessage( + mHandler.obtainMessage( + MSG_DECOR_VIEW_GESTURE_INTERCEPTION, + /* arg1= */ intercepted ? 1 : 0, + /* arg2= */ 0)); + } + + void decorViewInterceptionChanged(boolean intercepted) { + if (mView != null) { + try { + mWindowSession.reportDecorViewGestureInterceptionChanged(mWindow, intercepted); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** * Set the root-level system gesture exclusion rects. These are added to those provided by * the root's view hierarchy. */ @@ -5630,7 +5652,8 @@ public final class ViewRootImpl implements ViewParent, mUpdateHdrSdrRatioInfo = true; } - private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) { + private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode, + float desiredRatio) { if (mAttachInfo.mThreadedRenderer == null) { return; } @@ -5644,7 +5667,10 @@ public final class ViewRootImpl implements ViewParent, && !getConfiguration().isScreenWideColorGamut()) { colorMode = ActivityInfo.COLOR_MODE_DEFAULT; } - float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + float automaticRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + if (desiredRatio == 0 || desiredRatio > automaticRatio) { + desiredRatio = automaticRatio; + } if (desiredRatio != mDesiredHdrSdrRatio) { mDesiredHdrSdrRatio = desiredRatio; updateRenderHdrSdrRatio(); @@ -5942,6 +5968,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 35; private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36; private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37; + private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38; final class ViewRootHandler extends Handler { @Override @@ -6220,6 +6247,9 @@ public final class ViewRootImpl implements ViewParent, case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: { systemGestureExclusionChanged(); } break; + case MSG_DECOR_VIEW_GESTURE_INTERCEPTION: { + decorViewInterceptionChanged(/* intercepted= */ msg.arg1 == 1); + } break; case MSG_KEEP_CLEAR_RECTS_CHANGED: { keepClearRectsChanged(/* accessibilityFocusRectChanged= */ msg.arg1 == 1); } break; @@ -8579,10 +8609,6 @@ public final class ViewRootImpl implements ViewParent, mLastLayoutFrame.set(frame); } - if (mOnBackInvokedDispatcher.isSystemGestureExclusionNeeded()) { - setRootSystemGestureExclusionRects(List.of(frame)); - } - final WindowConfiguration winConfig = getCompatWindowConfiguration(); mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop() ? winConfig.getMaxBounds() @@ -9027,8 +9053,8 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; AnimationHandler.removeRequestor(this); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - "shutting down VRI"); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, "shutting down VRI"); WindowManagerGlobal.getInstance().doRemoveView(this); } @@ -10521,6 +10547,8 @@ public final class ViewRootImpl implements ViewParent, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { + // Although this is a AIDL method, it will only be triggered in local process through + // either WindowStateResizeItem or WindowlessWindowManager. final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 2f04b0c695da..87537fbc9961 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -24,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -1330,6 +1332,47 @@ public abstract class Window { } /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * <p>By default the system will choose an amount of HDR headroom that is appropriate + * for the underlying device capabilities & bit-depth of the panel. However, for some types + * of content this can end up being more headroom than necessary or desired. An example + * would be a messaging app or gallery thumbnail view where some amount of HDR pop is desired + * without overly influencing the perceived brightness of the majority SDR content. This can + * also be used to animate in/out of an HDR range for smoother transitions.</p> + * + * <p>Note: The actual amount of HDR headroom that will be given is subject to a variety + * of factors such as ambient conditions, display capabilities, or bit-depth limitations. + * See {@link Display#getHdrSdrRatio()} for more information as well as how to query the + * current value.</p> + * + * @param desiredHeadroom The amount of HDR headroom that is desired. Must be >= 1.0 (no HDR) + * and <= 10,000.0. Passing 0.0 will reset to the default, automatically + * chosen value. + * @see #getDesiredHdrHeadroom() + * @see Display#getHdrSdrRatio() + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0) float desiredHeadroom) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.setDesiredHdrHeadroom(desiredHeadroom); + dispatchWindowAttributesChanged(attrs); + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + * @see #setDesiredHdrHeadroom(float) + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return getAttributes().getDesiredHdrHeadroom(); + } + + /** * If {@code isPreferred} is true, this method requests that the connected display does minimal * post processing when this window is visible on the screen. Otherwise, it requests that the * display switches back to standard image processing. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 2f4bea0270c1..a3b93b433dda 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -82,6 +82,8 @@ import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -1683,7 +1685,7 @@ public interface WindowManager extends ViewManager { * orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This * listener gives application an opportunity to selectively react to device orientation changes. * The newly added listener will be called with current proposed rotation. Note that the context - * of this window manager instance must be a {@link android.annotation.UiContext}. + * of this window manager instance must be a {@code UiContext}. * * @param executor The executor on which callback method will be invoked. * @param listener Called when the proposed rotation for the context is being delivered. @@ -1691,7 +1693,7 @@ public interface WindowManager extends ViewManager { * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and * {@link Surface#ROTATION_270}. * @throws UnsupportedOperationException if this method is called on an instance that is not - * associated with a {@link android.annotation.UiContext}. + * associated with a {@code UiContext}. */ default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor, @NonNull IntConsumer listener) { @@ -3113,7 +3115,7 @@ public interface WindowManager extends ViewManager { /** * Never animate position changes of the window. * - * @see android.R.attr#Window_windowNoMoveAnimation + * @see android.R.styleable#Window_windowNoMoveAnimation * {@hide} */ @UnsupportedAppUsage @@ -4314,6 +4316,9 @@ public interface WindowManager extends ViewManager { @ActivityInfo.ColorMode private int mColorMode = COLOR_MODE_DEFAULT; + /** @hide */ + private float mDesiredHdrHeadroom = 0; + /** * Carries the requests about {@link WindowInsetsController.Appearance} and * {@link WindowInsetsController.Behavior} to the system windows which can produce insets. @@ -4526,7 +4531,7 @@ public interface WindowManager extends ViewManager { * Set whether animations can be played for position changes on this window. If disabled, * the window will move to its new position instantly without animating. * - * @attr ref android.R.attr#Window_windowNoMoveAnimation + * @attr ref android.R.styleable#Window_windowNoMoveAnimation */ public void setCanPlayMoveAnimation(boolean enable) { if (enable) { @@ -4541,7 +4546,7 @@ public interface WindowManager extends ViewManager { * This does not guarantee that an animation will be played in all such situations. For * example, drag-resizing may move the window but not play an animation. * - * @attr ref android.R.attr#Window_windowNoMoveAnimation + * @attr ref android.R.styleable#Window_windowNoMoveAnimation */ public boolean canPlayMoveAnimation() { return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0; @@ -4716,6 +4721,39 @@ public interface WindowManager extends ViewManager { } /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * @see Window#setDesiredHdrHeadroom(float) + * @param desiredHeadroom Desired amount of HDR headroom. Must be in the range of 1.0 (SDR) + * to 10,000.0, or 0.0 to reset to default. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0f) float desiredHeadroom) { + if (!Float.isFinite(desiredHeadroom)) { + throw new IllegalArgumentException("desiredHeadroom must be finite: " + + desiredHeadroom); + } + if (desiredHeadroom != 0 && (desiredHeadroom < 1.0f || desiredHeadroom > 10000.0f)) { + throw new IllegalArgumentException( + "desiredHeadroom must be 0.0 or in the range [1.0, 10000.0f], received: " + + desiredHeadroom); + } + mDesiredHdrHeadroom = desiredHeadroom; + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return mDesiredHdrHeadroom; + } + + /** * <p> * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount}, * but instead of dimmed, the content behind the window will be blurred (or combined with @@ -4865,6 +4903,7 @@ public interface WindowManager extends ViewManager { checkNonRecursiveParams(); out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */); out.writeInt(mDisplayFlags); + out.writeFloat(mDesiredHdrHeadroom); } public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR @@ -4936,6 +4975,7 @@ public interface WindowManager extends ViewManager { forciblyShownTypes = in.readInt(); paramsForRotation = in.createTypedArray(LayoutParams.CREATOR); mDisplayFlags = in.readInt(); + mDesiredHdrHeadroom = in.readFloat(); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -5196,6 +5236,11 @@ public interface WindowManager extends ViewManager { changes |= COLOR_MODE_CHANGED; } + if (mDesiredHdrHeadroom != o.mDesiredHdrHeadroom) { + mDesiredHdrHeadroom = o.mDesiredHdrHeadroom; + changes |= COLOR_MODE_CHANGED; + } + if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) { preferMinimalPostProcessing = o.preferMinimalPostProcessing; changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED; @@ -5423,6 +5468,9 @@ public interface WindowManager extends ViewManager { if (mColorMode != COLOR_MODE_DEFAULT) { sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode)); } + if (mDesiredHdrHeadroom != 0) { + sb.append(" desiredHdrHeadroom=").append(mDesiredHdrHeadroom); + } if (preferMinimalPostProcessing) { sb.append(" preferMinimalPostProcessing="); sb.append(preferMinimalPostProcessing); @@ -5821,6 +5869,7 @@ public interface WindowManager extends ViewManager { * * @hide */ + @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @TestApi @RequiresPermission(permission.ACCESS_SURFACE_FLINGER) default boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) { @@ -5836,6 +5885,7 @@ public interface WindowManager extends ViewManager { * * @hide */ + @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @TestApi @RequiresPermission(permission.ACCESS_SURFACE_FLINGER) default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index 7ad43c76efaa..26298bc645ad 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -47,7 +47,6 @@ import java.util.function.Supplier; * @see WindowInsets#getInsets(int) * @see WindowManager#getCurrentWindowMetrics() * @see WindowManager#getMaximumWindowMetrics() - * @see android.annotation.UiContext */ public final class WindowMetrics { @NonNull @@ -99,8 +98,7 @@ public final class WindowMetrics { } /** - * Returns the bounds of the area associated with this window or - * {@link android.annotation.UiContext}. + * Returns the bounds of the area associated with this window or {@code UiContext}. * <p> * <b>Note that the size of the reported bounds can have different size than * {@link Display#getSize(Point)}.</b> This method reports the window size including all system @@ -133,7 +131,7 @@ public final class WindowMetrics { /** * Returns the {@link WindowInsets} of the area associated with this window or - * {@link android.annotation.UiContext}. + * {@code UiContext}. * * @return the {@link WindowInsets} of the visual area. */ @@ -146,9 +144,8 @@ public final class WindowMetrics { } /** - * Returns the density of the area associated with this window or - * {@link android.annotation.UiContext}, which uses the same units as - * {@link android.util.DisplayMetrics#density}. + * Returns the density of the area associated with this window or {@code UiContext}, + * which uses the same units as {@link android.util.DisplayMetrics#density}. * * @see android.util.DisplayMetrics#DENSITY_DEFAULT * @see android.util.DisplayMetrics#density diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 8fe9b7bc0ca4..7c3b6ae42fcf 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -584,9 +584,13 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public void reportKeepClearAreasChanged(android.view.IWindow window, List<Rect> restrictedRects, - List<Rect> unrestrictedRects) { - } + public void reportDecorViewGestureInterceptionChanged(IWindow window, boolean intercepted) {} + + @Override + public void reportKeepClearAreasChanged( + android.view.IWindow window, + List<Rect> restrictedRects, + List<Rect> unrestrictedRects) {} @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index e6a8b7827b04..43bfe139c223 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1562,9 +1562,8 @@ public class AccessibilityNodeInfo implements Parcelable { * describes the action. * </p> * <p> - * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, - * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the - * view. + * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence, + * AccessibilityViewCommand)} to register an action directly on the view. * <p> * <strong>Note:</strong> Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. @@ -5167,8 +5166,7 @@ public class AccessibilityNodeInfo implements Parcelable { * </p> * <aside class="note"> * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, - * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the - * view. + * CharSequence, AccessibilityViewCommand)} to register an action directly on the view. * </p> */ public static final class AccessibilityAction implements Parcelable { diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index e31ad82d3f55..85dadd4a061d 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -5,4 +5,11 @@ flag { name: "force_invert_color" description: "Enable force force-dark for smart inversion and dark theme everywhere" bug: "282821643" -}
\ No newline at end of file +} + +flag { + namespace: "accessibility" + name: "allow_shortcut_chooser_on_lockscreen" + description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen" + bug: "303871725" +} diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index a07b62fe2890..32256b9b09c8 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -56,7 +56,7 @@ public class AnimationUtils { private static final int SEQUENTIALLY = 1; /** - * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, + * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, * this change ID enables to use expectedPresentationTime instead of the frameTime * for the frame start time . * @@ -108,11 +108,14 @@ public class AnimationUtils { * @hide */ @TestApi + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) { AnimationState state = sAnimationState.get(); state.animationClockLocked = true; state.currentVsyncTimeMillis = vsyncMillis; - state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos; + if (!expectedPresentationTimeApi()) { + state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos; + } } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 89fa83e5c7aa..a40ff643379a 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -16,7 +16,6 @@ package android.view.autofill; -import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS; import static android.service.autofill.FillRequest.FLAG_IME_SHOWING; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; @@ -31,12 +30,10 @@ import static android.view.autofill.Helper.sVerbose; import static android.view.autofill.Helper.toList; import android.accessibilityservice.AccessibilityServiceInfo; -import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; -import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -53,20 +50,17 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.metrics.LogMaker; -import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; -import android.os.ICancellationSignal; import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.AutofillService; -import android.service.autofill.FillCallback; import android.service.autofill.FillEventHistory; +import android.service.autofill.Flags; import android.service.autofill.IFillCallback; import android.service.autofill.UserData; import android.text.TextUtils; @@ -88,7 +82,6 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; -import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodManager; import android.widget.CheckBox; import android.widget.DatePicker; @@ -118,7 +111,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import sun.misc.Cleaner; @@ -188,12 +180,6 @@ import sun.misc.Cleaner; * shows an autofill save UI if the value of savable views have changed. If the user selects the * option to Save, the current value of the views is then sent to the autofill service. * - * <p>There is another choice for the application to provide it's datasets to the Autofill framework - * by setting an {@link AutofillRequestCallback} through - * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use - * its callback instead of the default {@link AutofillService}. See - * {@link AutofillRequestCallback} for more details. - * * <h3 id="additional-notes">Additional notes</h3> * * <p>It is safe to call <code>AutofillManager</code> methods from any thread. @@ -277,6 +263,12 @@ public final class AutofillManager { "android.view.autofill.extra.CLIENT_STATE"; /** + * @hide + */ + public static final String EXTRA_AUTH_STATE = + "android.view.autofill.extra.AUTH_STATE"; + + /** * Intent extra: the {@link android.view.inputmethod.InlineSuggestionsRequest} in the * autofill request. * @@ -327,7 +319,6 @@ public final class AutofillManager { /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8; - /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20; // NOTE: flag below is used by the session start receiver only, hence it can have values above /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1; @@ -428,6 +419,14 @@ public final class AutofillManager { public static final int STATE_UNKNOWN_FAILED = 6; /** + * Same as {@link #STATE_ACTIVE}, but when pending authentication after + * {@link AutofillManagerClient#authenticate(int, int, IntentSender, Intent, boolean)} + * + * @hide + */ + public static final int STATE_PENDING_AUTHENTICATION = 7; + + /** * Timeout in ms for calls to the field classification service. * @hide */ @@ -661,11 +660,6 @@ public final class AutofillManager { @GuardedBy("mLock") private boolean mEnabledForAugmentedAutofillOnly; - @GuardedBy("mLock") - @Nullable private AutofillRequestCallback mAutofillRequestCallback; - @GuardedBy("mLock") - @Nullable private Executor mRequestCallbackExecutor; - private boolean mScreenHasCredmanField; /** @@ -731,6 +725,10 @@ public final class AutofillManager { // Indicate whether WebView should always be included in the assist structure private boolean mShouldAlwaysIncludeWebviewInAssistStructure; + // Controls logic around apps changing some properties of their views when activity loses + // focus due to autofill showing biometric activity, password manager, or password breach check. + private boolean mRelayoutFix; + // Indicates whether called the showAutofillDialog() method. private boolean mShowAutofillDialogCalled = false; @@ -952,6 +950,8 @@ public final class AutofillManager { mShouldAlwaysIncludeWebviewInAssistStructure = AutofillFeatureFlags.shouldAlwaysIncludeWebviewInAssistStructure(); + + mRelayoutFix = Flags.relayout(); } /** @@ -1715,7 +1715,13 @@ public final class AutofillManager { synchronized (mLock) { if (mForAugmentedAutofillOnly) { if (sVerbose) { - Log.v(TAG, "notifyViewVisibilityChanged(): ignoring on augmented only mode"); + Log.v(TAG, "notifyViewVisibilityChanged(): ignoring on augmented only mode"); + } + return; + } + if (mRelayoutFix && mState == STATE_PENDING_AUTHENTICATION) { + if (sVerbose) { + Log.v(TAG, "notifyViewVisibilityChanged(): ignoring in auth pending mode"); } return; } @@ -2342,6 +2348,7 @@ public final class AutofillManager { if (!isActiveLocked()) { return; } + mState = STATE_ACTIVE; // If authenticate activity closes itself during onCreate(), there is no onStop/onStart // of app activity. We enforce enter event to re-show fill ui in such case. // CTS example: @@ -2406,38 +2413,6 @@ public final class AutofillManager { return new AutofillId(parent.getAutofillViewId(), virtualId); } - /** - * Sets the client's suggestions callback for autofill. - * - * @see AutofillRequestCallback - * - * @param executor specifies the thread upon which the callbacks will be invoked. - * @param callback which handles autofill request to provide client's suggestions. - */ - @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull AutofillRequestCallback callback) { - if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!"); - } - - synchronized (mLock) { - mRequestCallbackExecutor = executor; - mAutofillRequestCallback = callback; - } - } - - /** - * clears the client's suggestions callback for autofill. - */ - public void clearAutofillRequestCallback() { - synchronized (mLock) { - mRequestCallbackExecutor = null; - mAutofillRequestCallback = null; - } - } - @GuardedBy("mLock") private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, @NonNull AutofillValue value, int flags) { @@ -2498,13 +2473,6 @@ public final class AutofillManager { } } - if (mAutofillRequestCallback != null) { - if (sDebug) { - Log.d(TAG, "startSession with the client suggestions provider"); - } - flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS; - } - mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, clientActivity, @@ -2830,6 +2798,9 @@ public final class AutofillManager { Intent fillInIntent, boolean authenticateInline) { synchronized (mLock) { if (sessionId == mSessionId) { + if (mRelayoutFix) { + mState = STATE_PENDING_AUTHENTICATION; + } final AutofillClient client = getClient(); if (client != null) { // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() @@ -2859,28 +2830,6 @@ public final class AutofillManager { } } - private void onFillRequest(InlineSuggestionsRequest request, - CancellationSignal cancellationSignal, FillCallback callback) { - final AutofillRequestCallback autofillRequestCallback; - final Executor executor; - synchronized (mLock) { - autofillRequestCallback = mAutofillRequestCallback; - executor = mRequestCallbackExecutor; - } - if (autofillRequestCallback != null && executor != null) { - final long ident = Binder.clearCallingIdentity(); - try { - executor.execute(() -> - autofillRequestCallback.onFillRequest( - request, cancellationSignal, callback)); - } finally { - Binder.restoreCallingIdentity(ident); - } - } else { - callback.onSuccess(null); - } - } - /** @hide */ public static final int SET_STATE_FLAG_ENABLED = 0x01; /** @hide */ @@ -3563,6 +3512,8 @@ public final class AutofillManager { return "UNKNOWN"; case STATE_ACTIVE: return "ACTIVE"; + case STATE_PENDING_AUTHENTICATION: + return "PENDING_AUTHENTICATION"; case STATE_FINISHED: return "FINISHED"; case STATE_SHOWING_SAVE_UI: @@ -3592,7 +3543,12 @@ public final class AutofillManager { @GuardedBy("mLock") private boolean isActiveLocked() { - return mState == STATE_ACTIVE; + return mState == STATE_ACTIVE || isPendingAuthenticationLocked(); + } + + @GuardedBy("mLock") + private boolean isPendingAuthenticationLocked() { + return mRelayoutFix && mState == STATE_PENDING_AUTHENTICATION; } @GuardedBy("mLock") @@ -4457,23 +4413,6 @@ public final class AutofillManager { } @Override - public void requestFillFromClient(int id, InlineSuggestionsRequest request, - IFillCallback callback) { - final AutofillManager afm = mAfm.get(); - if (afm != null) { - ICancellationSignal transport = CancellationSignal.createTransport(); - try { - callback.onCancellable(transport); - } catch (RemoteException e) { - Slog.w(TAG, "Error requesting a cancellation", e); - } - - afm.onFillRequest(request, CancellationSignal.fromTransport(transport), - new FillCallback(callback, id)); - } - } - - @Override public void notifyFillDialogTriggerIds(List<AutofillId> ids) { final AutofillManager afm = mAfm.get(); if (afm != null) { diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java deleted file mode 100644 index e632a5849471..000000000000 --- a/core/java/android/view/autofill/AutofillRequestCallback.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.autofill; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.CancellationSignal; -import android.service.autofill.FillCallback; -import android.view.inputmethod.InlineSuggestionsRequest; - -/** - * <p>This class is used to provide some input suggestions to the Autofill framework. - * - * <P>When the user is requested to input something, Autofill will try to query input suggestions - * for the user choosing. If the application want to provide some internal input suggestions, - * implements this callback and register via - * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor, - * AutofillRequestCallback)}. Autofill will callback the - * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request - * input suggestions. - * - * <P>To make sure the callback to take effect, must register before the autofill session starts. - * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current - * session, and then the callback will be used at the next restarted session. - * - * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch - * {@link AutofillId}s from its view structure. Below is an example: - * <pre class="prettyprint"> - * AutofillId usernameId = findViewById(R.id.username).getAutofillId(); - * AutofillId passwordId = findViewById(R.id.password).getAutofillId(); - * </pre> - * To learn more about creating a {@link android.service.autofill.FillResponse}, read - * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>. - * - * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond - * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill - * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback - * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the - * client would like to keep no suggestions for the field, respond with an empty - * {@link android.service.autofill.FillResponse} which has no dataset. - * - * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or - * the keyboard may choose to block your app from the inline strip. - */ -public interface AutofillRequestCallback { - /** - * Called by the Android system to decide if a screen can be autofilled by the callback. - * - * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if - * currently inline suggestions are supported and can be displayed. - * @param cancellationSignal signal for observing cancellation requests. The system will use - * this to notify you that the fill result is no longer needed and you should stop - * handling this fill request in order to save resources. - * @param callback object used to notify the result of the request. - */ - void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest, - @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback); -} diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 6e13097fe319..917a974f992d 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -24,11 +24,9 @@ import android.content.Intent; import android.content.IntentSender; import android.graphics.Rect; import android.os.IBinder; -import android.service.autofill.IFillCallback; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.view.autofill.IAutofillWindowPresenter; -import android.view.inputmethod.InlineSuggestionsRequest; import android.view.KeyEvent; import com.android.internal.os.IResultReceiver; @@ -149,12 +147,6 @@ oneway interface IAutoFillManagerClient { void requestShowSoftInput(in AutofillId id); /** - * Requests to determine if a screen can be autofilled by the client app. - */ - void requestFillFromClient(int id, in InlineSuggestionsRequest request, - in IFillCallback callback); - - /** * Notifies autofill ids that require to show the fill dialog. */ void notifyFillDialogTriggerIds(in List<AutofillId> ids); diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 2c7d326587c7..42b3e38b544f 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -60,7 +60,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -377,6 +379,30 @@ public final class ContentCaptureManager { public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE = "content_protection_buffer_size"; + /** + * Sets the config for content protection required groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = + "content_protection_required_groups_config"; + + /** + * Sets the config for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = + "content_protection_optional_groups_config"; + + /** + * Sets the threshold for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = + "content_protection_optional_groups_threshold"; + /** @hide */ @TestApi public static final int LOGGING_LEVEL_OFF = 0; @@ -417,6 +443,18 @@ public final class ContentCaptureManager { public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000; /** @hide */ public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = ""; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = ""; + /** @hide */ + public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0; private final Object mLock = new Object(); diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index dc3d32317ded..bb815c0e8317 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -183,7 +183,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10; /** - * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps + * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps * the virtual children with a pair of view tree appearing and view tree appeared events. */ @ChangeId diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java index 6e3d9a8786af..927874ffc3a2 100644 --- a/core/java/android/view/displayhash/DisplayHashResultCallback.java +++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java @@ -106,7 +106,7 @@ public interface DisplayHashResultCallback { * {@link android.view.View#generateDisplayHash(String, Rect, Executor, * DisplayHashResultCallback)} results in an error and cannot generate a display hash. * - * @param errorCode One of the values in {@link DisplayHashErrorCode} + * @param errorCode the error code */ void onDisplayHashError(@DisplayHashErrorCode int errorCode); } diff --git a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 13a6f8d52dc6..56b5fac1c64a 100644 --- a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -19,4 +19,11 @@ flag { namespace: "toolkit" description: "Feature flag for using expected presentation time of the Choreographer" bug: "278730197" +} + +flag { + name: "set_frame_rate_callback" + namespace: "core_graphics" + description: "Enable the `setFrameRate` callback" + bug: "299946220" }
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index a92420a2f373..c5114b9550db 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -47,7 +47,6 @@ import android.view.MotionEvent; import android.view.MotionEvent.ToolType; import android.view.View; import android.view.autofill.AutofillId; -import android.widget.Editor; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodDebug; @@ -722,9 +721,9 @@ public class EditorInfo implements InputType, Parcelable { private boolean mIsStylusHandwritingEnabled; /** - * Set {@code true} if the {@link Editor} has + * Set {@code true} if the {@code Editor} has * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. - * {@code false} by default, {@link Editor} must set it {@code true} to indicate that + * {@code false} by default, {@code Editor} must set it {@code true} to indicate that * it supports stylus handwriting. * * @param enabled {@code true} if stylus handwriting is enabled. @@ -736,7 +735,7 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Returns {@code true} when an {@link Editor} has stylus handwriting enabled. + * Returns {@code true} when an {@code Editor} has stylus handwriting enabled. * {@code false} by default. * @see #setStylusHandwritingEnabled(boolean) * @see InputMethodManager#isStylusHandwritingAvailable() diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 70279cc8e845..c83dfe8e68f4 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -111,22 +111,6 @@ public final class InlineSuggestionsRequest implements Parcelable { private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; /** - * Whether the IME supports inline suggestions from the default Autofill service that - * provides the input view. - * - * Note: The default value is {@code true}. - */ - private boolean mServiceSupported; - - /** - * Whether the IME supports inline suggestions from the application that provides the - * input view. - * - * Note: The default value is {@code true}. - */ - private boolean mClientSupported; - - /** * @hide * @see {@link #mHostInputToken}. */ @@ -220,14 +204,6 @@ public final class InlineSuggestionsRequest implements Parcelable { return Bundle.EMPTY; } - private static boolean defaultServiceSupported() { - return true; - } - - private static boolean defaultClientSupported() { - return true; - } - /** @hide */ abstract static class BaseBuilder { abstract Builder setInlinePresentationSpecs( @@ -240,25 +216,13 @@ public final class InlineSuggestionsRequest implements Parcelable { abstract Builder setHostDisplayId(int value); } - /** @hide */ - public boolean isServiceSupported() { - return mServiceSupported; - } - - /** @hide */ - public boolean isClientSupported() { - return mClientSupported; - } - - - - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java + // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -274,9 +238,7 @@ public final class InlineSuggestionsRequest implements Parcelable { @NonNull Bundle extras, @Nullable IBinder hostInputToken, int hostDisplayId, - @Nullable InlinePresentationSpec inlineTooltipPresentationSpec, - boolean serviceSupported, - boolean clientSupported) { + @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) { this.mMaxSuggestionCount = maxSuggestionCount; this.mInlinePresentationSpecs = inlinePresentationSpecs; com.android.internal.util.AnnotationValidations.validate( @@ -293,8 +255,6 @@ public final class InlineSuggestionsRequest implements Parcelable { this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; - this.mServiceSupported = serviceSupported; - this.mClientSupported = clientSupported; onConstructed(); } @@ -378,9 +338,7 @@ public final class InlineSuggestionsRequest implements Parcelable { } /** - * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. - * - * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec) + * Specifies the UI specification for the inline suggestion tooltip in the response. */ @DataClass.Generated.Member public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() { @@ -401,9 +359,7 @@ public final class InlineSuggestionsRequest implements Parcelable { "extras = " + mExtras + ", " + "hostInputToken = " + mHostInputToken + ", " + "hostDisplayId = " + mHostDisplayId + ", " + - "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " + - "serviceSupported = " + mServiceSupported + ", " + - "clientSupported = " + mClientSupported + + "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + " }"; } @@ -427,9 +383,7 @@ public final class InlineSuggestionsRequest implements Parcelable { && extrasEquals(that.mExtras) && java.util.Objects.equals(mHostInputToken, that.mHostInputToken) && mHostDisplayId == that.mHostDisplayId - && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec) - && mServiceSupported == that.mServiceSupported - && mClientSupported == that.mClientSupported; + && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec); } @Override @@ -447,8 +401,6 @@ public final class InlineSuggestionsRequest implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken); _hash = 31 * _hash + mHostDisplayId; _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec); - _hash = 31 * _hash + Boolean.hashCode(mServiceSupported); - _hash = 31 * _hash + Boolean.hashCode(mClientSupported); return _hash; } @@ -459,8 +411,6 @@ public final class InlineSuggestionsRequest implements Parcelable { // void parcelFieldName(Parcel dest, int flags) { ... } int flg = 0; - if (mServiceSupported) flg |= 0x100; - if (mClientSupported) flg |= 0x200; if (mHostInputToken != null) flg |= 0x20; if (mInlineTooltipPresentationSpec != null) flg |= 0x80; dest.writeInt(flg); @@ -486,11 +436,9 @@ public final class InlineSuggestionsRequest implements Parcelable { // static FieldType unparcelFieldName(Parcel in) { ... } int flg = in.readInt(); - boolean serviceSupported = (flg & 0x100) != 0; - boolean clientSupported = (flg & 0x200) != 0; int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>(); - in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class); + in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader()); String hostPackageName = in.readString(); LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR); Bundle extras = in.readBundle(); @@ -514,8 +462,6 @@ public final class InlineSuggestionsRequest implements Parcelable { this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; - this.mServiceSupported = serviceSupported; - this.mClientSupported = clientSupported; onConstructed(); } @@ -549,8 +495,6 @@ public final class InlineSuggestionsRequest implements Parcelable { private @Nullable IBinder mHostInputToken; private int mHostDisplayId; private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; - private boolean mServiceSupported; - private boolean mClientSupported; private long mBuilderFieldsSet = 0L; @@ -683,9 +627,7 @@ public final class InlineSuggestionsRequest implements Parcelable { } /** - * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. - * - * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s + * Specifies the UI specification for the inline suggestion tooltip in the response. */ @DataClass.Generated.Member public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) { @@ -695,38 +637,10 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } - /** - * Whether the IME supports inline suggestions from the default Autofill service that - * provides the input view. - * - * Note: The default value is {@code true}. - */ - @DataClass.Generated.Member - public @NonNull Builder setServiceSupported(boolean value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x100; - mServiceSupported = value; - return this; - } - - /** - * Whether the IME supports inline suggestions from the application that provides the - * input view. - * - * Note: The default value is {@code true}. - */ - @DataClass.Generated.Member - public @NonNull Builder setClientSupported(boolean value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x200; - mClientSupported = value; - return this; - } - /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x400; // Mark builder used + mBuilderFieldsSet |= 0x100; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); @@ -749,12 +663,6 @@ public final class InlineSuggestionsRequest implements Parcelable { if ((mBuilderFieldsSet & 0x80) == 0) { mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec(); } - if ((mBuilderFieldsSet & 0x100) == 0) { - mServiceSupported = defaultServiceSupported(); - } - if ((mBuilderFieldsSet & 0x200) == 0) { - mClientSupported = defaultClientSupported(); - } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, mInlinePresentationSpecs, @@ -763,14 +671,12 @@ public final class InlineSuggestionsRequest implements Parcelable { mExtras, mHostInputToken, mHostDisplayId, - mInlineTooltipPresentationSpec, - mServiceSupported, - mClientSupported); + mInlineTooltipPresentationSpec); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x400) != 0) { + if ((mBuilderFieldsSet & 0x100) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -778,10 +684,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1615798784918L, - codegenVersion = "1.0.22", + time = 1696889841006L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate boolean mServiceSupported\nprivate boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static boolean defaultServiceSupported()\nprivate static boolean defaultClientSupported()\npublic boolean isServiceSupported()\npublic boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 5bb1e9318175..8159af3ddd4a 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -100,7 +100,6 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; -import android.widget.Editor; import android.window.ImeOnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; @@ -2374,16 +2373,16 @@ public final class InputMethodManager { * Prepares delegation of starting stylus handwriting session to a different editor in same * or different window than the view on which initial handwriting stroke was detected. * - * Delegation can be used to start stylus handwriting session before the {@link Editor} view or + * Delegation can be used to start stylus handwriting session before the {@code Editor} view or * its {@link InputConnection} is started. Calling this method starts buffering of stylus * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which * point the handwriting session can be started and the buffered stylus motion events will be * delivered to the IME. * e.g. Delegation can be used when initial handwriting stroke is - * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual - * {@link Editor} is on a different window. + * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual + * {@code Editor} is on a different window. * - * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled + * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p> * * @param delegatorView the view that receives initial stylus stroke and delegates it to the @@ -2402,21 +2401,21 @@ public final class InputMethodManager { * different window in a different package than the view on which initial handwriting stroke * was detected. * - * Delegation can be used to start stylus handwriting session before the {@link Editor} view or + * Delegation can be used to start stylus handwriting session before the {@code Editor} view or * its {@link InputConnection} is started. Calling this method starts buffering of stylus * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at * which point the handwriting session can be started and the buffered stylus motion events will * be delivered to the IME. * e.g. Delegation can be used when initial handwriting stroke is - * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual - * {@link Editor} is on a different window in the given package. + * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual + * {@code Editor} is on a different window in the given package. * * <p>Note: If delegator and delegate are in same package use * {@link #prepareStylusHandwritingDelegation(View)} instead.</p> * * @param delegatorView the view that receives initial stylus stroke and delegates it to the * actual editor. Its window must {@link View#hasWindowFocus have focus}. - * @param delegatePackageName package name that contains actual {@link Editor} which should + * @param delegatePackageName package name that contains actual {@code Editor} which should * start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}. * @see #prepareStylusHandwritingDelegation(View) * @see #acceptStylusHandwritingDelegation(View, String) diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java index 5be0e3f3ccf2..78de7e16edd7 100644 --- a/core/java/android/view/inspector/PropertyReader.java +++ b/core/java/android/view/inspector/PropertyReader.java @@ -124,7 +124,7 @@ public interface PropertyReader { void readObject(int id, @Nullable Object value); /** - * Read a color packed into a {@link ColorInt} as a property. + * Read a color packed into an int as a property. * * @param id Identifier of the property from a {@link PropertyMapper} * @param value Value of the property diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 26ceea6d1e4c..1fdd1a5a5a5f 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -80,6 +80,7 @@ import android.view.animation.LinearInterpolator; import android.view.autofill.AutofillId; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureSession; +import android.view.flags.Flags; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; @@ -92,7 +93,6 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.SurroundingText; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; -import android.widget.flags.Flags; import android.widget.RemoteViews.InteractionHandler; import com.android.internal.R; @@ -4518,7 +4518,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int overscrollMode = getOverScrollMode(); if (!trackMotionScroll(delta, delta)) { - if (Flags.platformWidgetHapticScrollFeedback()) { + if (Flags.scrollFeedbackApi()) { initHapticScrollFeedbackProviderIfNotExists(); mHapticScrollFeedbackProvider.onScrollProgress( event.getDeviceId(), event.getSource(), axis, delta); @@ -4534,7 +4534,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te float overscroll = (delta - (motionViewRealTop - motionViewPrevTop)) / ((float) getHeight()); boolean hitTopLimit = delta > 0; - if (Flags.platformWidgetHapticScrollFeedback()) { + if (Flags.scrollFeedbackApi()) { initHapticScrollFeedbackProviderIfNotExists(); mHapticScrollFeedbackProvider.onScrollLimit( event.getDeviceId(), event.getSource(), axis, diff --git a/core/java/android/widget/DifferentialMotionFlingHelper.java b/core/java/android/widget/DifferentialMotionFlingHelper.java index 95d24ec31209..ef01c3b79059 100644 --- a/core/java/android/widget/DifferentialMotionFlingHelper.java +++ b/core/java/android/widget/DifferentialMotionFlingHelper.java @@ -21,6 +21,8 @@ import android.content.Context; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; +import android.widget.flags.FeatureFlags; +import android.widget.flags.FeatureFlagsImpl; import com.android.internal.annotations.VisibleForTesting; @@ -50,6 +52,8 @@ public class DifferentialMotionFlingHelper { private final FlingVelocityThresholdCalculator mVelocityThresholdCalculator; private final DifferentialVelocityProvider mVelocityProvider; + private final FeatureFlags mWidgetFeatureFlags; + @Nullable private VelocityTracker mVelocityTracker; private float mLastFlingVelocity; @@ -134,7 +138,8 @@ public class DifferentialMotionFlingHelper { this(context, target, DifferentialMotionFlingHelper::calculateFlingVelocityThresholds, - DifferentialMotionFlingHelper::getCurrentVelocity); + DifferentialMotionFlingHelper::getCurrentVelocity, + /* widgetFeatureFlags= */ new FeatureFlagsImpl()); } @VisibleForTesting @@ -142,11 +147,13 @@ public class DifferentialMotionFlingHelper { Context context, DifferentialMotionFlingTarget target, FlingVelocityThresholdCalculator velocityThresholdCalculator, - DifferentialVelocityProvider velocityProvider) { + DifferentialVelocityProvider velocityProvider, + FeatureFlags widgetFeatureFlags) { mContext = context; mTarget = target; mVelocityThresholdCalculator = velocityThresholdCalculator; mVelocityProvider = velocityProvider; + mWidgetFeatureFlags = widgetFeatureFlags; } /** @@ -156,6 +163,9 @@ public class DifferentialMotionFlingHelper { * @param axis the axis being processed by the target View. */ public void onMotionEvent(MotionEvent event, int axis) { + if (!mWidgetFeatureFlags.enablePlatformWidgetDifferentialMotionFling()) { + return; + } boolean flingParamsChanged = calculateFlingVelocityThresholds(event, axis); if (mFlingVelocityThresholds[0] == Integer.MAX_VALUE) { // Integer.MAX_VALUE means that the device does not support fling for the current diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index e0e72ba1b9db..a1ebde76e98e 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -47,8 +47,8 @@ import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; +import android.view.flags.Flags; import android.view.inspector.InspectableProperty; -import android.widget.flags.Flags; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -1011,14 +1011,14 @@ public class ScrollView extends FrameLayout { if (newScrollY != oldScrollY) { super.scrollTo(mScrollX, newScrollY); if (hitLimit) { - if (Flags.platformWidgetHapticScrollFeedback()) { + if (Flags.scrollFeedbackApi()) { initHapticScrollFeedbackProviderIfNotExists(); mHapticScrollFeedbackProvider.onScrollLimit( event.getDeviceId(), event.getSource(), axis, /* isStart= */ newScrollY == 0); } } else { - if (Flags.platformWidgetHapticScrollFeedback()) { + if (Flags.scrollFeedbackApi()) { initHapticScrollFeedbackProviderIfNotExists(); mHapticScrollFeedbackProvider.onScrollProgress( event.getDeviceId(), event.getSource(), axis, delta); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2c413300ef17..e8281eac5928 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -567,6 +567,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mShadowDy; private int mShadowColor; + private int mLastOrientation; + private boolean mPreDrawRegistered; private boolean mPreDrawListenerDetached; @@ -1193,6 +1195,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; + mLastOrientation = getResources().getConfiguration().orientation; final Resources.Theme theme = context.getTheme(); @@ -4591,6 +4594,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mFontWeightAdjustment = newConfig.fontWeightAdjustment; setTypeface(getTypeface()); } + + InputMethodManager imm = getInputMethodManager(); + // if orientation changed and this TextView is currently served. + if (mLastOrientation != newConfig.orientation + && imm != null && imm.hasActiveInputConnection(this)) { + // EditorInfo.internalImeOptions are out of date. + imm.restartInput(this); + } + mLastOrientation = newConfig.orientation; } /** diff --git a/core/java/android/widget/flags/differential_motion_fling_flags.aconfig b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig new file mode 100644 index 000000000000..79cfe566ac05 --- /dev/null +++ b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig @@ -0,0 +1,8 @@ +package: "android.widget.flags" + +flag { + namespace: "toolkit" + name: "enable_platform_widget_differential_motion_fling" + description: "Enables differential motion fling in platform widgets" + bug: "293332089" +}
\ No newline at end of file diff --git a/core/java/android/widget/flags/scroll_view_flags.aconfig b/core/java/android/widget/flags/scroll_view_flags.aconfig deleted file mode 100644 index f93ade28750b..000000000000 --- a/core/java/android/widget/flags/scroll_view_flags.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "android.widget.flags" - -flag { - namespace: "widget" - name: "platform_widget_haptic_scroll_feedback" - description: "Enables haptic scroll feedback in platform widgets" - bug: "287914819" -}
\ No newline at end of file diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl index 2efb68a33889..ec8b66d8b2f2 100644 --- a/core/java/android/window/IRemoteTransition.aidl +++ b/core/java/android/window/IRemoteTransition.aidl @@ -59,4 +59,12 @@ oneway interface IRemoteTransition { void mergeAnimation(in IBinder transition, in TransitionInfo info, in SurfaceControl.Transaction t, in IBinder mergeTarget, in IRemoteTransitionFinishedCallback finishCallback); + + /** + * Called when a different handler has consumed the transition + * + * @param transition An identifier for the transition that was consumed. + * @param aborted Whether the transition is aborted or not. + */ + void onTransitionConsumed(in IBinder transition, in boolean aborted); } diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java index b2c977bb1e57..2736b68845a2 100644 --- a/core/java/android/window/SystemPerformanceHinter.java +++ b/core/java/android/window/SystemPerformanceHinter.java @@ -17,6 +17,8 @@ package android.window; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; @@ -29,8 +31,6 @@ import android.os.Trace; import android.util.Log; import android.view.SurfaceControl; -import com.android.internal.annotations.VisibleForTesting; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.Random; @@ -148,7 +148,6 @@ public class SystemPerformanceHinter { * Constructor for the hinter. * @hide */ - @VisibleForTesting public SystemPerformanceHinter(@NonNull Context context, @Nullable DisplayRootProvider displayRootProvider, @Nullable Supplier<SurfaceControl.Transaction> transactionSupplier) { @@ -208,11 +207,18 @@ public class SystemPerformanceHinter { boolean transactionChanged = false; // Per-display flags if (nowEnabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) { - mTransaction.setFrameRateSelectionStrategy( - mDisplayRootProvider.getRootForDisplay(session.displayId), + SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay( + session.displayId); + mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + // smoothSwitchOnly is false to request a higher framerate, even if it means switching + // the display mode will cause would jank on non-VRR devices because keeping a lower + // refresh rate would mean a poorer user experience. + mTransaction.setFrameRateCategory( + displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false); transactionChanged = true; - Trace.beginAsyncSection("PerfHint-framerate-" + session.reason, session.traceCookie); + Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-" + + session.reason, session.traceCookie); } // Global flags @@ -226,7 +232,7 @@ public class SystemPerformanceHinter { Trace.beginAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie); } if (transactionChanged) { - mTransaction.apply(); + mTransaction.applyAsyncUnsafe(); } } @@ -245,25 +251,32 @@ public class SystemPerformanceHinter { boolean transactionChanged = false; // Per-display flags if (nowDisabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) { - mTransaction.setFrameRateSelectionStrategy( - mDisplayRootProvider.getRootForDisplay(session.displayId), + SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay( + session.displayId); + mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, FRAME_RATE_SELECTION_STRATEGY_SELF); + // smoothSwitchOnly is false to request a higher framerate, even if it means switching + // the display mode will cause would jank on non-VRR devices because keeping a lower + // refresh rate would mean a poorer user experience. + mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT, + /* smoothSwitchOnly= */ false); transactionChanged = true; - Trace.endAsyncSection("PerfHint-framerate-" + session.reason, session.traceCookie); + Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason, + session.traceCookie); } // Global flags if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) { mTransaction.setEarlyWakeupEnd(); transactionChanged = true; - Trace.endAsyncSection("PerfHint-early_wakeup" + session.reason, session.traceCookie); + Trace.endAsyncSection("PerfHint-early_wakeup-" + session.reason, session.traceCookie); } if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) { mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET); Trace.endAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie); } if (transactionChanged) { - mTransaction.apply(); + mTransaction.applyAsyncUnsafe(); } } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index d2a16a3a9212..61f340a856c4 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -361,6 +362,15 @@ public final class TransitionInfo implements Parcelable { } /** + * Whether this transition contains any changes to the window hierarchy, + * including keyguard visibility. + */ + public boolean hasChangesOrSideEffects() { + return !mChanges.isEmpty() || isKeyguardGoingAway() + || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0; + } + + /** * Whether this transition includes keyguard going away. */ public boolean isKeyguardGoingAway() { diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 0ee07bbb3b5c..3d4bc2f1b51c 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -16,9 +16,6 @@ package android.window; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -37,8 +34,8 @@ import android.view.IWindowSession; import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; -import java.lang.annotation.Retention; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; @@ -60,18 +57,6 @@ import java.util.TreeMap; * @hide */ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { - @Retention(SOURCE) - @IntDef({ - BACK_CALLBACK_ENABLED, - BACK_CALLBACK_DISABLED, - BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS - }) - public @interface OnBackInvokedCallbackType {} - - public static final int BACK_CALLBACK_ENABLED = 0; - public static final int BACK_CALLBACK_DISABLED = 1; - public static final int BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS = 2; - private IWindowSession mWindowSession; private IWindow mWindow; private static final String TAG = "WindowOnBackDispatcher"; @@ -283,6 +268,13 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } /** + * Returns false if the legacy back behavior should be used. + */ + public boolean isOnBackInvokedCallbackEnabled() { + return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext()); + } + + /** * Dump information about this WindowOnBackInvokedDispatcher * @param prefix the prefix that will be prepended to each line of the produced output * @param writer the writer that will receive the resulting text @@ -395,20 +387,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } - /** Returns false if the legacy back behavior should be used. */ - public boolean isOnBackInvokedCallbackEnabled() { - return isOnBackInvokedCallbackEnabled(mChecker.getContext()); - } - - /** - * Returns true if system gesture exclusion is needed for global gesture compatibility with - * windowSwipeToDismiss styleable. - */ - public boolean isSystemGestureExclusionNeeded() { - return Checker.getBackCallbackType(mChecker.getContext()) - == BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS; - } - /** * Returns false if the legacy back behavior should be used. * <p> @@ -416,7 +394,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { * {@link OnBackInvokedCallback}. */ public static boolean isOnBackInvokedCallbackEnabled(@NonNull Context context) { - return Checker.getBackCallbackType(context) == BACK_CALLBACK_ENABLED; + return Checker.isOnBackInvokedCallbackEnabled(context); } @Override @@ -468,29 +446,28 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return mContext.get(); } - @OnBackInvokedCallbackType - private static int getBackCallbackType(@Nullable Context context) { + private static boolean isOnBackInvokedCallbackEnabled(@Nullable Context context) { // new back is enabled if the feature flag is enabled AND the app does not explicitly // request legacy back. boolean featureFlagEnabled = ENABLE_PREDICTIVE_BACK; if (!featureFlagEnabled) { - return BACK_CALLBACK_DISABLED; + return false; } if (ALWAYS_ENFORCE_PREDICTIVE_BACK) { - Log.i(TAG, "getBackCallbackType: always enable"); - return BACK_CALLBACK_ENABLED; + return true; } // If the context is null, return false to use legacy back. if (context == null) { Log.w(TAG, "OnBackInvokedCallback is not enabled because context is null."); - return BACK_CALLBACK_DISABLED; + return false; } boolean requestsPredictiveBack = false; // Check if the context is from an activity. + Context originalContext = context; while ((context instanceof ContextWrapper) && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } @@ -539,8 +516,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { // 3. windowSwipeToDismiss=false should be respected for apps not opted in, // which disables PB & onBackPressed caused by BackAnimController's // setTrigger(true) + // Use the original context to resolve the styled attribute so that they stay + // true to the window. TypedArray windowAttr = - context.obtainStyledAttributes( + originalContext.obtainStyledAttributes( new int[] {android.R.attr.windowSwipeToDismiss}); boolean windowSwipeToDismiss = true; if (windowAttr.getIndexCount() > 0) { @@ -552,15 +531,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss); } - if (!windowSwipeToDismiss) { - return BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS; - } else { - return BACK_CALLBACK_ENABLED; - } + requestsPredictiveBack = windowSwipeToDismiss; } } - return requestsPredictiveBack ? BACK_CALLBACK_ENABLED : BACK_CALLBACK_DISABLED; + return requestsPredictiveBack; } } } diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 1b98806a0f01..ccbf4a9b3d21 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -9,3 +9,11 @@ flag { is_fixed_read_only: true bug: "292032926" } + +flag { + namespace: "window_surfaces" + name: "explicit_refresh_rate_hints" + description: "Performance related hints during transitions" + is_fixed_read_only: true + bug: "300019131" +} diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ec5d4ff16ee5..24dc6dbfede8 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -22,3 +22,10 @@ flag { description: "Whether the TaskFragment system organizer feature is enabled" bug: "284050041" } + +flag { + namespace: "windowing_sdk" + name: "window_state_resize_item_flag" + description: "Whether to dispatch window resize through ClientTransaction is enabled" + bug: "301870955" +} diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 5dd558a5f850..987c14c6ab51 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -28,15 +28,19 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.isUserS import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.os.Bundle; import android.view.View; import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.Flags; import android.widget.AdapterView; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; @@ -207,6 +211,11 @@ public class AccessibilityShortcutChooserActivity extends Activity { isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); } + @VisibleForTesting + public AlertDialog getMenuDialog() { + return mMenuDialog; + } + private AlertDialog createMenuDialog() { final String dialogTitle = getString(R.string.accessibility_select_shortcut_menu_title); @@ -216,12 +225,25 @@ public class AccessibilityShortcutChooserActivity extends Activity { .setAdapter(mTargetAdapter, /* listener= */ null) .setOnDismissListener(dialog -> finish()); - if (isUserSetupCompleted(this)) { + boolean allowEditing = isUserSetupCompleted(this); + boolean showWhenLocked = false; + if (Flags.allowShortcutChooserOnLockscreen()) { + final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); + if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { + allowEditing = false; + showWhenLocked = true; + } + } + if (allowEditing) { final String positiveButtonText = getString(R.string.edit_accessibility_shortcut_menu_button); builder.setPositiveButton(positiveButtonText, /* listener= */ null); } - return builder.create(); + final AlertDialog dialog = builder.create(); + if (showWhenLocked) { + dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + return dialog; } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 2909b6a488b8..e494346bae5c 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -414,11 +414,6 @@ public final class SystemUiDeviceConfigFlags { "dark_launch_remote_prediction_service_enabled"; /** - * (boolean) Whether to enable pinch resizing for PIP. - */ - public static final String PIP_PINCH_RESIZE = "pip_pinch_resize"; - - /** * (boolean) Whether to enable stashing for PIP. */ public static final String PIP_STASHING = "pip_stashing"; diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index cb2d93474971..b1d22e069d9d 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -86,6 +86,28 @@ public class SystemUiSystemPropertiesFlags { public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag( "persist.debug.sysui.notification.enable_attention_helper_refactor"); + // TODO b/291899544: for released flags, use resource config values + /** Value used by polite notif. feature */ + public static final Flag NOTIF_COOLDOWN_T1 = devFlag( + "persist.debug.sysui.notification.notif_cooldown_t1", 5000); + /** Value used by polite notif. feature */ + public static final Flag NOTIF_COOLDOWN_T2 = devFlag( + "persist.debug.sysui.notification.notif_cooldown_t2", 3000); + /** Value used by polite notif. feature */ + public static final Flag NOTIF_VOLUME1 = devFlag( + "persist.debug.sysui.notification.notif_volume1", 30); + public static final Flag NOTIF_VOLUME2 = devFlag( + "persist.debug.sysui.notification.notif_volume2", 0); + /** Value used by polite notif. feature. -1 to ignore the counter */ + public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag( + "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10); + /** + * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1, + * rule2 + */ + public static final Flag NOTIF_COOLDOWN_RULE = devFlag( + "persist.debug.sysui.notification.notif_cooldown_rule", "rule1"); + /** b/301242692: Visit extra URIs used in notifications to prevent security issues. */ public static final Flag VISIT_RISKY_URIS = devFlag( "persist.sysui.notification.visit_risky_uris"); @@ -97,6 +119,10 @@ public class SystemUiSystemPropertiesFlags { public interface FlagResolver { /** Is the flag enabled? */ boolean isEnabled(Flag flag); + /** Get the flag value (integer) */ + int getIntValue(Flag flag); + /** Get the flag value (string) */ + String getStringValue(Flag flag); } /** The primary, immutable resolver returned by getResolver() */ @@ -134,6 +160,22 @@ public class SystemUiSystemPropertiesFlags { } /** + * Creates a flag that with a default integer value in debuggable builds. + */ + @VisibleForTesting + public static Flag devFlag(String name, int defaultValue) { + return new Flag(name, defaultValue, null); + } + + /** + * Creates a flag that with a default string value in debuggable builds. + */ + @VisibleForTesting + public static Flag devFlag(String name, String defaultValue) { + return new Flag(name, defaultValue, null); + } + + /** * Creates a flag that is disabled by default in debuggable builds. * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0. * If this flag's SystemProperty is not set, the flag can be enabled by setting the @@ -161,6 +203,8 @@ public class SystemUiSystemPropertiesFlags { public static final class Flag { public final String mSysPropKey; public final boolean mDefaultValue; + public final int mDefaultIntValue; + public final String mDefaultStringValue; @Nullable public final Flag mDebugDefault; @@ -170,6 +214,24 @@ public class SystemUiSystemPropertiesFlags { mSysPropKey = sysPropKey; mDefaultValue = defaultValue; mDebugDefault = debugDefault; + mDefaultIntValue = 0; + mDefaultStringValue = null; + } + + public Flag(@NonNull String sysPropKey, int defaultValue, @Nullable Flag debugDefault) { + mSysPropKey = sysPropKey; + mDefaultIntValue = defaultValue; + mDebugDefault = debugDefault; + mDefaultValue = false; + mDefaultStringValue = null; + } + + public Flag(@NonNull String sysPropKey, String defaultValue, @Nullable Flag debugDefault) { + mSysPropKey = sysPropKey; + mDefaultStringValue = defaultValue; + mDebugDefault = debugDefault; + mDefaultValue = false; + mDefaultIntValue = 0; } } @@ -181,6 +243,16 @@ public class SystemUiSystemPropertiesFlags { public boolean isEnabled(Flag flag) { return flag.mDefaultValue; } + + @Override + public int getIntValue(Flag flag) { + return flag.mDefaultIntValue; + } + + @Override + public String getStringValue(Flag flag) { + return flag.mDefaultStringValue; + } } /** Implementation of the interface used in debuggable builds. */ @@ -199,5 +271,23 @@ public class SystemUiSystemPropertiesFlags { public boolean getBoolean(String key, boolean defaultValue) { return SystemProperties.getBoolean(key, defaultValue); } + + /** Look up the value; overridable for tests to avoid needing to set SystemProperties */ + @VisibleForTesting + public int getIntValue(Flag flag) { + if (flag.mDebugDefault == null) { + return SystemProperties.getInt(flag.mSysPropKey, flag.mDefaultIntValue); + } + return SystemProperties.getInt(flag.mSysPropKey, getIntValue(flag.mDebugDefault)); + } + + /** Look up the value; overridable for tests to avoid needing to set SystemProperties */ + @VisibleForTesting + public String getStringValue(Flag flag) { + if (flag.mDebugDefault == null) { + return SystemProperties.get(flag.mSysPropKey, flag.mDefaultStringValue); + } + return SystemProperties.get(flag.mSysPropKey, getStringValue(flag.mDebugDefault)); + } } } diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 58376a77c705..0801dd8c0bd8 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -62,16 +62,14 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local @@ -89,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_MIME_TYPES); + private static final int MAX_RESULTS_NUMBER = 23; + private static String joinNewline(String... args) { return TextUtils.join("\n", args); } @@ -375,62 +375,53 @@ public abstract class FileSystemProvider extends DocumentsProvider { } /** - * This method is similar to - * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns - * all children documents including hidden directories/files. - * - * <p> - * In a scoped storage world, access to "Android/data" style directories are hidden for privacy - * reasons. This method may show privacy sensitive data, so its usage should only be in - * restricted modes. - * - * @param parentDocumentId the directory to return children for. - * @param projection list of {@link Document} columns to put into the - * cursor. If {@code null} all supported columns should be - * included. - * @param sortOrder how to order the rows, formatted as an SQL - * {@code ORDER BY} clause (excluding the ORDER BY itself). - * Passing {@code null} will use the default sort order, which - * may be unordered. This ordering is a hint that can be used to - * prioritize how data is fetched from the network, but UI may - * always enforce a specific ordering - * @throws FileNotFoundException when parent document doesn't exist or query fails + * WARNING: this method should really be {@code final}, but for the backward compatibility it's + * not; new classes that extend {@link FileSystemProvider} should override + * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method. */ - protected Cursor queryChildDocumentsShowAll( - String parentDocumentId, String[] projection, String sortOrder) + @Override + public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) throws FileNotFoundException { - return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false); } + /** + * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it + * could return <b>all</b> content of the directory, <b>including restricted (hidden) + * directories and files</b>. + * <p> + * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and + * {@code Android/obb/} on the external storage) are hidden for privacy reasons. + * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care. + */ @Override - public Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException { - // Access to some directories is hidden for privacy reasons. - return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); + public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, + String sortOrder) throws FileNotFoundException { + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); } - private Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder, - @NonNull Predicate<File> filter) throws FileNotFoundException { - final File parent = getFileForDocId(parentDocumentId); + protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, + boolean includeHidden) throws FileNotFoundException { + final File parent = getFileForDocId(documentId); final MatrixCursor result = new DirectoryCursor( - resolveProjection(projection), parentDocumentId, parent); + resolveProjection(projection), documentId, parent); + + if (!parent.isDirectory()) { + Log.w(TAG, '"' + documentId + "\" is not a directory"); + return result; + } - if (!filter.test(parent)) { - Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId); + if (!includeHidden && shouldHideDocument(documentId)) { + Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); return result; } - if (parent.isDirectory()) { - for (File file : FileUtils.listFilesOrEmpty(parent)) { - if (filter.test(file)) { - includeFile(result, null, file); - } - } - } else { - Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); + for (File file : FileUtils.listFilesOrEmpty(parent)) { + if (!includeHidden && shouldHideDocument(file)) continue; + + includeFile(result, null, file); } + return result; } @@ -452,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * @see ContentResolver#EXTRA_HONORED_ARGS */ - protected final Cursor querySearchDocuments( - File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) - throws FileNotFoundException { + protected final Cursor querySearchDocuments(File folder, String[] projection, + Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); - final List<File> pending = new ArrayList<>(); - pending.add(folder); - while (!pending.isEmpty() && result.getCount() < 24) { - final File file = pending.remove(0); - if (shouldHide(file)) continue; + + // We'll be a running a BFS here. + final Queue<File> pending = new ArrayDeque<>(); + pending.offer(folder); + + while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) { + final File file = pending.poll(); + + // Skip hidden documents (both files and directories) + if (shouldHideDocument(file)) continue; if (file.isDirectory()) { for (File child : FileUtils.listFilesOrEmpty(file)) { - pending.add(child); + pending.offer(child); } } - if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file, - queryArgs)) { + + if (exclusion.contains(file.getAbsolutePath())) continue; + + if (matchSearchQueryArguments(file, queryArgs)) { includeFile(result, null, file); } } @@ -612,26 +609,23 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); if (flagIndex != -1) { + final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); int flags = 0; if (file.canWrite()) { - if (mimeType.equals(Document.MIME_TYPE_DIR)) { + flags |= Document.FLAG_SUPPORTS_DELETE; + flags |= Document.FLAG_SUPPORTS_RENAME; + flags |= Document.FLAG_SUPPORTS_MOVE; + if (isDir) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; - - if (shouldBlockFromTree(docId)) { - flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; - } - } else { flags |= Document.FLAG_SUPPORTS_WRITE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; } } + if (isDir && shouldBlockDirectoryFromTree(docId)) { + flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; + } + if (mimeType.startsWith("image/")) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } @@ -664,22 +658,36 @@ public abstract class FileSystemProvider extends DocumentsProvider { return row; } - private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( - "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); - /** - * In a scoped storage world, access to "Android/data" style directories are - * hidden for privacy reasons. + * Some providers may want to restrict access to certain directories and files, + * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for + * privacy reasons. + * Such providers should override this method. */ - protected boolean shouldHide(@NonNull File file) { - return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); + protected boolean shouldHideDocument(@NonNull String documentId) + throws FileNotFoundException { + return false; } - private boolean shouldShow(@NonNull File file) { - return !shouldHide(file); + /** + * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of + * a {@link String} {@code documentId}. + * + * @see #shouldHideDocument(String) + */ + protected final boolean shouldHideDocument(@NonNull File document) + throws FileNotFoundException { + return shouldHideDocument(getDocIdForFile(document)); } - protected boolean shouldBlockFromTree(@NonNull String docId) { + /** + * @return if the directory that should be blocked from being selected when the user launches + * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE + */ + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { return false; } diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java new file mode 100644 index 000000000000..e55c64199f45 --- /dev/null +++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java @@ -0,0 +1,56 @@ +/* + * 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.internal.display; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.util.Log; +import android.view.Display; + +/** + * Constants and utility methods for refresh rate settings. + */ +public class RefreshRateSettingsUtils { + + private static final String TAG = "RefreshRateSettingsUtils"; + + public static final float DEFAULT_REFRESH_RATE = 60f; + + /** + * Find the highest refresh rate among all the modes of the default display. + * + * @param context The context + * @return The highest refresh rate + */ + public static float findHighestRefreshRateForDefaultDisplay(Context context) { + final DisplayManager dm = context.getSystemService(DisplayManager.class); + final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); + + if (display == null) { + Log.w(TAG, "No valid default display device"); + return DEFAULT_REFRESH_RATE; + } + + float maxRefreshRate = DEFAULT_REFRESH_RATE; + for (Display.Mode mode : display.getSupportedModes()) { + if (mode.getRefreshRate() > maxRefreshRate) { + maxRefreshRate = mode.getRefreshRate(); + } + } + return maxRefreshRate; + } +} diff --git a/core/java/com/android/internal/foldables/Android.bp b/core/java/com/android/internal/foldables/Android.bp new file mode 100644 index 000000000000..f1d06da98186 --- /dev/null +++ b/core/java/com/android/internal/foldables/Android.bp @@ -0,0 +1,7 @@ +aconfig_declarations { + name: "fold_lock_setting_flags", + package: "com.android.internal.foldables.flags", + srcs: [ + "fold_lock_setting_flags.aconfig", + ], +} diff --git a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java index 4e3888a9e92d..a115ecff4ac3 100644 --- a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java +++ b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java @@ -17,16 +17,23 @@ package com.android.internal.foldables; import android.content.res.Resources; +import android.os.Build; import android.sysprop.FoldLockBehaviorProperties; +import android.util.Slog; import com.android.internal.R; +import com.android.internal.foldables.flags.Flags; + +import java.util.function.Supplier; /** * Wrapper class to access {@link FoldLockBehaviorProperties} and also assists with testing */ public class FoldLockSettingAvailabilityProvider { - boolean mFoldLockBehaviorResourceValue; + private static final String TAG = "FoldLockSettingAvailabilityProvider"; + private final boolean mFoldLockBehaviorResourceValue; + private final Supplier<Boolean> mFoldLockSettingEnabled = Flags::foldLockSettingEnabled; public FoldLockSettingAvailabilityProvider(Resources resources) { mFoldLockBehaviorResourceValue = resources.getBoolean( @@ -35,6 +42,22 @@ public class FoldLockSettingAvailabilityProvider { public boolean isFoldLockBehaviorAvailable() { return mFoldLockBehaviorResourceValue - && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false); + && flagOrSystemProperty(); + } + + private boolean flagOrSystemProperty() { + if ((Build.IS_ENG || Build.IS_USERDEBUG) + && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false)) { + return true; + } + try { + return mFoldLockSettingEnabled.get(); + } catch (Throwable ex) { + Slog.i(TAG, + "Flags not ready yet. Return false for " + + Flags.FLAG_FOLD_LOCK_SETTING_ENABLED, + ex); + return false; + } } } diff --git a/core/java/com/android/internal/foldables/OWNERS b/core/java/com/android/internal/foldables/OWNERS new file mode 100644 index 000000000000..6ce1ee4d3de2 --- /dev/null +++ b/core/java/com/android/internal/foldables/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/display/OWNERS diff --git a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig new file mode 100644 index 000000000000..44f436eaaa19 --- /dev/null +++ b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.internal.foldables.flags" + +flag { + name: "fold_lock_setting_enabled" + namespace: "display_manager" + description: "Feature flag for Fold Lock Setting" + bug: "274447767" + is_fixed_read_only: true +} diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 1be916f44f5b..85662634c22d 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -290,11 +290,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind }; private Consumer<Boolean> mCrossWindowBlurEnabledListener; + private final WearGestureInterceptionDetector mWearGestureInterceptionDetector; + DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { super(context); mFeatureId = featureId; - mShowInterpolator = AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); mHideInterpolator = AnimationUtils.loadInterpolator(context, @@ -314,6 +315,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateLogTag(params); mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); + + mWearGestureInterceptionDetector = + WearGestureInterceptionDetector.isEnabled(context) + ? new WearGestureInterceptionDetector(context, this) + : null; } void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { @@ -544,6 +550,18 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null && mWearGestureInterceptionDetector != null) { + boolean wasIntercepting = mWearGestureInterceptionDetector.isIntercepting(); + boolean intercepting = mWearGestureInterceptionDetector.onInterceptTouchEvent(event); + if (wasIntercepting != intercepting) { + viewRootImpl.updateDecorViewGestureInterception(intercepting); + } + if (intercepting) { + return true; + } + } + if (!SWEEP_OPEN_MENU) { return false; } diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java new file mode 100644 index 000000000000..6fd50180e78b --- /dev/null +++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java @@ -0,0 +1,211 @@ +/* + * 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.internal.policy; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.TypedArray; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; + +/** + * Wear-specific gesture interception detector to be installed at DecorView, for compatibility of + * apps depending on legacy SwipeDismissLayout behavior. + * + * <p>Results of the detector will be used by {@code DecorView} to intercept motion events. The + * interception state will also be sent to {@code android.view.ViewRootImpl} and {@code + * com.android.server.wm.DisplayContent} through {@code android.view.IWindowSession}. + * + * <p>SystemUI can register {@code android.view.IDecorViewGestureListener} to listen for the result + * of the detector. The result will be valid for between a pair of touch down/up events. + */ +public class WearGestureInterceptionDetector { + private static final boolean DEBUG = false; + private static final String TAG = "WearGestureInterceptionDetector"; + + private final DecorView mInstalledDecorView; + private final float mTouchSlop; + private final float mSwipingStartThreshold; + private boolean mSwiping; + + private float mDownX; + private float mDownY; + private int mActivePointerId; + private boolean mDiscardIntercept; + + WearGestureInterceptionDetector(Context context, DecorView installedDecorView) { + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mInstalledDecorView = installedDecorView; + mSwipingStartThreshold = mTouchSlop * 2; + } + + /** Check if this gesture interception detector should be enabled. */ + public static boolean isEnabled(Context context) { + PackageManager pm = context.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) { + return false; + } + + // Compatibility check for flag that disables legacy SwipeDismissLayout. + TypedArray windowAttr = + context.obtainStyledAttributes(new int[] {android.R.attr.windowSwipeToDismiss}); + boolean windowSwipeToDismiss = true; + if (windowAttr.getIndexCount() > 0) { + windowSwipeToDismiss = windowAttr.getBoolean(0, true); + } + windowAttr.recycle(); + return windowSwipeToDismiss; + } + + private boolean isPointerIndexValid(MotionEvent ev) { + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + if (DEBUG) { + Log.e(TAG, "Invalid pointer index: ignoring."); + } + mDiscardIntercept = true; + return false; + } + return true; + } + + private void updateSwiping(MotionEvent ev) { + if (mSwiping) { + return; + } + float deltaX = ev.getRawX() - mDownX; + float deltaY = ev.getRawY() - mDownY; + // Check if we have left the touch slop area. + if ((deltaX * deltaX) + (deltaY * deltaY) > (mTouchSlop * mTouchSlop)) { + mSwiping = deltaX > mSwipingStartThreshold && Math.abs(deltaY) < Math.abs(deltaX); + } + } + + private void updateDiscardIntercept(MotionEvent ev) { + if (!mSwiping) { + // Don't look at canScroll until we have passed the touch slop + return; + } + if (mDiscardIntercept) { + return; + } + final boolean checkLeft = mDownX < ev.getRawX(); + final float x = ev.getX(mActivePointerId); + final float y = ev.getY(mActivePointerId); + if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) { + mDiscardIntercept = true; + } + } + + /** Resets internal members when canceling. */ + private void resetMembers() { + mDownX = 0; + mDownY = 0; + mSwiping = false; + mDiscardIntercept = false; + } + + /** Should we intercept the MotionEvent for system gesture? */ + public boolean isIntercepting() { + return !mDiscardIntercept && mSwiping; + } + + /** Tests if the MotionEvent should be intercepted */ + public boolean onInterceptTouchEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + resetMembers(); + mDownX = ev.getRawX(); + mDownY = ev.getRawY(); + mActivePointerId = ev.getPointerId(0); + break; + case MotionEvent.ACTION_POINTER_DOWN: + mActivePointerId = ev.getPointerId(ev.getActionIndex()); + break; + case MotionEvent.ACTION_POINTER_UP: + int associatedPointerIndex = ev.getActionIndex(); + if (ev.getPointerId(associatedPointerIndex) == mActivePointerId) { + // This was our active pointer going up. + // Choose the first available pointer index. + int newActionIndex = associatedPointerIndex == 0 ? 1 : 0; + mActivePointerId = ev.getPointerId(newActionIndex); + } + break; + case MotionEvent.ACTION_MOVE: + if (mDiscardIntercept) { + break; + } + if (!isPointerIndexValid(ev)) { + break; + } + updateSwiping(ev); + updateDiscardIntercept(ev); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + resetMembers(); + break; + } + return isIntercepting(); + } + + /** + * Tests scroll-ability within child views of v in the direction of dx. + * + * @param v View to test for horizontal scroll-ability + * @param checkSelf Whether the view v passed should itself be checked for scroll-ability + * (true), or just its children (false). + * @param checkLeft Which direction to check? Left = true, right = false. + * @param x X coordinate of the active touch point + * @param y Y coordinate of the active touch point + * @return true if child views of v can be scrolled by delta of dx. + */ + private boolean canScroll(View v, boolean checkSelf, boolean checkLeft, float x, float y) { + if (v instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) v; + final int scrollX = v.getScrollX(); + final int scrollY = v.getScrollY(); + final int count = group.getChildCount(); + for (int i = count - 1; i >= 0; i--) { + final View child = group.getChildAt(i); + + if (x + scrollX < child.getLeft() + || x + scrollX >= child.getRight() + || y + scrollY < child.getTop() + || y + scrollY >= child.getBottom()) { + // This child is out of bound, don't bother checking. + continue; + } + + // Recursively check until finding the first scrollable or none is scrollable. + if (canScroll( + /* view= */ child, + /* checkSelf= */ true, + /* checkLeft= */ checkLeft, + /* x= */ x + scrollX - child.getLeft(), + /* y= */ y + scrollY - child.getTop())) { + return true; + } + } + } + + return checkSelf && v.canScrollHorizontally(checkLeft ? -1 : 1); + } +} diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b5d70d379e0c..50253cf9e457 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1315,7 +1315,16 @@ void AndroidRuntime::exit(int code) ALOGI("VM exiting with result code %d.", code); onExit(code); } + +#ifdef __ANDROID_CLANG_COVERAGE__ + // When compiled with coverage, a function is registered with atexit to call + // `__llvm_profile_write_file` when the process exit. + // For Clang code coverage to work, call exit instead of _exit to run hooks + // registered with atexit. + ::exit(code); +#else ::_exit(code); +#endif } void AndroidRuntime::onVmCreated(JNIEnv* env) diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 9384f41e26f5..178c0d0d95be 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -127,6 +127,7 @@ static struct { jfieldID xDpi; jfieldID yDpi; jfieldID refreshRate; + jfieldID vsyncRate; jfieldID appVsyncOffsetNanos; jfieldID presentationDeadlineNanos; jfieldID group; @@ -469,9 +470,10 @@ static void nativeSetDefaultBufferSize(JNIEnv* env, jclass clazz, jlong nativeOb } } -static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) { +static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync, + jboolean oneWay) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->apply(sync); + transaction->apply(sync, oneWay); } static void nativeMergeTransaction(JNIEnv* env, jclass clazz, @@ -961,10 +963,11 @@ static void nativeSetDefaultFrameRateCompatibility(JNIEnv* env, jclass clazz, jl } static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj, - jlong nativeObject, jint category) { + jlong nativeObject, jint category, + jboolean smoothSwitchOnly) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); - transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category)); + transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly); } static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj, @@ -1230,6 +1233,7 @@ static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi); env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate); + env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate); env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset); env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos, config.presentationDeadline); @@ -2119,7 +2123,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetDefaultBufferSize}, {"nativeCreateTransaction", "()J", (void*)nativeCreateTransaction }, - {"nativeApplyTransaction", "(JZ)V", + {"nativeApplyTransaction", "(JZZ)V", (void*)nativeApplyTransaction }, {"nativeGetNativeTransactionFinalizer", "()J", (void*)nativeGetNativeTransactionFinalizer }, @@ -2178,7 +2182,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetFrameRate }, {"nativeSetDefaultFrameRateCompatibility", "(JJI)V", (void*)nativeSetDefaultFrameRateCompatibility}, - {"nativeSetFrameRateCategory", "(JJI)V", + {"nativeSetFrameRateCategory", "(JJIZ)V", (void*)nativeSetFrameRateCategory}, {"nativeSetFrameRateSelectionStrategy", "(JJI)V", (void*)nativeSetFrameRateSelectionStrategy}, @@ -2393,6 +2397,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F"); gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F"); gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F"); + gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F"); gDisplayModeClassInfo.appVsyncOffsetNanos = GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J"); gDisplayModeClassInfo.presentationDeadlineNanos = diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e34e42317c0c..ca768ad434f1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -48,6 +48,7 @@ <protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" /> <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" /> + <protected-broadcast android:name="android.intent.action.PACKAGE_UNSTOPPED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" /> <protected-broadcast android:name="android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION" /> @@ -2107,12 +2108,12 @@ android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service interfaces. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.MANAGE_REMOTE_AUTH" android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service authentication methods. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.USE_REMOTE_AUTH" android:protectionLevel="signature" /> @@ -7232,6 +7233,15 @@ android:description="@string/permdesc_fullScreenIntent" android:protectionLevel="normal|appop" /> + <!-- @SystemApi Required for the privileged assistant apps targeting + {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} + that receive voice trigger from the trusted hotword detection service. + <p>Protection level: signature|privileged|appop + @FlaggedApi("android.permission.flags.voice_activation_permission_apis") + @hide --> + <permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" + android:protectionLevel="signature|privileged|appop" /> + <!-- @SystemApi Allows requesting the framework broadcast the {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent. @hide --> diff --git a/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml new file mode 100644 index 000000000000..dddad814b451 --- /dev/null +++ b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml @@ -0,0 +1,27 @@ +<!-- +Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="20" + android:viewportHeight="20"> + <path + android:pathData="M15.333,5.333V4.667C15.333,4.3 15.033,4 14.667,4L13.333,4C12.967,4 12.667,4.3 12.667,4.667V5.333H12V8C12,8.367 12.3,8.667 12.667,8.667H13.333L13.333,13.333C13.333,14.067 12.733,14.667 12,14.667C11.267,14.667 10.667,14.067 10.667,13.333L10.667,11.333V6.667C10.667,5.193 9.473,4 8,4C6.527,4 5.333,5.193 5.333,6.667L5.333,11.333H4.667C4.3,11.333 4,11.633 4,12L4,14.667H4.667V15.333C4.667,15.7 4.967,16 5.333,16H6.667C7.033,16 7.333,15.7 7.333,15.333V14.667H8L8,12C8,11.633 7.7,11.333 7.333,11.333H6.667L6.667,6.667C6.667,5.933 7.267,5.333 8,5.333C8.733,5.333 9.333,5.933 9.333,6.667V11.333L9.333,13.333C9.333,14.807 10.527,16 12,16C13.473,16 14.667,14.807 14.667,13.333L14.667,8.667H15.333C15.7,8.667 16,8.367 16,8V5.333H15.333Z" + android:fillColor="#FFFFFFFF" + android:fillType="evenOdd"/> +</vector> + + diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index a49e5477aabd..6f879e42b534 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -674,7 +674,7 @@ <string name="device_unlock_notification_name" msgid="2632928999862915709">"ডিভাইস আনলক করুন"</string> <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"অন্য কোনওভাবে আনলক করার চেষ্টা করুন"</string> <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"আপনার \'ফিঙ্গারপ্রিন্ট\' শনাক্ত করা না গেলে \'ফেস আনলক\' ব্যবহার করুন, যেমন যখন আপনার আঙুল ভিজে থাকে"</string> - <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"আপনার মুখ শনাক্ত করা না গেলে \'ফিঙ্গারপ্রিন্ট আনলক\' ব্যবহার করুন, যেমন যখন পর্যাপ্ত আলো নেই"</string> + <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"পর্যাপ্ত আলো না থাকার পরিস্থিতিতে, আপনার মুখ শনাক্ত করা না গেলে \'ফিঙ্গারপ্রিন্ট আনলক\' ব্যবহার করুন"</string> <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"ফেস আনলক"</string> <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"\'ফেস আনলক\' ফিচার ব্যবহার করার ক্ষেত্রে হওয়া সমস্যা"</string> <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"আপনার ফেস মডেল মুছে দেওয়ার জন্য ট্যাপ করুন এবং তারপরে আবার ফেস যোগ করুন"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index a897170d0e63..2150324d2fb1 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -476,7 +476,7 @@ <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"atzitu kokapen-hornitzaileen komando gehigarriak"</string> <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak erabiltzeko baimena ematen die aplikazioei. Horrela, agian aplikazioek GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezakete."</string> <string name="permlab_accessFineLocation" msgid="6426318438195622966">"lortu kokapen zehatza aurreko planoan bakarrik"</string> - <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Abian denean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Bateria-erabilera areagotzen du horrek."</string> + <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Abian denean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Agian bateria gehiago erabiliko du."</string> <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"atzitu gutxi gorabeherako kokapena aurreko planoan bakarrik"</string> <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Abian denean, aplikazioak gutxi gorabeherako kokapena lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan."</string> <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"atzitu kokapena atzeko planoan"</string> @@ -2137,7 +2137,7 @@ <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Aplikazioak ez du grabatzeko baimenik, baina baliteke audioa grabatzea USB bidezko gailu horren bidez."</string> <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Pantaila nagusia"</string> <string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Atzera"</string> - <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Erabilitako azken aplikazioak"</string> + <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Azkenaldian erabilitako aplikazioak"</string> <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Jakinarazpenak"</string> <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Ezarpen bizkorrak"</string> <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Piztu edo itzaltzeko leihoa"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 286751628ea5..d7d29a4c5ef7 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -299,8 +299,8 @@ <string name="android_system_label" msgid="5974767339591067210">"Système Android"</string> <string name="user_owner_label" msgid="8628726904184471211">"Passer au profil personnel"</string> <string name="managed_profile_label" msgid="7316778766973512382">"Passer au profil pro"</string> - <string name="user_owner_app_label" msgid="1553595155465750298">"Passer au <xliff:g id="APP_NAME">%1$s</xliff:g> personnel"</string> - <string name="managed_profile_app_label" msgid="367401088383965725">"Passer au <xliff:g id="APP_NAME">%1$s</xliff:g> professionnel"</string> + <string name="user_owner_app_label" msgid="1553595155465750298">"Passer à l\'application <xliff:g id="APP_NAME">%1$s</xliff:g> personnelle"</string> + <string name="managed_profile_app_label" msgid="367401088383965725">"Passer à l\'application <xliff:g id="APP_NAME">%1$s</xliff:g> professionnelle"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contacts"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"accéder à vos contacts"</string> <string name="permgrouplab_location" msgid="1858277002233964394">"Position"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 5a98ef8d6885..d2b6dc8663aa 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -674,7 +674,7 @@ <string name="device_unlock_notification_name" msgid="2632928999862915709">"기기 잠금 해제"</string> <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"다른 잠금 해제 방법 사용"</string> <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"손가락에 물기가 있는 등 지문이 인식되지 않을 때는 얼굴 인식 잠금 해제를 사용하세요."</string> - <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"충분히 밝지 않은 경우 등 얼굴이 인식되지 않을 때는 지문 잠금 해제를 사용하세요."</string> + <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"주변이 어두운 경우 등 얼굴이 인식되지 않을 때는 지문 잠금 해제를 사용해 보세요."</string> <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"얼굴 인식 잠금 해제"</string> <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"얼굴 인식 잠금 해제 문제"</string> <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"탭하여 얼굴 모델을 삭제한 후 다시 얼굴을 추가하세요"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 5f3b3159aae9..485e9b614d01 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -674,7 +674,7 @@ <string name="device_unlock_notification_name" msgid="2632928999862915709">"डिव्हाइस अनलॉक"</string> <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"अनलॉक करण्याची दुसरी पद्धत वापरून पहा"</string> <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"तुमचे फिंगरप्रिंट ओळखले जात नाही, तेव्हा फेस अनलॉक वापरा, जसे की तुमची बोटे ओली असताना"</string> - <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"तुमचा चेहरा ओळखला जात नाही, तेव्हा फिंगरप्रिंट अनलॉक वापरा, जसे की पुरेसा प्रकाश नसताना"</string> + <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"पुरेसा प्रकाश नसणे इत्यादिमुळे तुमचा चेहरा ओळखला जात नाही, अशावेळी फिंगरप्रिंट अनलॉक वापरा"</string> <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"फेस अनलॉक"</string> <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"फेस अनलॉकसंबंधित समस्या"</string> <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"फेस मॉडेल हटवण्यासाठी टॅप करा, त्यानंतर तुमचा चेहरा पुन्हा जोडा"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 9583c0e51b95..628627d53e8a 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -675,7 +675,7 @@ <string name="device_unlock_notification_name" msgid="2632928999862915709">"Desbloqueio do dispositivo"</string> <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Tente desbloquear de outra maneira"</string> <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Use o Desbloqueio facial quando sua impressão digital não for reconhecida, como quando seus dedos estiverem molhados"</string> - <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, como quando não houver luz suficiente"</string> + <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, se estiver escuro, por exemplo"</string> <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Desbloqueio facial"</string> <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problema com o Desbloqueio facial"</string> <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 9583c0e51b95..628627d53e8a 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -675,7 +675,7 @@ <string name="device_unlock_notification_name" msgid="2632928999862915709">"Desbloqueio do dispositivo"</string> <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Tente desbloquear de outra maneira"</string> <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Use o Desbloqueio facial quando sua impressão digital não for reconhecida, como quando seus dedos estiverem molhados"</string> - <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, como quando não houver luz suficiente"</string> + <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, se estiver escuro, por exemplo"</string> <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Desbloqueio facial"</string> <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problema com o Desbloqueio facial"</string> <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 8573c20c8021..aa22aeafe3b0 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -673,8 +673,8 @@ <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"వేలిముద్ర చిహ్నం"</string> <string name="device_unlock_notification_name" msgid="2632928999862915709">"పరికర అన్లాక్"</string> <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"అన్లాక్ చేయడానికి మరొక మార్గాన్ని ట్రై చేయండి"</string> - <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"మీ వేలిముద్ర గుర్తించబడనప్పుడు, మీ వేళ్లు తడిగా ఉన్నప్పుడు ఫేస్ అన్లాక్ను ఉపయోగించండి"</string> - <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"మీ ఫేస్ గుర్తించబడనప్పుడు, తగినంత వెలుతురు లేనప్పుడు వేలిముద్ర అన్లాక్ను ఉపయోగించండి"</string> + <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"మీ వేళ్లు తడిగా ఉండటం లేక ఇతరత్రా కారణాల వల్ల మీ వేలిముద్రను గుర్తించకపోతే, ఫేస్ అన్లాక్ను ఉపయోగించండి"</string> + <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"తగినంత వెలుతురు లేకపోవడం లేక ఇతరత్రా కారణాల వల్ల మీ ఫేస్ గుర్తించబడనప్పుడు, వేలిముద్ర అన్లాక్ను ఉపయోగించండి"</string> <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"ఫేస్ అన్లాక్"</string> <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"ఫేస్ అన్లాక్తో సమస్య"</string> <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ఫేస్ మోడల్ను తొలగించడానికి నొక్కండి, ఆపై మీ ముఖాన్ని మళ్లీ జోడించండి"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 936c978bea47..d34e9769dcd3 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -675,8 +675,8 @@ <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"Значок відбитка пальця"</string> <string name="device_unlock_notification_name" msgid="2632928999862915709">"Розблокування пристрою"</string> <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Спробуйте інший спосіб розблокування"</string> - <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Розблоковуйте пристрій за допомогою фейс-контролю, коли не вдається розпізнати ваш відбиток пальця (наприклад, коли у вас мокрі пальці)"</string> - <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Розблоковуйте пристрій відбитком пальця, коли не вдається розпізнати ваше обличчя (наприклад, коли недостатньо світла)"</string> + <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Якщо пристрій не розпізнає ваш відбиток пальця (наприклад, коли у вас мокрі руки), використовуйте фейс-контроль"</string> + <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Якщо пристрій не розпізнає ваше обличчя (наприклад, коли освітлення погане), використовуйте відбиток пальця"</string> <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Фейс-контроль"</string> <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Сталася помилка з фейсконтролем"</string> <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Натисніть, щоб видалити свою модель обличчя, а потім знову додайте її"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index ccdd945b8732..06ec1dd42267 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1891,7 +1891,7 @@ <string name="zen_mode_duration_hours" msgid="7841806065034711849">"{count,plural, =1{1 小時}other{# 小時}}"</string> <string name="zen_mode_duration_hours_short" msgid="3666949653933099065">"{count,plural, =1{1 小時}other{# 小時}}"</string> <string name="zen_mode_until_next_day" msgid="1403042784161725038">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> - <string name="zen_mode_until" msgid="2250286190237669079">"完成時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> + <string name="zen_mode_until" msgid="2250286190237669079">"結束時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (下一次響鬧)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"直至你關閉為止"</string> <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"直至你關閉「請勿騷擾」功能"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e54347fc2744..04fd70a96201 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -10118,6 +10118,9 @@ screen that can be used to provide more information about a provider. For longer strings it will be truncated. --> <attr name="settingsSubtitle" format="string" /> + <!-- Fully qualified class name of an activity that allows the user to modify + the settings for this service. --> + <attr name="settingsActivity" /> </declare-styleable> <!-- A list of capabilities that indicates to the OS what kinds of credentials diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7ef81abb8f0d..b211ac2fd316 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1026,6 +1026,12 @@ <!-- Duration, in milliseconds, of the display white balance animated transitions. --> <integer name="config_displayWhiteBalanceTransitionTime">3000</integer> + <!-- Duration, in milliseconds, of the display white balance animated transitions when increasing cct. --> + <integer name="config_displayWhiteBalanceTransitionTimeIncrease">1000</integer> + + <!-- Duration, in milliseconds, of the display white balance animated transitions when decreasing cct. --> + <integer name="config_displayWhiteBalanceTransitionTimeDecrease">40000</integer> + <!-- Device states where the sensor based rotation values should be reversed around the Z axis for the default display. TODO(b/265312193): Remove this workaround when this bug is fixed.--> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index fac6aac1db16..a2a4e34f3527 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -826,6 +826,9 @@ security policy. [CHAR_LIMIT=NONE]--> <string name="notification_channel_accessibility_security_policy">Accessibility usage</string> + <!-- Text shown when viewing channel settings for notifications related to displays --> + <string name="notification_channel_display">Display</string> + <!-- Label for foreground service notification when one app is running. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] --> <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is @@ -6290,6 +6293,16 @@ ul.</string> <!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] --> <string name="mic_access_off_toast">Microphone is blocked</string> + <!-- Title of connected display unavailable notifications. [CHAR LIMIT=NONE] --> + <string name="connected_display_unavailable_notification_title">Can\'t mirror to display</string> + <!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] --> + <string name="connected_display_unavailable_notification_content">Use a different cable and try again</string> + + <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] --> + <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string> + <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] --> + <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string> + <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] --> <string name="concurrent_display_notification_name">Dual screen</string> <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 643f4b148d74..c1ecb05a6df2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1997,6 +1997,7 @@ <java-symbol type="drawable" name="stat_sys_throttled" /> <java-symbol type="drawable" name="vpn_connected" /> <java-symbol type="drawable" name="vpn_disconnected" /> + <java-symbol type="drawable" name="usb_cable_unknown_issue" /> <java-symbol type="id" name="ask_checkbox" /> <java-symbol type="id" name="compat_checkbox" /> <java-symbol type="id" name="original_app_icon" /> @@ -3486,6 +3487,8 @@ <java-symbol type="array" name="config_displayWhiteBalanceDisplaySteps" /> <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" /> <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" /> + <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeIncrease" /> + <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeDecrease" /> <!-- Device states where the sensor based rotation values should be reversed around the Z axis for the default display. @@ -3811,6 +3814,7 @@ <java-symbol type="string" name="notification_channel_do_not_disturb" /> <java-symbol type="string" name="notification_channel_accessibility_magnification" /> <java-symbol type="string" name="notification_channel_accessibility_security_policy" /> + <java-symbol type="string" name="notification_channel_display" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultFieldClassificationService" /> <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" /> @@ -5064,6 +5068,10 @@ <java-symbol type="array" name="device_state_notification_thermal_contents"/> <java-symbol type="array" name="device_state_notification_power_save_titles"/> <java-symbol type="array" name="device_state_notification_power_save_contents"/> + <java-symbol type="string" name="connected_display_unavailable_notification_title"/> + <java-symbol type="string" name="connected_display_unavailable_notification_content"/> + <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/> + <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/> <java-symbol type="string" name="concurrent_display_notification_name"/> <java-symbol type="string" name="concurrent_display_notification_active_title"/> <java-symbol type="string" name="concurrent_display_notification_active_content"/> diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 824f5910f96c..75a72310db69 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -610,8 +610,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void cancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); mTunerSessions[0].cancel(); @@ -621,9 +619,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void cancel_forNonCurrentUser_doesNotCancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); - verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any()); doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mTunerSessions[0].cancel(); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java index 3b9d7ba5de3e..6edfa0294fdd 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -540,8 +540,6 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { @Test public void cancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); mTunerSessions[0].cancel(); @@ -551,8 +549,6 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { @Test public void cancel_forNonCurrentUser_doesNotCancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mTunerSessions[0].cancel(); diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 81dab0833af1..2993a0e63228 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -64,6 +64,7 @@ android_test { "servicestests-utils", "device-time-shell-utils", "testables", + "com.android.text.flags-aconfig-java", ], libs: [ diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java new file mode 100644 index 000000000000..c00eb91d752a --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java @@ -0,0 +1,80 @@ +/* + * 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 android.app.servertransaction; + +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowStateResizeItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowStateResizeItemTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowStateResizeItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IWindow mWindow; + @Mock + private ClientWindowFrames mFrames; + @Mock + private MergedConfiguration mConfiguration; + @Mock + private InsetsState mInsetsState; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() throws RemoteException { + final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + item.execute(mHandler, mPendingActions); + + verify(mWindow).resized(mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + } +} diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java index f8348d28b7fe..eefa6e482adf 100644 --- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java +++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java @@ -32,6 +32,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.List; + /** * Unit test for {@link ContentCaptureOptions}. * @@ -44,6 +46,9 @@ public class ContentCaptureOptionsTest { private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo"); private static final ComponentName COMPONENT1 = new ComponentName("comp", "one"); private static final ComponentName COMPONENT2 = new ComponentName("two", "comp"); + private static final List<List<String>> CONTENT_PROTECTION_REQUIRED_GROUPS = + List.of(List.of("first"), List.of("second", "third"), List.of()); + private static final List<List<String>> CONTENT_PROTECTION_OPTIONAL_GROUPS = List.of(); private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS = new ContentCaptureOptions( /* loggingLevel= */ 1000, @@ -55,7 +60,10 @@ public class ContentCaptureOptionsTest { /* enableReceiver= */ false, new ContentCaptureOptions.ContentProtectionOptions( /* enableReceiver= */ true, - /* bufferSize= */ 2001), + /* bufferSize= */ 2001, + CONTENT_PROTECTION_REQUIRED_GROUPS, + CONTENT_PROTECTION_OPTIONAL_GROUPS, + /* optionalGroupsThreshold= */ 2002), /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2)); @Mock private Context mContext; @@ -134,6 +142,19 @@ public class ContentCaptureOptionsTest { .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver) .append(", bufferSize=") .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize) + .append(", requiredGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups + .size()) + .append(", optionalGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups + .size()) + .append(", optionalGroupsThreshold=") + .append( + CONTENT_CAPTURE_OPTIONS + .contentProtectionOptions + .optionalGroupsThreshold) .append("], whitelisted=") .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents) .append(']') @@ -166,6 +187,15 @@ public class ContentCaptureOptionsTest { .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver); assertThat(actual.contentProtectionOptions.bufferSize) .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize); + assertThat(actual.contentProtectionOptions.requiredGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups); + assertThat(actual.contentProtectionOptions.optionalGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups); + assertThat(actual.contentProtectionOptions.optionalGroupsThreshold) + .isEqualTo( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroupsThreshold); assertThat(actual.whitelistedComponents) .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents); } diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 0941a2b6d263..6c14ee382bdc 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -137,7 +137,7 @@ public class FontScaleConverterActivityTest { ); }); - PollingCheck.waitFor(/* timeout= */ 7000, () -> { + PollingCheck.waitFor(/* timeout= */ 10000, () -> { AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false); rule.getScenario().onActivity(activity -> isActivityAtCorrectScale.set( @@ -163,7 +163,7 @@ public class FontScaleConverterActivityTest { }); PollingCheck.waitFor( - /* timeout= */ 5000, + /* timeout= */ 10000, () -> InstrumentationRegistry.getInstrumentation() .getContext() .getResources() diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index a8a5059eea20..3f78396e3a70 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -16,6 +16,8 @@ package android.graphics; +import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -30,6 +32,9 @@ import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextRunShaper; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.text.FontConfig; import android.util.ArrayMap; @@ -39,6 +44,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; @@ -107,6 +113,10 @@ public class TypefaceSystemFallbackTest { GLYPH_2EM_WIDTH = paint.measureText("a"); } + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { final AssetManager am = @@ -877,6 +887,130 @@ public class TypefaceSystemFallbackTest { assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); } + private static void assertA3emFontIsUsed(Typeface typeface) { + final Paint paint = new Paint(); + assertNotNull(typeface); + paint.setTypeface(typeface); + assertTrue("a3em font must be used", GLYPH_3EM_WIDTH == paint.measureText("a") + && GLYPH_1EM_WIDTH == paint.measureText("b") + && GLYPH_1EM_WIDTH == paint.measureText("c")); + } + + private static void assertB3emFontIsUsed(Typeface typeface) { + final Paint paint = new Paint(); + assertNotNull(typeface); + paint.setTypeface(typeface); + assertTrue("b3em font must be used", GLYPH_1EM_WIDTH == paint.measureText("a") + && GLYPH_3EM_WIDTH == paint.measureText("b") + && GLYPH_1EM_WIDTH == paint.measureText("c")); + } + + private static String getBaseXml(String font, String lang) { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family>" + + " <font weight='400' style='normal'>no_coverage.ttf</font>" + + " </family>" + + " <family name='named-family'>" + + " <font weight='400' style='normal'>no_coverage.ttf</font>" + + " </family>" + + " <family lang='%s'>" + + " <font weight='400' style='normal'>%s</font>" + + " </family>" + + "</familyset>"; + return String.format(xml, lang, font); + } + + private static String getCustomizationXml(String font, String op, String lang) { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-locale-family' operation='%s' lang='%s'>" + + " <font weight='400' style='normal' fallbackFor='named-family'>%s</font>" + + " </family>" + + "</fonts-modification>"; + return String.format(xml, op, lang, font); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_prepend() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "prepend", "ja-JP"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // operation "prepend" places font before the original font, thus b3em is used. + assertB3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_replace() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "replace", "ja-JP"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // operation "replace" removes the original font, thus b3em font is used. + assertB3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_append() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "append", "ja-JP"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // operation "append" comes next to the original font, so the original "a3em" is used. + assertA3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_ScriptMismatch() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "replace", "ko-KR"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // Since the script doesn't match, the customization is ignored. + assertA3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_SubscriptMatch() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "replace", "ko-Hani-KR"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // Hani script is supported by Japanese, Jpan. + assertB3emFontIsUsed(typeface); + } + @Test(expected = IllegalArgumentException.class) public void testBuildSystemFallback__Customization_new_named_family_no_name_exception() { final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" @@ -902,7 +1036,6 @@ public class TypefaceSystemFallbackTest { readFontCustomization(oemXml); } - @Test public void testBuildSystemFallback_UpdatableFont() { final String xml = "<?xml version='1.0' encoding='UTF-8'?>" diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java index 55ded9c8813d..517aeae53784 100644 --- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java +++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java @@ -16,6 +16,10 @@ package android.service.notification; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; + import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM; import static junit.framework.Assert.assertEquals; @@ -24,20 +28,40 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Mockito.spy; + +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.os.Bundle; import android.os.Parcel; +import android.os.SharedMemory; +import android.testing.TestableContext; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver; import org.junit.After; +import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import java.util.ArrayList; +import java.util.List; + @SmallTest @RunWith(Parameterized.class) public class NotificationRankingUpdateTest { @@ -56,16 +80,368 @@ public class NotificationRankingUpdateTest { @Parameterized.Parameter public boolean mRankingUpdateAshmem; + @Rule + public TestableContext mContext = + spy(new TestableContext(InstrumentationRegistry.getContext(), null)); + + protected TestableContext getContext() { + return mContext; + } + + public static String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"}; + + /** + * Creates a NotificationRankingUpdate with prepopulated Ranking entries + * @param context A testable context, used for PendingIntent creation + * @return The NotificationRankingUpdate to be used as test data + */ + public static NotificationRankingUpdate generateUpdate(TestableContext context) { + NotificationListenerService.Ranking[] rankings = + new NotificationListenerService.Ranking[mKeys.length]; + for (int i = 0; i < mKeys.length; i++) { + final String key = mKeys[i]; + NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); + ranking.populate( + key, + i, + !isIntercepted(i), + getVisibilityOverride(i), + getSuppressedVisualEffects(i), + getImportance(i), + getExplanation(key), + getOverrideGroupKey(key), + getChannel(key, i), + getPeople(key, i), + getSnoozeCriteria(key, i), + getShowBadge(i), + getUserSentiment(i), + getHidden(i), + lastAudiblyAlerted(i), + getNoisy(i), + getSmartActions(key, i, context), + getSmartReplies(key, i), + canBubble(i), + isTextChanged(i), + isConversation(i), + getShortcutInfo(i), + getRankingAdjustment(i), + isBubble(i), + getProposedImportance(i), + hasSensitiveContent(i) + ); + rankings[i] = ranking; + } + return new NotificationRankingUpdate(rankings); + } + + /** + * Produces a visibility override value based on the provided index. + */ + public static int getVisibilityOverride(int index) { + return index * 9; + } + + /** + * Produces a group key based on the provided key. + */ + public static String getOverrideGroupKey(String key) { + return key + key; + } + + /** + * Produces a boolean that can be used to represent isIntercepted, based on the provided index. + */ + public static boolean isIntercepted(int index) { + return index % 2 == 0; + } + + /** + * Produces a suppressed visual effects value based on the provided index + */ + public static int getSuppressedVisualEffects(int index) { + return index * 2; + } + + /** + * Produces an importance value, based on the provided index + */ + public static int getImportance(int index) { + return index; + } + + /** + * Produces an explanation value, based on the provided key + */ + public static String getExplanation(String key) { + return key + "explain"; + } + + /** + * Produces a notification channel, based on the provided key and index + */ + public static NotificationChannel getChannel(String key, int index) { + return new NotificationChannel(key, key, getImportance(index)); + } + + /** + * Produces a boolean that can be used to represent showBadge, based on the provided index + */ + public static boolean getShowBadge(int index) { + return index % 3 == 0; + } + + /** + * Produces a user sentiment value, based on the provided index + */ + public static int getUserSentiment(int index) { + switch(index % 3) { + case 0: + return USER_SENTIMENT_NEGATIVE; + case 1: + return USER_SENTIMENT_NEUTRAL; + case 2: + return USER_SENTIMENT_POSITIVE; + } + return USER_SENTIMENT_NEUTRAL; + } + + /** + * Produces a boolean that can be used to represent "hidden," based on the provided index. + */ + public static boolean getHidden(int index) { + return index % 2 == 0; + } + + /** + * Produces a long to represent lastAudiblyAlerted based on the provided index. + */ + public static long lastAudiblyAlerted(int index) { + return index * 2000L; + } + + /** + * Produces a boolean that can be used to represent "noisy," based on the provided index. + */ + public static boolean getNoisy(int index) { + return index < 1; + } + + /** + * Produces strings that can be used to represent people, based on the provided key and index. + */ + public static ArrayList<String> getPeople(String key, int index) { + ArrayList<String> people = new ArrayList<>(); + for (int i = 0; i < index; i++) { + people.add(i + key); + } + return people; + } + + /** + * Produces a number of snoozeCriteria, based on the provided key and index. + */ + public static ArrayList<SnoozeCriterion> getSnoozeCriteria(String key, int index) { + ArrayList<SnoozeCriterion> snooze = new ArrayList<>(); + for (int i = 0; i < index; i++) { + snooze.add(new SnoozeCriterion(key + i, getExplanation(key), key)); + } + return snooze; + } + + /** + * Produces a list of Actions which can be used to represent smartActions. + * These actions are built from pending intents with intent titles based on the provided + * key, and ids based on the provided index. + */ + public static ArrayList<Notification.Action> getSmartActions(String key, + int index, + TestableContext context) { + ArrayList<Notification.Action> actions = new ArrayList<>(); + for (int i = 0; i < index; i++) { + PendingIntent intent = PendingIntent.getBroadcast( + context, + index /*requestCode*/, + new Intent("ACTION_" + key), + PendingIntent.FLAG_IMMUTABLE /*flags*/); + actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build()); + } + return actions; + } + + /** + * Produces index number of "smart replies," all based on the provided key and index + */ + public static ArrayList<CharSequence> getSmartReplies(String key, int index) { + ArrayList<CharSequence> choices = new ArrayList<>(); + for (int i = 0; i < index; i++) { + choices.add("choice_" + key + "_" + i); + } + return choices; + } + + /** + * Produces a boolean that can be used to represent canBubble, based on the provided index + */ + public static boolean canBubble(int index) { + return index % 4 == 0; + } + + /** + * Produces a boolean that can be used to represent isTextChanged, based on the provided index. + */ + public static boolean isTextChanged(int index) { + return index % 4 == 0; + } + + /** + * Produces a boolean that can be used to represent isConversation, based on the provided index. + */ + public static boolean isConversation(int index) { + return index % 4 == 0; + } + + /** + * Produces a ShortcutInfo value based on the provided index. + */ + public static ShortcutInfo getShortcutInfo(int index) { + ShortcutInfo si = new ShortcutInfo( + index, String.valueOf(index), "packageName", new ComponentName("1", "1"), null, + "title", 0, "titleResName", "text", 0, "textResName", + "disabledMessage", 0, "disabledMessageResName", + null, null, 0, null, 0, 0, + 0, "iconResName", "bitmapPath", null, 0, + null, null, null, null); + return si; + } + + /** + * Produces a rankingAdjustment value, based on the provided index. + */ + public static int getRankingAdjustment(int index) { + return index % 3 - 1; + } + + /** + * Produces a proposedImportance, based on the provided index. + */ + public static int getProposedImportance(int index) { + return index % 5 - 1; + } + + /** + * Produces a boolean that can be used to represent hasSensitiveContent, based on the provided + * index. + */ + public static boolean hasSensitiveContent(int index) { + return index % 3 == 0; + } + + /** + * Produces a boolean that can be used to represent isBubble, based on the provided index. + */ + public static boolean isBubble(int index) { + return index % 4 == 0; + } + + /** + * Checks that each of the pairs of actions in the two provided lists has identical titles, + * and that the lists have the same number of elements. + */ + public void assertActionsEqual( + List<Notification.Action> expecteds, List<Notification.Action> actuals) { + Assert.assertEquals(expecteds.size(), actuals.size()); + for (int i = 0; i < expecteds.size(); i++) { + Notification.Action expected = expecteds.get(i); + Notification.Action actual = actuals.get(i); + Assert.assertEquals(expected.title.toString(), actual.title.toString()); + } + } + + /** + * Checks that all subelements of the provided NotificationRankingUpdates are equal. + */ + public void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) { + detailedAssertEquals(a.getRankingMap(), b.getRankingMap()); + } + + /** + * Checks that all subelements of the provided Ranking objects are equal. + */ + public void detailedAssertEquals(String comment, NotificationListenerService.Ranking a, + NotificationListenerService.Ranking b) { + Assert.assertEquals(comment, a.getKey(), b.getKey()); + Assert.assertEquals(comment, a.getRank(), b.getRank()); + Assert.assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter()); + Assert.assertEquals(comment, a.getLockscreenVisibilityOverride(), + b.getLockscreenVisibilityOverride()); + Assert.assertEquals(comment, a.getSuppressedVisualEffects(), + b.getSuppressedVisualEffects()); + Assert.assertEquals(comment, a.getImportance(), b.getImportance()); + Assert.assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation()); + Assert.assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey()); + Assert.assertEquals(comment, a.getChannel().toString(), b.getChannel().toString()); + Assert.assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople()); + Assert.assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria()); + Assert.assertEquals(comment, a.canShowBadge(), b.canShowBadge()); + Assert.assertEquals(comment, a.getUserSentiment(), b.getUserSentiment()); + Assert.assertEquals(comment, a.isSuspended(), b.isSuspended()); + Assert.assertEquals(comment, a.getLastAudiblyAlertedMillis(), + b.getLastAudiblyAlertedMillis()); + Assert.assertEquals(comment, a.isNoisy(), b.isNoisy()); + Assert.assertEquals(comment, a.getSmartReplies(), b.getSmartReplies()); + Assert.assertEquals(comment, a.canBubble(), b.canBubble()); + Assert.assertEquals(comment, a.isConversation(), b.isConversation()); + if (a.getConversationShortcutInfo() != null && b.getConversationShortcutInfo() != null) { + Assert.assertEquals(comment, a.getConversationShortcutInfo().getId(), + b.getConversationShortcutInfo().getId()); + } else { + // One or both must be null, so we can check for equality. + Assert.assertEquals(a.getConversationShortcutInfo(), b.getConversationShortcutInfo()); + } + assertActionsEqual(a.getSmartActions(), b.getSmartActions()); + Assert.assertEquals(a.getProposedImportance(), b.getProposedImportance()); + Assert.assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent()); + } + + /** + * Checks that the two RankingMaps have identical keys, and that each Ranking object for + * each of those keys is identical. + */ + public void detailedAssertEquals(NotificationListenerService.RankingMap a, + NotificationListenerService.RankingMap b) { + NotificationListenerService.Ranking arank = new NotificationListenerService.Ranking(); + NotificationListenerService.Ranking brank = new NotificationListenerService.Ranking(); + assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys()); + for (String key : a.getOrderedKeys()) { + a.getRanking(key, arank); + b.getRanking(key, brank); + detailedAssertEquals("ranking for key <" + key + ">", arank, brank); + } + } + @Before public void setUp() { mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT); - SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> { - if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) { - return mRankingUpdateAshmem; + SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() { + @Override + public boolean isEnabled(Flag flag) { + if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) { + return mRankingUpdateAshmem; + } + return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag); + } + + @Override + public int getIntValue(Flag flag) { + return 0; + } + + @Override + public String getStringValue(Flag flag) { + return null; } - return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag); }; } @@ -74,8 +450,13 @@ public class NotificationRankingUpdateTest { SystemUiSystemPropertiesFlags.TEST_RESOLVER = null; } - public NotificationListenerService.Ranking createTestRanking(String key, int rank) { + /** + * Creates a mostly empty Test Ranking object with the specified key, rank, and smartActions. + */ + public NotificationListenerService.Ranking createEmptyTestRanking( + String key, int rank, ArrayList<Notification.Action> actions) { NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); + ranking.populate( /* key= */ key, /* rank= */ rank, @@ -93,7 +474,7 @@ public class NotificationRankingUpdateTest { /* hidden= */ false, /* lastAudiblyAlertedMs= */ -1, /* noisy= */ false, - /* smartActions= */ null, + /* smartActions= */ actions, /* smartReplies= */ null, /* canBubble= */ false, /* isTextChanged= */ false, @@ -107,54 +488,111 @@ public class NotificationRankingUpdateTest { return ranking; } + // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking. @Test - public void testRankingUpdate_rankingConstructor() { - NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123); - NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( - new NotificationListenerService.Ranking[]{ranking}); + public void testRankingUpdate_parcel() { + NotificationRankingUpdate nru = generateUpdate(getContext()); + Parcel parcel = Parcel.obtain(); + nru.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel); + // The rankingUpdate file descriptor is only non-null in the new path. + if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( + SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { + assertTrue(nru1.isFdNotNullAndClosed()); + } + detailedAssertEquals(nru, nru1); + parcel.recycle(); + } - NotificationListenerService.RankingMap retrievedRankings = rankingUpdate.getRankingMap(); - NotificationListenerService.Ranking retrievedRanking = - new NotificationListenerService.Ranking(); - assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking)); - assertEquals(123, retrievedRanking.getRank()); + // Tests parceling of RankingMap and RankingMap.equals + @Test + public void testRankingMap_parcel() { + NotificationListenerService.RankingMap rmap = generateUpdate(getContext()).getRankingMap(); + Parcel parcel = Parcel.obtain(); + rmap.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotificationListenerService.RankingMap rmap1 = + NotificationListenerService.RankingMap.CREATOR.createFromParcel(parcel); + + detailedAssertEquals(rmap, rmap1); + Assert.assertEquals(rmap, rmap1); + parcel.recycle(); } + // Tests parceling of Ranking and Ranking.equals @Test - public void testRankingUpdate_parcelConstructor() { - NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123); - NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( - new NotificationListenerService.Ranking[]{ranking}); + public void testRanking_parcel() { + NotificationListenerService.Ranking ranking = + generateUpdate(getContext()).getRankingMap().getRawRankingObject(mKeys[0]); + Parcel parcel = Parcel.obtain(); + ranking.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotificationListenerService.Ranking ranking1 = + new NotificationListenerService.Ranking(parcel); + detailedAssertEquals("rankings differ: ", ranking, ranking1); + Assert.assertEquals(ranking, ranking1); + parcel.recycle(); + } - Parcel parceledRankingUpdate = Parcel.obtain(); - rankingUpdate.writeToParcel(parceledRankingUpdate, 0); - parceledRankingUpdate.setDataPosition(0); + // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking. + @Test + public void testRankingUpdate_equals_legacy() { + NotificationRankingUpdate nru = generateUpdate(getContext()); + NotificationRankingUpdate nru2 = generateUpdate(getContext()); + detailedAssertEquals(nru, nru2); + Assert.assertEquals(nru, nru2); + NotificationListenerService.Ranking tweak = + nru2.getRankingMap().getRawRankingObject(mKeys[0]); + tweak.populate( + tweak.getKey(), + tweak.getRank(), + !tweak.matchesInterruptionFilter(), // note the inversion here! + tweak.getLockscreenVisibilityOverride(), + tweak.getSuppressedVisualEffects(), + tweak.getImportance(), + tweak.getImportanceExplanation(), + tweak.getOverrideGroupKey(), + tweak.getChannel(), + (ArrayList) tweak.getAdditionalPeople(), + (ArrayList) tweak.getSnoozeCriteria(), + tweak.canShowBadge(), + tweak.getUserSentiment(), + tweak.isSuspended(), + tweak.getLastAudiblyAlertedMillis(), + tweak.isNoisy(), + (ArrayList) tweak.getSmartActions(), + (ArrayList) tweak.getSmartReplies(), + tweak.canBubble(), + tweak.isTextChanged(), + tweak.isConversation(), + tweak.getConversationShortcutInfo(), + tweak.getRankingAdjustment(), + tweak.isBubble(), + tweak.getProposedImportance(), + tweak.hasSensitiveContent() + ); + assertNotEquals(nru, nru2); + } - NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate( - parceledRankingUpdate); + @Test + public void testRankingUpdate_rankingConstructor() { + NotificationRankingUpdate nru = generateUpdate(getContext()); + NotificationRankingUpdate constructedNru = new NotificationRankingUpdate( + new NotificationListenerService.Ranking[]{ + nru.getRankingMap().getRawRankingObject(mKeys[0]), + nru.getRankingMap().getRawRankingObject(mKeys[1]), + nru.getRankingMap().getRawRankingObject(mKeys[2]), + nru.getRankingMap().getRawRankingObject(mKeys[3]), + nru.getRankingMap().getRawRankingObject(mKeys[4]) + }); - NotificationListenerService.RankingMap retrievedRankings = - retrievedRankingUpdate.getRankingMap(); - assertNotNull(retrievedRankings); - // The rankingUpdate file descriptor is only non-null in the new path. - if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { - assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed()); - } - NotificationListenerService.Ranking retrievedRanking = - new NotificationListenerService.Ranking(); - assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking)); - assertEquals(123, retrievedRanking.getRank()); - assertTrue(retrievedRankingUpdate.equals(rankingUpdate)); - parceledRankingUpdate.recycle(); + detailedAssertEquals(nru, constructedNru); } @Test public void testRankingUpdate_emptyParcelInCheck() { - NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123); - NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( - new NotificationListenerService.Ranking[]{ranking}); - + NotificationRankingUpdate rankingUpdate = generateUpdate(getContext()); Parcel parceledRankingUpdate = Parcel.obtain(); rankingUpdate.writeToParcel(parceledRankingUpdate, 0); @@ -163,37 +601,119 @@ public class NotificationRankingUpdateTest { NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate( parceledRankingUpdate); assertNull(retrievedRankingUpdate.getRankingMap()); + parceledRankingUpdate.recycle(); } @Test public void testRankingUpdate_describeContents() { - NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123); - NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( - new NotificationListenerService.Ranking[]{ranking}); + NotificationRankingUpdate rankingUpdate = generateUpdate(getContext()); assertEquals(0, rankingUpdate.describeContents()); } @Test public void testRankingUpdate_equals() { - NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123); + NotificationListenerService.Ranking ranking = createEmptyTestRanking(TEST_KEY, 123, null); NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( new NotificationListenerService.Ranking[]{ranking}); - // Reflexive equality. - assertTrue(rankingUpdate.equals(rankingUpdate)); - // Null or wrong class inequality. + // Reflexive equality, including handling nulls properly + detailedAssertEquals(rankingUpdate, rankingUpdate); + // Null or wrong class inequality assertFalse(rankingUpdate.equals(null)); assertFalse(rankingUpdate.equals(ranking)); - // Different ranking contents inequality. - NotificationListenerService.Ranking ranking2 = createTestRanking(TEST_KEY, 456); + // Different rank inequality + NotificationListenerService.Ranking ranking2 = createEmptyTestRanking(TEST_KEY, 456, null); NotificationRankingUpdate rankingUpdate2 = new NotificationRankingUpdate( new NotificationListenerService.Ranking[]{ranking2}); assertFalse(rankingUpdate.equals(rankingUpdate2)); - // Same ranking contents equality. - ranking2 = createTestRanking(TEST_KEY, 123); + // Different key inequality + ranking2 = createEmptyTestRanking(TEST_KEY + "DIFFERENT", 123, null); rankingUpdate2 = new NotificationRankingUpdate( new NotificationListenerService.Ranking[]{ranking2}); - assertTrue(rankingUpdate.equals(rankingUpdate2)); + assertFalse(rankingUpdate.equals(rankingUpdate2)); + } + + @Test + public void testRankingUpdate_writesSmartActionToParcel() { + if (!mRankingUpdateAshmem) { + return; + } + ArrayList<Notification.Action> actions = new ArrayList<>(); + PendingIntent intent = PendingIntent.getBroadcast( + getContext(), + 0 /*requestCode*/, + new Intent("ACTION_" + TEST_KEY), + PendingIntent.FLAG_IMMUTABLE /*flags*/); + actions.add(new Notification.Action.Builder(null /*icon*/, TEST_KEY, intent).build()); + + NotificationListenerService.Ranking ranking = + createEmptyTestRanking(TEST_KEY, 123, actions); + NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( + new NotificationListenerService.Ranking[]{ranking}); + + Parcel parcel = Parcel.obtain(); + rankingUpdate.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + SharedMemory fd = parcel.readParcelable(getClass().getClassLoader(), SharedMemory.class); + Bundle smartActionsBundle = parcel.readBundle(getClass().getClassLoader()); + + // Assert the file descriptor is valid + assertNotNull(fd); + assertFalse(fd.getFd() == -1); + + // Assert that the smart action is in the parcel + assertNotNull(smartActionsBundle); + ArrayList<Notification.Action> recoveredActions = + smartActionsBundle.getParcelableArrayList(TEST_KEY, Notification.Action.class); + assertNotNull(recoveredActions); + assertEquals(actions.size(), recoveredActions.size()); + assertEquals(actions.get(0).title.toString(), recoveredActions.get(0).title.toString()); + parcel.recycle(); + } + + @Test + public void testRankingUpdate_handlesEmptySmartActionList() { + if (!mRankingUpdateAshmem) { + return; + } + ArrayList<Notification.Action> actions = new ArrayList<>(); + NotificationListenerService.Ranking ranking = + createEmptyTestRanking(TEST_KEY, 123, actions); + NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( + new NotificationListenerService.Ranking[]{ranking}); + + Parcel parcel = Parcel.obtain(); + rankingUpdate.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + // Ensure that despite an empty actions list, we can still unparcel the update. + NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel); + assertNotNull(newRankingUpdate); + assertNotNull(newRankingUpdate.getRankingMap()); + detailedAssertEquals(rankingUpdate, newRankingUpdate); + parcel.recycle(); + } + + @Test + public void testRankingUpdate_handlesNullSmartActionList() { + if (!mRankingUpdateAshmem) { + return; + } + NotificationListenerService.Ranking ranking = + createEmptyTestRanking(TEST_KEY, 123, null); + NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( + new NotificationListenerService.Ranking[]{ranking}); + + Parcel parcel = Parcel.obtain(); + rankingUpdate.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + // Ensure that despite an empty actions list, we can still unparcel the update. + NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel); + assertNotNull(newRankingUpdate); + assertNotNull(newRankingUpdate.getRankingMap()); + detailedAssertEquals(rankingUpdate, newRankingUpdate); + parcel.recycle(); } } diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index 101f7c21fa19..5c411d5335a7 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -31,6 +31,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Collections; + /** * Unit test for {@link ContentCaptureManager}. * @@ -69,7 +71,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ false, BUFFER_SIZE)); + /* enableReceiver= */ false, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -82,7 +88,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, /* bufferSize= */ 0)); + /* enableReceiver= */ true, + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -95,7 +105,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, BUFFER_SIZE)); + /* enableReceiver= */ true, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index 3373b8b13273..e76d266c614c 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -47,6 +47,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -112,7 +113,11 @@ public class MainContentCaptureSessionTest { createOptions( /* enableContentCaptureReceiver= */ true, new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, -BUFFER_SIZE)); + /* enableReceiver= */ true, + -BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); MainContentCaptureSession session = createSession(options); session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; @@ -313,7 +318,11 @@ public class MainContentCaptureSessionTest { return createOptions( enableContentCaptureReceiver, new ContentCaptureOptions.ContentProtectionOptions( - enableContentProtectionReceiver, BUFFER_SIZE)); + enableContentProtectionReceiver, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); } private ContentCaptureManager createManager(ContentCaptureOptions options) { diff --git a/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java index 51c8bc06e878..cce2faf71897 100644 --- a/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java +++ b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java @@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import android.view.MotionEvent; +import android.widget.flags.FakeFeatureFlagsImpl; +import android.widget.flags.Flags; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -54,6 +56,8 @@ public class DifferentialMotionFlingHelperTest { private final TestDifferentialMotionFlingTarget mFlingTarget = new TestDifferentialMotionFlingTarget(); + private final FakeFeatureFlagsImpl mFakeWidgetFeatureFlags = new FakeFeatureFlagsImpl(); + private DifferentialMotionFlingHelper mFlingHelper; @Before @@ -62,7 +66,10 @@ public class DifferentialMotionFlingHelperTest { ApplicationProvider.getApplicationContext(), mFlingTarget, mVelocityThresholdCalculator, - mVelocityProvider); + mVelocityProvider, + mFakeWidgetFeatureFlags); + mFakeWidgetFeatureFlags.setFlag( + Flags.FLAG_ENABLE_PLATFORM_WIDGET_DIFFERENTIAL_MOTION_FLING, true); } @Test @@ -139,6 +146,18 @@ public class DifferentialMotionFlingHelperTest { } @Test + public void flingFeatureFlagDisabled_noFlingCalculation() { + mFakeWidgetFeatureFlags.setFlag( + Flags.FLAG_ENABLE_PLATFORM_WIDGET_DIFFERENTIAL_MOTION_FLING, false); + mMinVelocity = 50; + mMaxVelocity = 100; + deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60); + + assertFalse(mVelocityCalculated); + assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0); + } + + @Test public void negativeFlingVelocityAboveMaximum_velocityClamped() { mMinVelocity = 50; mMaxVelocity = 100; diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 25f5819fb671..6229530dc33f 100644 --- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -18,6 +18,8 @@ package android.window; import static android.os.PerformanceHintManager.Session.CPU_LOAD_RESET; import static android.os.PerformanceHintManager.Session.CPU_LOAD_UP; +import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; import static android.window.SystemPerformanceHinter.HINT_ADPF; @@ -29,6 +31,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -150,7 +153,11 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); - verify(mTransaction).apply(); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); + verify(mTransaction).applyAsyncUnsafe(); } @Test @@ -164,7 +171,11 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); - verify(mTransaction).apply(); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); + verify(mTransaction).applyAsyncUnsafe(); } @Test @@ -177,7 +188,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF verify(mTransaction).setEarlyWakeupStart(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); } @Test @@ -189,7 +200,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF verify(mTransaction).setEarlyWakeupEnd(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); } @Test @@ -231,8 +242,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); } @@ -248,8 +263,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); } @@ -265,8 +284,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); } } @@ -280,8 +303,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); reset(mTransaction); reset(mAdpfSession); @@ -290,15 +317,17 @@ public class SystemPerformanceHinterTests { mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON); // Verify we never call SF and perf manager since session1 is already running verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); + verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean()); verify(mTransaction, never()).setEarlyWakeupEnd(); - verify(mTransaction, never()).apply(); + verify(mTransaction, never()).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); session2.close(); // Verify we have not cleaned up because session1 is still running verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); + verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean()); verify(mTransaction, never()).setEarlyWakeupEnd(); - verify(mTransaction, never()).apply(); + verify(mTransaction, never()).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); session1.close(); @@ -306,8 +335,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); } @@ -321,8 +354,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction).setEarlyWakeupStart(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); reset(mTransaction); reset(mAdpfSession); @@ -333,8 +370,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mSecondaryDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).setFrameRateCategory( + eq(mSecondaryDisplayRoot), + eq(FRAME_RATE_CATEGORY_HIGH), + eq(false)); verify(mTransaction, never()).setEarlyWakeupStart(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); reset(mTransaction); reset(mAdpfSession); @@ -345,11 +386,19 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setFrameRateCategory( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction, never()).setFrameRateSelectionStrategy( eq(mSecondaryDisplayRoot), anyInt()); + verify(mTransaction, never()).setFrameRateCategory( + eq(mSecondaryDisplayRoot), + anyInt(), + eq(false)); verify(mTransaction, never()).setEarlyWakeupEnd(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession, never()).sendHint(anyInt()); reset(mTransaction); reset(mAdpfSession); @@ -362,8 +411,12 @@ public class SystemPerformanceHinterTests { verify(mTransaction).setFrameRateSelectionStrategy( eq(mSecondaryDisplayRoot), eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setFrameRateCategory( + eq(mSecondaryDisplayRoot), + eq(FRAME_RATE_CATEGORY_DEFAULT), + eq(false)); verify(mTransaction).setEarlyWakeupEnd(); - verify(mTransaction).apply(); + verify(mTransaction).applyAsyncUnsafe(); verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java index 681ba9c353e5..06b62f85bede 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java @@ -29,6 +29,8 @@ import static androidx.test.espresso.matcher.ViewMatchers.withClassName; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +41,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -47,11 +51,17 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; +import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.Flags; import android.view.accessibility.IAccessibilityManager; import androidx.lifecycle.Lifecycle; @@ -90,6 +100,9 @@ public class AccessibilityShortcutChooserActivityTest { private TestAccessibilityShortcutChooserActivity mActivity; @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private AccessibilityServiceInfo mAccessibilityServiceInfo; @@ -101,6 +114,8 @@ public class AccessibilityShortcutChooserActivityTest { private ApplicationInfo mApplicationInfo; @Mock private IAccessibilityManager mAccessibilityManagerService; + @Mock + private KeyguardManager mKeyguardManager; @Before public void setUp() throws Exception { @@ -116,22 +131,19 @@ public class AccessibilityShortcutChooserActivityTest { Collections.singletonList(mAccessibilityServiceInfo))); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( anyString(), anyInt(), anyInt())).thenReturn(true); - TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService); - mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); - mScenario.onActivity(activity -> mActivity = activity); - mScenario.moveToState(Lifecycle.State.CREATED); - mScenario.moveToState(Lifecycle.State.STARTED); - mScenario.moveToState(Lifecycle.State.RESUMED); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + TestAccessibilityShortcutChooserActivity.setupForTesting( + mAccessibilityManagerService, mKeyguardManager); } @After public void cleanUp() { - mScenario.moveToState(Lifecycle.State.DESTROYED); + mScenario.close(); } @Test public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() { + launchActivity(); openShortcutsList(); // Performing the double-click is flaky so retry if needed. @@ -154,6 +166,7 @@ public class AccessibilityShortcutChooserActivityTest { throws Exception { when(mAccessibilityManagerService.isAccessibilityTargetAllowed( eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false); + launchActivity(); openShortcutsList(); onView(withText(TEST_LABEL)).perform(scrollTo(), click()); @@ -165,6 +178,7 @@ public class AccessibilityShortcutChooserActivityTest { @Test public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() { TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true); + launchActivity(); openShortcutsList(); onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); @@ -176,6 +190,7 @@ public class AccessibilityShortcutChooserActivityTest { @Test public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() { TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false); + launchActivity(); openShortcutsList(); onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); @@ -184,6 +199,30 @@ public class AccessibilityShortcutChooserActivityTest { onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist()); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_ALLOW_SHORTCUT_CHOOSER_ON_LOCKSCREEN) + public void createDialog_onLockscreen_hasExpectedContent() { + when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); + launchActivity(); + + final AlertDialog dialog = mActivity.getMenuDialog(); + + assertThat(dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility()) + .isEqualTo(View.GONE); + assertThat(dialog.getWindow().getAttributes().flags + & WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + .isEqualTo(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + + private void launchActivity() { + mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); + mScenario.onActivity(activity -> mActivity = activity); + mScenario.moveToState(Lifecycle.State.CREATED); + mScenario.moveToState(Lifecycle.State.STARTED); + mScenario.moveToState(Lifecycle.State.RESUMED); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + private void openShortcutsList() { UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL)); if (editButton != null) { @@ -198,9 +237,13 @@ public class AccessibilityShortcutChooserActivityTest { public static class TestAccessibilityShortcutChooserActivity extends AccessibilityShortcutChooserActivity { private static IAccessibilityManager sAccessibilityManagerService; + private static KeyguardManager sKeyguardManager; - public static void setupForTesting(IAccessibilityManager accessibilityManagerService) { + public static void setupForTesting( + IAccessibilityManager accessibilityManagerService, + KeyguardManager keyguardManager) { sAccessibilityManagerService = accessibilityManagerService; + sKeyguardManager = keyguardManager; } @Override @@ -210,6 +253,9 @@ public class AccessibilityShortcutChooserActivityTest { return new AccessibilityManager(this, new Handler(getMainLooper()), sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true); } + if (Context.KEYGUARD_SERVICE.equals(name)) { + return sKeyguardManager; + } return super.getSystemService(name); } diff --git a/framework-minus-apex-ravenwood-policies.txt b/framework-minus-apex-ravenwood-policies.txt new file mode 100644 index 000000000000..6bac58bf8ed7 --- /dev/null +++ b/framework-minus-apex-ravenwood-policies.txt @@ -0,0 +1 @@ +# Ravenwood "policy" file for framework-minus-apex. diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 735bc180c015..52b0b95d3e76 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -236,7 +236,9 @@ public class FontListParser { } } - return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate, + return new FontConfig(families, filtered, resultNamedFamilies, + customization.getLocaleFamilyCustomizations(), + lastModifiedDate, configVersion); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 9d32272755c0..db1cc446b2d6 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -16,8 +16,11 @@ package android.graphics; +import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; + import android.annotation.ColorInt; import android.annotation.ColorLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -2125,7 +2128,7 @@ public class Paint { * @return the font's recommended interline spacing. */ public float getFontMetrics(FontMetrics metrics) { - return nGetFontMetrics(mNativePaint, metrics); + return nGetFontMetrics(mNativePaint, metrics, false); } /** @@ -2139,6 +2142,32 @@ public class Paint { } /** + * Get the font metrics used for the locale + * + * Obtain the metrics of the font that is used for the specified locale by + * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent + * and maximum descent will be set. + * + * This API is useful for determining the default line height of the empty text field, e.g. + * {@link android.widget.EditText}. + * + * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its + * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the + * descent will be the custom font's descent or larger. + * + * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g. + * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the + * ascent will be the serif font's ascent or smaller, the descent will be the serif font's + * descent or larger. + * + * @param metrics an output parameter. All members will be set by calling this function. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void getFontMetricsForLocale(@NonNull FontMetrics metrics) { + nGetFontMetrics(mNativePaint, metrics, true); + } + + /** * Returns the font metrics value for the given text. * * If the text is rendered with multiple font files, this function returns the large ascent and @@ -2318,7 +2347,7 @@ public class Paint { * @return the font's interline spacing. */ public int getFontMetricsInt(FontMetricsInt fmi) { - return nGetFontMetricsInt(mNativePaint, fmi); + return nGetFontMetricsInt(mNativePaint, fmi, false); } public FontMetricsInt getFontMetricsInt() { @@ -2328,6 +2357,32 @@ public class Paint { } /** + * Get the font metrics used for the locale + * + * Obtain the metrics of the font that is used for the specified locale by + * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent + * and maximum descent will be set. + * + * This API is useful for determining the default line height of the empty text field, e.g. + * {@link android.widget.EditText}. + * + * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its + * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the + * descent will be the custom font's descent or larger. + * + * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g. + * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the + * ascent will be the serif font's ascent or smaller, the descent will be the serif font's + * descent or larger. + * + * @param metrics an output parameter. All members will be set by calling this function. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void getFontMetricsIntForLocale(@NonNull FontMetricsInt metrics) { + nGetFontMetricsInt(mNativePaint, metrics, true); + } + + /** * Return the recommend line spacing based on the current typeface and * text size. * @@ -3446,9 +3501,11 @@ public class Paint { @FastNative private static native void nSetFontFeatureSettings(long paintPtr, String settings); @FastNative - private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics); + private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics, + boolean useLocale); @FastNative - private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi); + private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi, + boolean useLocale); // ---------------- @CriticalNative ------------------------ diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index b458dd9021d0..6e04a2f5e405 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -22,6 +22,7 @@ import static android.text.FontConfig.NamedFamilyList; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.FontListParser; +import android.text.FontConfig; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; @@ -34,6 +35,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -52,14 +54,19 @@ public class FontCustomizationParser { private final List<Alias> mAdditionalAliases; + private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations; + public Result() { mAdditionalNamedFamilies = Collections.emptyMap(); + mLocaleFamilyCustomizations = Collections.emptyList(); mAdditionalAliases = Collections.emptyList(); } public Result(Map<String, NamedFamilyList> additionalNamedFamilies, + List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations, List<Alias> additionalAliases) { mAdditionalNamedFamilies = additionalNamedFamilies; + mLocaleFamilyCustomizations = localeFamilyCustomizations; mAdditionalAliases = additionalAliases; } @@ -70,6 +77,10 @@ public class FontCustomizationParser { public List<Alias> getAdditionalAliases() { return mAdditionalAliases; } + + public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() { + return mLocaleFamilyCustomizations; + } } /** @@ -89,7 +100,9 @@ public class FontCustomizationParser { } private static Result validateAndTransformToResult( - List<NamedFamilyList> families, List<Alias> aliases) { + List<NamedFamilyList> families, + List<FontConfig.Customization.LocaleFallback> outLocaleFamilies, + List<Alias> aliases) { HashMap<String, NamedFamilyList> namedFamily = new HashMap<>(); for (int i = 0; i < families.size(); ++i) { final NamedFamilyList family = families.get(i); @@ -105,7 +118,7 @@ public class FontCustomizationParser { + "requires fallackTarget attribute"); } } - return new Result(namedFamily, aliases); + return new Result(namedFamily, outLocaleFamilies, aliases); } private static Result readFamilies( @@ -115,12 +128,13 @@ public class FontCustomizationParser { ) throws XmlPullParserException, IOException { List<NamedFamilyList> families = new ArrayList<>(); List<Alias> aliases = new ArrayList<>(); + List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - readFamily(parser, fontDir, families, updatableFontMap); + readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap); } else if (tag.equals("family-list")) { readFamilyList(parser, fontDir, families, updatableFontMap); } else if (tag.equals("alias")) { @@ -129,13 +143,14 @@ public class FontCustomizationParser { FontListParser.skip(parser); } } - return validateAndTransformToResult(families, aliases); + return validateAndTransformToResult(families, outLocaleFamilies, aliases); } private static void readFamily( @NonNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, + @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization, @Nullable Map<String, File> updatableFontMap) throws XmlPullParserException, IOException { final String customizationType = parser.getAttributeValue(null, "customizationType"); @@ -148,6 +163,29 @@ public class FontCustomizationParser { if (fontFamily != null) { out.add(fontFamily); } + } else if (customizationType.equals("new-locale-family")) { + final String lang = parser.getAttributeValue(null, "lang"); + final String op = parser.getAttributeValue(null, "operation"); + final int intOp; + if (op.equals("append")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND; + } else if (op.equals("prepend")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; + } else if (op.equals("replace")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; + } else { + throw new IllegalArgumentException("Unknown operation=" + op); + } + + final FontConfig.FontFamily family = FontListParser.readFamily( + parser, fontDir, updatableFontMap, false); + + // For ignoring the customization, consume the new-locale-family element but don't + // register any customizations. + if (com.android.text.flags.Flags.customLocaleFallback()) { + outCustomization.add(new FontConfig.Customization.LocaleFallback( + Locale.forLanguageTag(lang), intOp, family)); + } } else { throw new IllegalArgumentException("Unknown customizationType=" + customizationType); } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index d4e35b30c8d0..618aa5b5019c 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -16,10 +16,15 @@ package android.graphics.fonts; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_APPEND; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.FontListParser; import android.graphics.Typeface; +import android.os.LocaleList; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Log; @@ -38,6 +43,7 @@ import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -119,7 +125,6 @@ public final class SystemFonts { } } - final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false, cache); @@ -300,11 +305,11 @@ public final class SystemFonts { } catch (IOException e) { Log.e(TAG, "Failed to open/read system font configurations.", e); return new FontConfig(Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), 0, 0); + Collections.emptyList(), Collections.emptyList(), 0, 0); } catch (XmlPullParserException e) { Log.e(TAG, "Failed to parse the system font configuration.", e); return new FontConfig(Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), 0, 0); + Collections.emptyList(), Collections.emptyList(), 0, 0); } } @@ -328,6 +333,8 @@ public final class SystemFonts { ArrayMap<String, ByteBuffer> outBufferCache) { final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>(); + final List<FontConfig.Customization.LocaleFallback> localeFallbacks = + fontConfig.getLocaleFallbackCustomizations(); final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists(); for (int i = 0; i < namedFamilies.size(); ++i) { @@ -336,10 +343,54 @@ public final class SystemFonts { } // Then, add fallback fonts to the fallback map. + final List<FontConfig.Customization.LocaleFallback> customizations = new ArrayList<>(); final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies(); + final SparseIntArray seenCustomization = new SparseIntArray(); for (int i = 0; i < xmlFamilies.size(); i++) { final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i); - pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + + customizations.clear(); + for (int j = 0; j < localeFallbacks.size(); ++j) { + if (seenCustomization.get(j, -1) != -1) { + continue; // The customization is already applied. + } + FontConfig.Customization.LocaleFallback localeFallback = localeFallbacks.get(j); + if (scriptMatch(xmlFamily.getLocaleList(), localeFallback.getScript())) { + customizations.add(localeFallback); + seenCustomization.put(j, 1); + } + } + + if (customizations.isEmpty()) { + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + } else { + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_PREPEND) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + } + } + boolean isReplaced = false; + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_REPLACE) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + isReplaced = true; + } + } + if (!isReplaced) { // If nothing is replaced, push the original one. + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + } + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_APPEND) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + } + } + } } // Build the font map and fallback map. @@ -365,4 +416,42 @@ public final class SystemFonts { Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result); return result; } + + private static boolean scriptMatch(LocaleList localeList, String targetScript) { + if (localeList == null || localeList.isEmpty()) { + return false; + } + for (int i = 0; i < localeList.size(); ++i) { + Locale locale = localeList.get(i); + if (locale == null) { + continue; + } + String baseScript = FontConfig.resolveScript(locale); + if (baseScript.equals(targetScript)) { + return true; + } + + // Subtag match + if (targetScript.equals("Bopo") && baseScript.equals("Hanb")) { + // Hanb is Han with Bopomofo. + return true; + } else if (targetScript.equals("Hani")) { + if (baseScript.equals("Hanb") || baseScript.equals("Hans") + || baseScript.equals("Hant") || baseScript.equals("Kore") + || baseScript.equals("Jpan")) { + // Han id suppoted by Taiwanese, Traditional Chinese, Simplified Chinese, Korean + // and Japanese. + return true; + } + } else if (targetScript.equals("Hira") || targetScript.equals("Hrkt") + || targetScript.equals("Kana")) { + if (baseScript.equals("Jpan") || baseScript.equals("Hrkt")) { + // Hiragana, Hiragana-Katakana, Katakana is supported by Japanese and + // Hiragana-Katakana script. + return true; + } + } + } + return false; + } } diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index e81525fb7d60..f5e5803d4796 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -134,10 +134,25 @@ public final class LineBreakConfig { */ public static final int LINE_BREAK_STYLE_STRICT = 3; + /** + * The line break style that used for preventing automatic line breaking. + * + * This is useful when you want to preserve some words in the same line by using + * {@link android.text.style.LineBreakConfigSpan} or + * {@link android.text.style.LineBreakConfigSpan.NoBreakSpan} as a shorthand. + * Note that even if this style is specified, the grapheme based line break is still performed + * for preventing clipping text. + * + * @see android.text.style.LineBreakConfigSpan + * @see android.text.style.LineBreakConfigSpan.NoBreakSpan + */ + @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) + public static final int LINE_BREAK_STYLE_NO_BREAK = 4; + /** @hide */ @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = { LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL, - LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED + LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK }) @Retention(RetentionPolicy.SOURCE) public @interface LineBreakStyle {} diff --git a/keystore/aaid/aidl/Android.bp b/keystore/aaid/aidl/Android.bp new file mode 100644 index 000000000000..97acfb4ea4c3 --- /dev/null +++ b/keystore/aaid/aidl/Android.bp @@ -0,0 +1,31 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +aidl_interface { + name: "android.security.aaid_aidl", + srcs: ["android/security/keystore/*.aidl"], + unstable: true, + backend: { + rust: { + enabled: true, + }, + cpp: { + enabled: true, + }, + }, +} diff --git a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl index dbffd5f57ce2..c360cb8f281a 100644 --- a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl +++ b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl @@ -1,5 +1,5 @@ /** - * Copyright (c) 2016, The Android Open Source Project + * 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. @@ -14,19 +14,15 @@ * limitations under the License. */ -package android.security.keymaster; +package android.security.keystore; -import android.security.keymaster.KeyAttestationApplicationId; -import android.security.keymaster.KeyAttestationPackageInfo; -import android.content.pm.Signature; +import android.security.keystore.KeyAttestationApplicationId; -/** - * This must be kept manually in sync with system/security/keystore until AIDL - * can generate both Java and C++ bindings. - * - * @hide - */ +/** @hide */ interface IKeyAttestationApplicationIdProvider { - /* keep in sync with /system/security/keystore/keystore_attestation_id.cpp */ + /** + * Provides information describing the possible applications identified by a UID. + * @hide + */ KeyAttestationApplicationId getKeyAttestationApplicationId(int uid); } diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl new file mode 100644 index 000000000000..c33e8309b2f2 --- /dev/null +++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl @@ -0,0 +1,31 @@ +/* + * 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 android.security.keystore; + +import android.security.keystore.KeyAttestationPackageInfo; + +/** + * @hide + * The information aggregated by this parcelable is used by keystore to identify a caller of the + * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore + * can only determine a caller by uid granularity, and a uid can be shared by multiple packages. + * The remote party must decide if it trusts all of the packages enough to consider the + * confidentiality of the key material in question intact. + */ +parcelable KeyAttestationApplicationId { + KeyAttestationPackageInfo[] packageInfos; +} diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl new file mode 100644 index 000000000000..5f647d0b1abe --- /dev/null +++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl @@ -0,0 +1,33 @@ +/* + * 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 android.security.keystore; + +import android.security.keystore.Signature; + +/** + * @hide + * This parcelable constitutes and excerpt from the PackageManager's PackageInfo for the purpose of + * key attestation. It is part of the KeyAttestationApplicationId, which is used by + * keystore to identify the caller of the keystore API towards a remote party. + */ +parcelable KeyAttestationPackageInfo { + String packageName; + + long versionCode; + + Signature[] signatures; +} diff --git a/keystore/aaid/aidl/android/security/keystore/Signature.aidl b/keystore/aaid/aidl/android/security/keystore/Signature.aidl new file mode 100644 index 000000000000..800499a13355 --- /dev/null +++ b/keystore/aaid/aidl/android/security/keystore/Signature.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +/** + * @hide + * Represents a signature data read from the package file. Extracted from from the PackageManager's + * PackageInfo for the purpose of key attestation. It is part of the KeyAttestationPackageInfo, + * which is used by keystore to identify the caller of the keystore API towards a remote party. + */ +parcelable Signature { + /** + * Represents signing certificate data associated with application package, signatures are + * expected to be a hex-encoded ASCII string representing valid X509 certificate. + */ + byte[] data; +} diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 0f3488bbe8d1..31c2eb2efaed 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -28,8 +28,8 @@ import android.system.keystore2.ResponseCode; import android.util.Log; /** - * @hide This is the client side for IKeystoreUserManager AIDL. - * It shall only be used by the LockSettingsService. + * @hide This is the client side for IKeystoreMaintenance AIDL. + * It is used mainly by LockSettingsService. */ public class AndroidKeyStoreMaintenance { private static final String TAG = "AndroidKeyStoreMaintenance"; @@ -66,7 +66,7 @@ public class AndroidKeyStoreMaintenance { } /** - * Informs Keystore 2.0 about removing a usergit mer + * Informs Keystore 2.0 about removing a user * * @param userId - Android user id of the user being removed * @return 0 if successful or a {@code ResponseCode} @@ -91,7 +91,7 @@ public class AndroidKeyStoreMaintenance { * * @param userId - Android user id of the user * @param password - a secret derived from the synthetic password provided by the - * LockSettingService + * LockSettingsService * @return 0 if successful or a {@code ResponseCode} * @hide */ @@ -110,7 +110,7 @@ public class AndroidKeyStoreMaintenance { } /** - * Informs Keystore 2.0 that an app was uninstalled and the corresponding namspace is to + * Informs Keystore 2.0 that an app was uninstalled and the corresponding namespace is to * be cleared. */ public static int clearNamespace(@Domain int domain, long namespace) { @@ -172,10 +172,10 @@ public class AndroidKeyStoreMaintenance { * namespace. * * @return * 0 on success - * * KEY_NOT_FOUND if the source did not exists. + * * KEY_NOT_FOUND if the source did not exist. * * PERMISSION_DENIED if any of the required permissions was missing. * * INVALID_ARGUMENT if the destination was occupied or any domain value other than - * the allowed once were specified. + * the allowed ones was specified. * * SYSTEM_ERROR if an unexpected error occurred. */ public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) { diff --git a/packages/SystemUI/ktfmt_includes.txt b/ktfmt_includes.txt index d3254b7914e2..e4bf4c26dc7d 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/ktfmt_includes.txt @@ -1,3 +1,4 @@ ++services/permission +packages/SystemUI -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index d55a41fc0cf7..7c0d0e37e28c 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -5,4 +5,18 @@ flag { namespace: "multitasking" description: "An Example Flag" bug: "300136750" -}
\ No newline at end of file +} + +flag { + name: "enable_app_pairs" + namespace: "multitasking" + description: "Enables the ability to create and save app pairs to the Home screen" + bug: "274835596" +} + +flag { + name: "desktop_windowing" + namespace: "multitasking" + description: "Enables desktop windowing" + bug: "304778354" +} diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 97a9d4874455..74364001b6c5 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -58,6 +58,9 @@ if a custom action is present before closing it. --> <integer name="config_pipForceCloseDelay">1000</integer> + <!-- Allow PIP to resize via pinch gesture. --> + <bool name="config_pipEnablePinchResize">true</bool> + <!-- Animation duration when using long press on recents to dock --> <integer name="long_press_dock_anim_duration">250</integer> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 7a309f5758a0..1f6f7aeadd45 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -143,7 +143,9 @@ <dimen name="bubble_expanded_view_padding">16dp</dimen> <!-- Padding for the edge of the expanded view that is closest to the edge of the screen used when displaying in landscape on a large screen. --> - <dimen name="bubble_expanded_view_largescreen_landscape_padding">128dp</dimen> + <dimen name="bubble_expanded_view_largescreen_landscape_padding">102dp</dimen> + <!-- The width of the expanded view on large screens. --> + <dimen name="bubble_expanded_view_largescreen_width">540dp</dimen> <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include a slight touch slop around the expanded view. --> <dimen name="bubble_expanded_view_slop">8dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java index 34bf9e0dd98f..2e5448a9e8d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java @@ -18,6 +18,7 @@ package com.android.wm.shell; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; +import android.annotation.SuppressLint; import android.app.WindowConfiguration; import android.util.SparseArray; import android.view.SurfaceControl; @@ -29,6 +30,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; import java.util.List; @@ -44,9 +46,14 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer { /** Display area leashes, which is mapped by display IDs. */ private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>(); - public RootDisplayAreaOrganizer(Executor executor) { + public RootDisplayAreaOrganizer(@NonNull Executor executor, @NonNull ShellInit shellInit) { super(executor); - List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT); + shellInit.addInitCallback(this::onInit, this); + } + + @SuppressLint("MissingPermission") // Only called by SysUI. + private void onInit() { + final List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT); for (int i = infos.size() - 1; i >= 0; --i) { onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 38550b405c0e..ab61a48a715c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -18,6 +18,7 @@ package com.android.wm.shell; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; +import android.annotation.SuppressLint; import android.annotation.UiContext; import android.app.ResourcesManager; import android.content.Context; @@ -38,6 +39,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; import java.util.ArrayList; @@ -69,10 +71,17 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { private final Context mContext; - public RootTaskDisplayAreaOrganizer(Executor executor, Context context) { + public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context, + @NonNull ShellInit shellInit) { super(executor); mContext = context; - List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER); + shellInit.addInitCallback(this::onInit, this); + } + + @SuppressLint("MissingPermission") // Only called by SysUI. + private void onInit() { + final List<DisplayAreaAppearedInfo> infos = + registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER); for (int i = infos.size() - 1; i >= 0; --i) { onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index ea7053d8ee49..17e06e93b3a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -54,10 +54,6 @@ public class BubblePositioner { public static final float FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN = 0.3f; /** The max percent of screen width to use for the flyout on phone. */ public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f; - /** The percent of screen width for the expanded view on a large screen. **/ - private static final float EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT = 0.48f; - /** The percent of screen width for the expanded view on a large screen. **/ - private static final float EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT = 0.70f; /** The percent of screen width for the expanded view on a small tablet. **/ private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f; /** The percent of screen width for the expanded view when shown in the bubble bar. **/ @@ -95,6 +91,7 @@ public class BubblePositioner { private int mPointerWidth; private int mPointerHeight; private int mPointerOverlap; + private int mManageButtonHeightIncludingMargins; private int mManageButtonHeight; private int mOverflowHeight; private int mMinimumFlyoutWidthLargeScreen; @@ -176,21 +173,20 @@ public class BubblePositioner { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); } else { - mExpandedViewLargeScreenWidth = isLandscape() - ? (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT) - : (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT); + mExpandedViewLargeScreenWidth = + res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width); } if (mIsLargeScreen) { - if (isLandscape() && !mIsSmallTablet) { + if (mIsSmallTablet) { + final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2; + mExpandedViewLargeScreenInsetClosestEdge = centeredInset; + mExpandedViewLargeScreenInsetFurthestEdge = centeredInset; + } else { mExpandedViewLargeScreenInsetClosestEdge = res.getDimensionPixelSize( R.dimen.bubble_expanded_view_largescreen_landscape_padding); mExpandedViewLargeScreenInsetFurthestEdge = bounds.width() - mExpandedViewLargeScreenInsetClosestEdge - mExpandedViewLargeScreenWidth; - } else { - final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2; - mExpandedViewLargeScreenInsetClosestEdge = centeredInset; - mExpandedViewLargeScreenInsetFurthestEdge = centeredInset; } } else { mExpandedViewLargeScreenInsetClosestEdge = mExpandedViewPadding; @@ -202,7 +198,9 @@ public class BubblePositioner { mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin); mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap); - mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height); + mManageButtonHeightIncludingMargins = + res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height); + mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height); mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height); mMinimumFlyoutWidthLargeScreen = res.getDimensionPixelSize( @@ -420,7 +418,7 @@ public class BubblePositioner { int pointerSize = showBubblesVertically() ? mPointerWidth : (mPointerHeight + mPointerMargin); - int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeight; + int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins; return getAvailableRect().height() - expandedContainerY - paddingTop @@ -438,6 +436,15 @@ public class BubblePositioner { // overflow in landscape on phone is max return MAX_HEIGHT; } + + if (mIsLargeScreen && !mIsSmallTablet && !isOverflow) { + // the expanded view height on large tablets is calculated based on the shortest screen + // size and is the same in both portrait and landscape + int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom); + int shortestScreenSide = Math.min(mScreenRect.height(), mScreenRect.width()); + return shortestScreenSide - 2 * maxVerticalInset - mManageButtonHeight; + } + float desiredHeight = isOverflow ? mOverflowHeight : ((Bubble) bubble).getDesiredHeight(mContext); @@ -466,7 +473,8 @@ public class BubblePositioner { return topAlignment; } // If we're here, we're showing vertically & developer has made height less than maximum. - int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeight; + int manageButtonHeight = + isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins; float pointerPosition = getPointerPosition(bubblePosition); float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight; float topIfCentered = pointerPosition - (expandedViewHeight / 2); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS index 7af038999797..6519eee18e69 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS @@ -1 +1,2 @@ madym@google.com +hwwang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java index 931cf0cee28c..c6c9b3562308 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java @@ -94,6 +94,10 @@ public class TvWindowMenuActionButton extends RelativeLayout { mCurrentIcon = icon; // Remove old image while waiting for the new one to load. mIconImageView.setImageDrawable(null); + if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + // Disallow loading icon from content URI + return; + } icon.loadDrawableAsync(mContext, d -> { // The image hasn't been set any other way and the drawable belongs to the most // recently set Icon. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 3b32b6c7b083..d520ff791e07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -126,12 +126,22 @@ public class PipBoundsState { private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); + // the size of the current bounds relative to the max size spec + private float mBoundsScale; + public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource, @NonNull PipDisplayLayoutState pipDisplayLayoutState) { mContext = context; reloadResources(); mSizeSpecSource = sizeSpecSource; mPipDisplayLayoutState = pipDisplayLayoutState; + + // Update the relative proportion of the bounds compared to max possible size. Max size + // spec takes the aspect ratio of the bounds into account, so both width and height + // scale by the same factor. + addPipExclusionBoundsChangeCallback((bounds) -> { + mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f); + }); } /** Reloads the resources. */ @@ -160,6 +170,15 @@ public class PipBoundsState { return new Rect(mBounds); } + /** + * Get the scale of the current bounds relative to the maximum size possible. + * + * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}. + */ + public float getBoundsScale() { + return mBoundsScale; + } + /** Returns the current movement bounds. */ @NonNull public Rect getMovementBounds() { @@ -622,6 +641,9 @@ public class PipBoundsState { pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight); pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip); pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip); + pw.println(innerPrefix + "mMinSize=" + mMinSize); + pw.println(innerPrefix + "mMaxSize=" + mMaxSize); + pw.println(innerPrefix + "mBoundsScale" + mBoundsScale); if (mPipReentryState == null) { pw.println(innerPrefix + "mPipReentryState=null"); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index e6d3abcc6e1d..c51af46accdb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -110,13 +110,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.windowdecor.WindowDecorViewModel; -import java.util.Optional; - import dagger.BindsOptionalOf; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only * accessible from components within the WM subcomponent (can be explicitly exposed to the @@ -658,15 +658,15 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer( - @ShellMainThread ShellExecutor mainExecutor, Context context) { - return new RootTaskDisplayAreaOrganizer(mainExecutor, context); + @ShellMainThread ShellExecutor mainExecutor, Context context, ShellInit shellInit) { + return new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit); } @WMSingleton @Provides static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer( - @ShellMainThread ShellExecutor mainExecutor) { - return new RootDisplayAreaOrganizer(mainExecutor); + @ShellMainThread ShellExecutor mainExecutor, ShellInit shellInit) { + return new RootDisplayAreaOrganizer(mainExecutor, shellInit); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 5dfba5e7ff1d..14a040a40874 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -203,6 +203,7 @@ public abstract class WMShellModule { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -218,6 +219,7 @@ public abstract class WMShellModule { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 106486714a5c..63f20fd8e997 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -797,21 +797,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm.getMovementBounds(postChangeBounds), mPipBoundsState.getStashedState()); - // Scale PiP on density dpi change, so it appears to be the same size physically. - final boolean densityDpiChanged = - mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 - && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() - != layout.densityDpi()); - if (densityDpiChanged) { - final float scale = (float) layout.densityDpi() - / mPipDisplayLayoutState.getDisplayLayout().densityDpi(); - postChangeBounds.set(0, 0, - (int) (postChangeBounds.width() * scale), - (int) (postChangeBounds.height() * scale)); - } - updateDisplayLayout.run(); + // Resize the PiP bounds to be at the same scale relative to the new size spec. For + // example, if PiP was resized to 90% of the maximum size on the previous layout, + // make sure it is 90% of the new maximum size spec. + postChangeBounds.set(0, 0, + (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()), + (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale())); + // Calculate the PiP bounds in the new orientation based on same fraction along the // rotated movement bounds. final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds( @@ -822,6 +816,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipDisplayLayoutState.getDisplayBounds(), mPipDisplayLayoutState.getDisplayLayout().stableInsets()); + // make sure we user resize to the updated bounds to avoid animating to any outdated + // sizes from the previous layout upon double tap CUJ + mPipBoundsState.setHasUserResizedPip(true); + mTouchHandler.setUserResizeBounds(postChangeBounds); + + final boolean densityDpiChanged = + mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 + && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() + != layout.densityDpi()); if (densityDpiChanged) { // Using PipMotionHelper#movePip directly here may cause race condition since // the app content in PiP mode may or may not be updated for the new density dpi. @@ -833,15 +836,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Directly move PiP to its final destination bounds without animation. mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds); } - - // if the pip window size is beyond allowed bounds user resize to normal bounds - if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x - || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x - || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y - || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) { - mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction); - } - } else { updateDisplayLayout.run(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index e5f9fdc7a740..f175775ce8b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -15,7 +15,6 @@ */ package com.android.wm.shell.pip.phone; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; @@ -31,7 +30,6 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; import android.os.Looper; -import android.provider.DeviceConfig; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; import android.view.InputChannel; @@ -155,21 +153,8 @@ public class PipResizeGestureHandler { mContext.getDisplay().getRealSize(mMaxSize); reloadResources(); - mEnablePinchResize = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - PIP_PINCH_RESIZE, - /* defaultValue = */ true); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mMainExecutor, - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) { - mEnablePinchResize = properties.getBoolean( - PIP_PINCH_RESIZE, /* defaultValue = */ true); - } - } - }); + final Resources res = mContext.getResources(); + mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize); } public void onConfigurationChanged() { @@ -579,6 +564,12 @@ public class PipResizeGestureHandler { resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); } + // If user resize is smaller than min size, auto resize to min + if (mLastResizeBounds.width() < mMinSize.x + || mLastResizeBounds.height() < mMinSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); + } + // get the current movement bounds final Rect movementBounds = mPipBoundsAlgorithm .getMovementBounds(mLastResizeBounds); @@ -679,6 +670,8 @@ public class PipResizeGestureHandler { pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize); pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed); pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset); + pw.println(innerPrefix + "mMinSize=" + mMinSize); + pw.println(innerPrefix + "mMaxSize=" + mMaxSize); } class PipResizeInputEventReceiver extends BatchedInputEventReceiver { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 2ce4fb9e297b..452a41696fcf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -779,13 +779,10 @@ public class PipTouchHandler { } /** - * Resizes the pip window and updates user resized bounds - * - * @param bounds target bounds to resize to - * @param snapFraction snap fraction to apply after resizing + * Sets the user resize bounds tracked by {@link PipResizeGestureHandler} */ - void userResizeTo(Rect bounds, float snapFraction) { - mPipResizeGestureHandler.userResizeTo(bounds, snapFraction); + void setUserResizeBounds(Rect bounds) { + mPipResizeGestureHandler.setUserResizeBounds(bounds); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 5e2c61b9d3cd..68ca2313f709 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -143,6 +143,8 @@ import com.android.wm.shell.util.SplitBounds; import com.android.wm.shell.util.TransitionUtil; import com.android.wm.shell.windowdecor.WindowDecorViewModel; +import dalvik.annotation.optimization.NeverCompile; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -2240,6 +2242,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } + /** + * Returns the {@link StageType} where {@param token} is being used + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise + */ + @StageType + public int getSplitItemStage(@Nullable WindowContainerToken token) { + if (token == null) { + return STAGE_TYPE_UNDEFINED; + } + + if (mMainStage.containsToken(token)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsToken(token)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + @Override public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final StageTaskListener topLeftStage = @@ -2477,7 +2498,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent( recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + @StageType int topStage = STAGE_TYPE_UNDEFINED; + if (isSplitScreenVisible()) { + // Get the stage where a child exists to keep that stage onTop + if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) { + topStage = STAGE_TYPE_MAIN; + } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) { + topStage = STAGE_TYPE_SIDE; + } + } + prepareExitSplitScreen(topStage, outWCT); } } @@ -2691,7 +2721,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean shouldAnimate = true; if (mSplitTransitions.isPendingEnter(transition)) { - shouldAnimate = startPendingEnterAnimation( + shouldAnimate = startPendingEnterAnimation(transition, mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; @@ -2730,7 +2760,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - private boolean startPendingEnterAnimation( + private boolean startPendingEnterAnimation(@NonNull IBinder transition, @NonNull SplitScreenTransitions.EnterSession enterTransition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { @@ -2759,21 +2789,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - if (mSplitTransitions.mPendingEnter.mExtraTransitType + SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter; + if (pendingEnter.mExtraTransitType == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { // Open to side should only be used when split already active and foregorund. if (mainChild == null && sideChild == null) { Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", "Launched a task in split, but didn't receive any task in transition.")); // This should happen when the target app is already on front, so just cancel. - mSplitTransitions.mPendingEnter.cancel(null); + pendingEnter.cancel(null); return true; } } else { if (mainChild == null || sideChild == null) { final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); - mSplitTransitions.mPendingEnter.cancel( + pendingEnter.cancel( (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", "launched 2 tasks in split, but didn't receive " @@ -2784,6 +2815,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mRecentTasks.isPresent() && sideChild != null) { mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId); } + if (pendingEnter.mRemoteHandler != null) { + // Pass false for aborted since WM didn't abort, business logic chose to + // terminate/exit early + pendingEnter.mRemoteHandler.onTransitionConsumed(transition, + false /*aborted*/, finishT); + } mSplitUnsupportedToast.show(); return true; } @@ -2894,7 +2931,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - /** Synchronize split-screen state with transition and make appropriate preparations. */ + /** + * Synchronize split-screen state with transition and make appropriate preparations. + * @param toStage The stage that will not be dismissed. If set to + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed + */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { @@ -3109,6 +3150,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER); } + @NeverCompile @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index e828eedc275c..451e61855943 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -50,6 +50,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -344,6 +345,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // Keyguard handler cannot handle it, process through original mixed mActiveTransitions.remove(keyguardMixed); } + } else if (mPipHandler != null) { + mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); } } @@ -511,8 +514,26 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // make a new startTransaction because pip's startEnterAnimation "consumes" it so // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (mSplitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + mSplitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } // Let split update internal state for dismiss. - mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + mSplitHandler.prepareDismissAnimation(topStageToKeep, EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, finishTransaction); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index fab2dd2bf3e1..8b050e524038 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -152,6 +152,16 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { } @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishTransaction) { + try { + mRemote.getRemoteTransition().onTransitionConsumed(transition, aborted); + } catch (RemoteException e) { + Log.e(Transitions.TAG, "Error calling onTransitionConsumed()", e); + } + } + + @Override public String toString() { return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":" + mRemote.getRemoteTransition(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index a90edf20f94e..592b22a47bc4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -86,7 +86,16 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT) { - mRequestedRemotes.remove(transition); + RemoteTransition remoteTransition = mRequestedRemotes.remove(transition); + if (remoteTransition == null) { + return; + } + + try { + remoteTransition.getRemoteTransition().onTransitionConsumed(transition, aborted); + } catch (RemoteException e) { + Log.e(TAG, "Error delegating onTransitionConsumed()", e); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java index 87c438a5b37d..ba0ef20c412e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java @@ -19,13 +19,12 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import java.util.ArrayList; - /** * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these * as sentinels for fast-forwarding through animations when the screen is off. @@ -34,30 +33,25 @@ import java.util.ArrayList; * don't register it like a normal handler. */ class SleepHandler implements Transitions.TransitionHandler { - final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - mSleepTransitions.remove(transition); - startTransaction.apply(); - finishCallback.onTransitionFinished(null); - return true; + if (info.hasChangesOrSideEffects()) { + Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition"); + return false; + } else { + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + return true; + } } @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - mSleepTransitions.add(transition); return new WindowContainerTransaction(); } - - @Override - public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, - @Nullable SurfaceControl.Transaction finishTransaction) { - mSleepTransitions.remove(transition); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index c74b3f30e52d..0d9a9e9f07ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -277,7 +277,7 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellExecutor animExecutor) { this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor, mainHandler, animExecutor, null, - new RootTaskDisplayAreaOrganizer(mainExecutor, context)); + new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit)); } public Transitions(@NonNull Context context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 82fc0f49c143..aff35a347183 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -231,4 +231,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL int getCaptionHeightId(@WindowingMode int windowingMode) { return R.dimen.freeform_decor_caption_height; } + + @Override + int getCaptionViewId() { + return R.id.caption; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index bf99ab35cdd7..ca91d580b4ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.WindowInsets.Type.statusBars; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -50,6 +51,8 @@ import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -67,6 +70,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -131,6 +135,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener = new DesktopModeKeyguardChangeListener(); private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final DisplayInsetsController mDisplayInsetsController; + private boolean mInImmersiveMode; public DesktopModeWindowDecorViewModel( Context context, @@ -141,6 +147,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -156,6 +163,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, @@ -176,6 +184,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -191,6 +200,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskOrganizer = taskOrganizer; mShellController = shellController; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mSyncQueue = syncQueue; mTransitions = transitions; mDesktopTasksController = desktopTasksController; @@ -213,6 +223,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } }); mShellCommandHandler.addDumpCallback(this::dump, this); + mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(), + new DesktopModeOnInsetsChangedListener()); } @Override @@ -655,9 +667,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev); if (DesktopModeStatus.isEnabled()) { - if (relevantDecor == null + if (!mInImmersiveMode && (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM - || mTransitionDragActive) { + || mTransitionDragActive)) { handleCaptionThroughStatusBar(ev, relevantDecor); } } @@ -1051,6 +1063,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return mIsKeyguardVisible && mIsKeyguardOccluded; } } + + @VisibleForTesting + class DesktopModeOnInsetsChangedListener implements + DisplayInsetsController.OnInsetsChangedListener { + @Override + public void insetsChanged(InsetsState insetsState) { + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + final DesktopModeWindowDecoration decor = getFocusedDecor(); + if (decor == null) { + return; + } + // If status bar inset is visible, top task is not in immersive mode + final boolean inImmersiveMode = !source.isVisible(); + // Calls WindowDecoration#relayout if decoration visibility needs to be updated + if (inImmersiveMode != mInImmersiveMode) { + decor.relayout(decor.mTaskInfo); + mInImmersiveMode = inImmersiveMode; + } + + return; + } + } + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 380b59e84485..248e83747c48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -638,6 +638,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode)); } + @Override + int getCaptionViewId() { + return R.id.desktop_mode_caption; + } + /** * Add transition to mTransitionsPausingRelayout */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index 09fc3dacf6f3..368231e2d7f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -132,6 +132,13 @@ public class ResizeVeil { t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction()); t.apply(); }); + mVeilAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + t.setAlpha(mVeilSurface, 1); + t.apply(); + } + }); final ValueAnimator iconAnimator = new ValueAnimator(); iconAnimator.setFloatValues(0f, 1f); @@ -192,8 +199,8 @@ public class ResizeVeil { */ public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { if (mVeilAnimator != null && mVeilAnimator.isStarted()) { - // TODO(b/300145351): Investigate why ValueAnimator#end does not work here. - mVeilAnimator.setCurrentPlayTime(RESIZE_ALPHA_DURATION); + mVeilAnimator.removeAllUpdateListeners(); + mVeilAnimator.end(); } relayout(newBounds, t); mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 6062e34cd601..0548a8e751cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.statusBars; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration.WindowingMode; @@ -30,6 +31,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.view.Display; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; @@ -44,6 +47,7 @@ import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.desktopmode.DesktopModeStatus; import java.util.function.Supplier; @@ -118,6 +122,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private WindowlessWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; private Configuration mWindowDecorConfig; + private boolean mIsCaptionVisible; private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); @@ -224,6 +229,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .inflate(params.mLayoutResId, null); } + updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId); + final Resources resources = mDecorWindowContext.getResources(); final Configuration taskConfig = mTaskInfo.getConfiguration(); final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); @@ -271,22 +278,26 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Caption insets mCaptionInsetsRect.set(taskBounds); - mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY; - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), - mCaptionInsetsRect); + if (mIsCaptionVisible) { + mCaptionInsetsRect.bottom = + mCaptionInsetsRect.top + captionHeight + params.mCaptionY; + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), + mCaptionInsetsRect); + } else { + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.captionBar()); + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.mandatorySystemGestures()); + } } else { startT.hide(mCaptionContainerSurface); } // Task surface itself float shadowRadius = loadDimension(resources, params.mShadowRadiusId); - int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); - mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; - mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; - mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; final Point taskPosition = mTaskInfo.positionInParent; if (isFullscreen) { // Setting the task crop to the width/height stops input events from being sent to @@ -302,13 +313,22 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); } startT.setShadowRadius(mTaskSurface, shadowRadius) - .setColor(mTaskSurface, mTmpColor) .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) .setShadowRadius(mTaskSurface, shadowRadius); if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + if (!DesktopModeStatus.isVeiledResizeEnabled()) { + // When fluid resize is enabled, add a background to freeform tasks + int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); + mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; + mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; + mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; + startT.setColor(mTaskSurface, mTmpColor); + } startT.setCornerRadius(mTaskSurface, params.mCornerRadius); finishT.setCornerRadius(mTaskSurface, params.mCornerRadius); + } else if (!DesktopModeStatus.isVeiledResizeEnabled()) { + startT.unsetColor(mTaskSurface); } if (mCaptionWindowManager == null) { @@ -342,10 +362,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } + /** + * Checks if task has entered/exited immersive mode and requires a change in caption visibility. + */ + private void updateCaptionVisibility(View rootView, int displayId) { + final InsetsState insetsState = mDisplayController.getInsetsState(displayId); + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + mIsCaptionVisible = source.isVisible(); + setCaptionVisibility(rootView, mIsCaptionVisible); + + return; + } + } + + private void setCaptionVisibility(View rootView, boolean visible) { + if (rootView == null) { + return; + } + final int v = visible ? View.VISIBLE : View.GONE; + final View captionView = rootView.findViewById(getCaptionViewId()); + captionView.setVisibility(v); + } + int getCaptionHeightId(@WindowingMode int windowingMode) { return Resources.ID_NULL; } + int getCaptionViewId() { + return Resources.ID_NULL; + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -460,7 +511,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> */ public void addCaptionInset(WindowContainerTransaction wct) { final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode()); - if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) { + if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL + || !mIsCaptionVisible) { return; } diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 0058d115ce56..7f020725d61f 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -116,6 +116,7 @@ java_library { "wm-flicker-common-assertions", "launcher-helper-lib", "launcher-aosp-tapl", + "com_android_wm_shell_flags_lib", ], } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml index b13e9a248575..1df11369a049 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -81,9 +81,7 @@ <option name="shell-timeout" value="6600s"/> <option name="test-timeout" value="6000s"/> <option name="hidden-api-checks" value="false"/> - <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> - --> <!-- PerfettoListener related arguments --> <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> <option name="instrumentation-arg" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 6748626d4e46..0fd1b2c3f0de 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.subject.exceptions.IncorrectRegionException import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest @@ -40,14 +41,26 @@ class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } - /** Checks that the visible region area of [pipApp] always decreases during the animation. */ + /** + * Checks that the visible region area of [pipApp] decreases + * and then increases during the animation. + */ @Presubmit @Test - fun pipLayerAreaDecreases() { + fun pipLayerAreaDecreasesThenIncreases() { + val isAreaDecreasing = arrayOf(true) flicker.assertLayers { val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> - current.visibleRegion.notBiggerThan(previous.visibleRegion.region) + if (isAreaDecreasing[0]) { + try { + current.visibleRegion.notBiggerThan(previous.visibleRegion.region) + } catch (e: IncorrectRegionException) { + isAreaDecreasing[0] = false + } + } else { + previous.visibleRegion.notBiggerThan(current.visibleRegion.region) + } } } } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index d09a90cd7dc7..aadadd604d3e 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -35,6 +35,7 @@ android_test { static_libs: [ "WindowManager-Shell", "junit", + "flag-junit-base", "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", @@ -49,6 +50,7 @@ android_test { "testables", "platform-test-annotations", "servicestests-utils", + "com_android_wm_shell_flags_lib", ], libs: [ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java index 58d9a6486ff2..287a97c9b5b0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -22,20 +22,24 @@ import static android.view.View.LAYOUT_DIRECTION_LTR; import static android.view.View.LAYOUT_DIRECTION_RTL; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.content.Intent; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; +import android.util.DisplayMetrics; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; @@ -257,6 +261,27 @@ public class BubblePositionerTest extends ShellTestCase { assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue(); } + @Test + public void testExpandedViewHeight_onLargeTablet() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1800, 2600); + + new WindowManagerConfig() + .setLargeScreen() + .setInsets(insets) + .setScreenBounds(screenBounds) + .setUpConfig(); + mPositioner.update(); + + Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); + Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); + + int manageButtonHeight = + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height); + float expectedHeight = 1800 - 2 * 20 - manageButtonHeight; + assertThat(mPositioner.getExpandedViewHeight(bubble)).isWithin(0.1f).of(expectedHeight); + } + /** * Calculates the Y position bubbles should be placed based on the config. Based on * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and @@ -323,6 +348,8 @@ public class BubblePositionerTest extends ShellTestCase { ? MIN_WIDTH_FOR_TABLET : MIN_WIDTH_FOR_TABLET - 1; mConfiguration.orientation = mOrientation; + mConfiguration.screenWidthDp = pxToDp(mScreenBounds.width()); + mConfiguration.screenHeightDp = pxToDp(mScreenBounds.height()); when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection); WindowInsets windowInsets = mock(WindowInsets.class); @@ -331,5 +358,11 @@ public class BubblePositionerTest extends ShellTestCase { when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds); when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); } + + private int pxToDp(float px) { + int dpi = mContext.getResources().getDisplayMetrics().densityDpi; + float dp = px / ((float) dpi / DisplayMetrics.DENSITY_DEFAULT); + return (int) dp; + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index d34e27b57071..db98abbbcbf1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.ComponentName; +import android.graphics.Point; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -60,6 +61,9 @@ public class PipBoundsStateTest extends ShellTestCase { /** The minimum possible size of the override min size's width or height */ private static final int OVERRIDABLE_MIN_SIZE = 40; + /** The margin of error for floating point results. */ + private static final float MARGIN_OF_ERROR = 0.05f; + private PipBoundsState mPipBoundsState; private SizeSpecSource mSizeSpecSource; private ComponentName mTestComponentName1; @@ -88,6 +92,27 @@ public class PipBoundsStateTest extends ShellTestCase { } @Test + public void testBoundsScale() { + mPipBoundsState.setMaxSize(300, 300); + mPipBoundsState.setBounds(new Rect(0, 0, 100, 100)); + + final int currentWidth = mPipBoundsState.getBounds().width(); + final Point maxSize = mPipBoundsState.getMaxSize(); + final float expectedBoundsScale = Math.min((float) currentWidth / maxSize.x, 1.0f); + + // test for currentWidth < maxWidth + assertEquals(expectedBoundsScale, mPipBoundsState.getBoundsScale(), MARGIN_OF_ERROR); + + // reset the bounds to be at the maximum size spec + mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x, maxSize.y)); + assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f); + + // reset the bounds to be over the maximum size spec + mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x * 2, maxSize.y * 2)); + assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f); + } + + @Test public void testSetReentryState() { final Size size = new Size(100, 100); final float snapFraction = 0.5f; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 6777a5bd8ceb..9719ba89b4bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -28,11 +28,13 @@ import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.testing.TestableResources; import android.view.MotionEvent; import android.view.ViewConfiguration; import androidx.test.filters.SmallTest; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -98,6 +100,9 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + final TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride(R.bool.config_pipEnablePinchResize, true); + mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index ebc284b1b77e..befc702b01aa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -208,6 +208,30 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest + public void testRemoteTransitionConsumed() { + // Omit side child change + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(TRANSIT_OPEN, mMainChild) + .build(); + TestRemoteTransition testRemote = new TestRemoteTransition(); + + IBinder transition = mSplitScreenTransitions.startEnterTransition( + TRANSIT_OPEN, new WindowContainerTransaction(), + new RemoteTransition(testRemote, "Test"), mStageCoordinator, + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + mMainStage.onTaskAppeared(mMainChild, createMockSurface()); + boolean accepted = mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + assertTrue(accepted); + + assertTrue(testRemote.isConsumed()); + + } + + @Test + @UiThreadTest public void testMonitorInSplit() { enterSplit(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index d5986780d5d8..da83d4c0a122 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; @@ -92,6 +93,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; @@ -145,7 +147,9 @@ public class ShellTransitionTests extends ShellTestCase { final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor); - verify(shellInit, times(1)).addInitCallback(any(), eq(t)); + // One from Transitions, one from RootTaskDisplayAreaOrganizer + verify(shellInit).addInitCallback(any(), eq(t)); + verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class)); } @Test @@ -285,6 +289,10 @@ public class ShellTransitionTests extends ShellTestCase { SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + } }; IBinder transitToken = new Binder(); transitions.requestStartTransition(transitToken, @@ -427,6 +435,10 @@ public class ShellTransitionTests extends ShellTestCase { SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + } }; TransitionFilter filter = new TransitionFilter(); @@ -473,6 +485,10 @@ public class ShellTransitionTests extends ShellTestCase { SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + } }; final int transitType = TRANSIT_FIRST_CUSTOM + 1; @@ -1153,7 +1169,7 @@ public class ShellTransitionTests extends ShellTestCase { } @Test - public void testEmptyTransitionStillReportsKeyguardGoingAway() { + public void testEmptyTransition_withKeyguardGoingAway_plays() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1172,6 +1188,65 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testSleepTransition_withKeyguardGoingAway_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If keyguard-going-away flag set, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + @Test + public void testSleepTransition_withChanges_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a transition with some changes + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP) + .addChange(TRANSIT_OPEN).build(); + info.setTrack(0); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is an actual change, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + + @Test + public void testSleepTransition_empty_SyncBySleepHandler() { + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, 0x0, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is nothing to actually play, it should not be offered to handlers. + assertEquals(0, mDefaultHandler.activeCount()); + } + + @Test public void testMultipleTracks() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java index 39ab23877c68..87330d2dc877 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java @@ -31,6 +31,7 @@ import android.window.WindowContainerTransaction; */ public class TestRemoteTransition extends IRemoteTransition.Stub { private boolean mCalled = false; + private boolean mConsumed = false; final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction(); @Override @@ -48,6 +49,11 @@ public class TestRemoteTransition extends IRemoteTransition.Stub { IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + mConsumed = true; + } + /** * Check whether this remote transition * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, @@ -56,4 +62,12 @@ public class TestRemoteTransition extends IRemoteTransition.Stub { public boolean isCalled() { return mCalled; } + + /** + * Check whether this remote transition's {@link #onTransitionConsumed(IBinder, boolean)} + * is called + */ + public boolean isConsumed() { + return mConsumed; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 8eaf5a004c0a..57aa47e85556 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -33,8 +33,12 @@ import android.view.Choreographer import android.view.Display.DEFAULT_DISPLAY import android.view.InputChannel import android.view.InputMonitor +import android.view.InsetsSource +import android.view.InsetsState import android.view.SurfaceControl import android.view.SurfaceView +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars import androidx.core.content.getSystemService import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -42,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -53,10 +58,12 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -68,6 +75,7 @@ import org.mockito.kotlin.whenever import java.util.Optional import java.util.function.Supplier + /** Tests of [DesktopModeWindowDecorViewModel] */ @SmallTest @RunWith(AndroidTestingRunner::class) @@ -80,6 +88,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock private lateinit var displayInsetsController: DisplayInsetsController @Mock private lateinit var mockSyncQueue: SyncTransactionQueue @Mock private lateinit var mockDesktopTasksController: DesktopTasksController @Mock private lateinit var mockInputMonitor: InputMonitor @@ -97,6 +106,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } private lateinit var shellInit: ShellInit + private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel @Before @@ -111,6 +121,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockTaskOrganizer, mockDisplayController, mockShellController, + displayInsetsController, mockSyncQueue, mockTransitions, Optional.of(mockDesktopTasksController), @@ -131,6 +142,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1]) shellInit.init() + + val listenerCaptor = + argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>() + verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture()) + desktopModeOnInsetsChangedListener = listenerCaptor.firstValue } @Test @@ -274,6 +290,67 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decoration).addTransitionPausingRelayout(transition) } + @Test + fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout occurs when status bar inset visibility changes + verify(decoration, times(1)).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add navigation bar insets source + val insetsState = InsetsState() + val navigationBarInsetsSourceId = 1 + val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars()) + navigationBarInsetsSource.isVisible = false + insetsState.addSource(navigationBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout does not occur when non-status bar inset changes visibility + verify(decoration, never()).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout runs only once when status bar inset visibility changes. + verify(decoration, times(1)).relayout(task) + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, @@ -313,6 +390,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockDesktopModeWindowDecorFactory.create( any(), any(), any(), eq(task), any(), any(), any(), any(), any()) ).thenReturn(decoration) + decoration.mTaskInfo = task + whenever(decoration.isFocused).thenReturn(task.isFocused) return decoration } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 966a99eea925..8061aa3f844a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -17,12 +17,19 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.captionBar; +import static android.view.WindowInsets.Type.mandatorySystemGestures; +import static android.view.WindowInsets.Type.statusBars; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction; import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertTrue; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; @@ -36,6 +43,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.LENIENT; import android.app.ActivityManager; import android.content.Context; @@ -48,6 +56,7 @@ import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.AttachedSurfaceControl; import android.view.Display; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; @@ -59,10 +68,12 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.tests.R; import org.junit.Before; @@ -89,6 +100,7 @@ public class WindowDecorationTests extends ShellTestCase { private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400); private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); private static final int CORNER_RADIUS = 20; + private static final int STATUS_BAR_INSET_SOURCE_ID = 0; private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult = new WindowDecoration.RelayoutResult<>(); @@ -113,6 +125,7 @@ public class WindowDecorationTests extends ShellTestCase { private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions = new ArrayList<>(); private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>(); + private final InsetsState mInsetsState = new InsetsState(); private SurfaceControl.Transaction mMockSurfaceControlStartT; private SurfaceControl.Transaction mMockSurfaceControlFinishT; private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; @@ -136,6 +149,11 @@ public class WindowDecorationTests extends ShellTestCase { .create(any(), any(), any()); when(mMockSurfaceControlViewHost.getRootSurfaceControl()) .thenReturn(mMockRootSurfaceControl); + when(mMockView.findViewById(anyInt())).thenReturn(mMockView); + + // Add status bar inset so that WindowDecoration does not think task is in immersive mode + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true); + doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); } @Test @@ -201,12 +219,8 @@ public class WindowDecorationTests extends ShellTestCase { createMockSurfaceControlBuilder(captionContainerSurface); mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); - final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = - new ActivityManager.TaskDescription.Builder() - .setBackgroundColor(Color.YELLOW); final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) - .setTaskDescriptionBuilder(taskDescriptionBuilder) .setBounds(TASK_BOUNDS) .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) .setVisible(true) @@ -255,8 +269,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS); verify(mMockSurfaceControlStartT) .show(taskSurface); - verify(mMockSurfaceControlStartT) - .setColor(taskSurface, new float[] {1.f, 1.f, 0.f}); verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10); assertEquals(300, mRelayoutResult.mWidth); @@ -502,6 +514,142 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); } + @Test + public void testRelayout_fluidResizeEnabled_freeformTask_setTaskSurfaceColor() { + StaticMockitoSession mockitoSession = mockitoSession().mockStatic( + DesktopModeStatus.class).strictness( + LENIENT).startMocking(); + when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false); + + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final SurfaceControl decorContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder decorContainerSurfaceBuilder = + createMockSurfaceControlBuilder(decorContainerSurface); + mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder() + .setBackgroundColor(Color.YELLOW); + + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build(); + taskInfo.isFocused = true; + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + windowDecor.relayout(taskInfo); + + verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f}); + + mockitoSession.finishMocking(); + } + + @Test + public void testInsetsAddedWhenCaptionIsVisible() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()) + .isVisible()); + assertTrue(mInsetsState.sourceSize() == 1); + assertTrue(mInsetsState.sourceAt(0).getType() == statusBars()); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar()), any()); + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures()), any()); + } + + @Test + public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() { + StaticMockitoSession mockitoSession = mockitoSession().mockStatic( + DesktopModeStatus.class).strictness(LENIENT).startMocking(); + when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false); + + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final SurfaceControl decorContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder decorContainerSurfaceBuilder = + createMockSurfaceControlBuilder(decorContainerSurface); + mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder() + .setBackgroundColor(Color.YELLOW); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build(); + taskInfo.isFocused = true; + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + windowDecor.relayout(taskInfo); + + verify(mMockSurfaceControlStartT).unsetColor(taskSurface); + + mockitoSession.finishMocking(); + } + + + @Test + public void testInsetsRemovedWhenCaptionIsHidden() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar())); + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures())); + } + private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt index c396a2032eed..794082124344 100644 --- a/libs/hwui/api/current.txt +++ b/libs/hwui/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.graphics { public class ColorMatrix { diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt index d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/module-lib-current.txt +++ b/libs/hwui/api/module-lib-current.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt index d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/module-lib-removed.txt +++ b/libs/hwui/api/module-lib-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt index d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/removed.txt +++ b/libs/hwui/api/removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt index d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/system-current.txt +++ b/libs/hwui/api/system-current.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt index d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/system-removed.txt +++ b/libs/hwui/api/system-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 7aef7a51b90c..14639456f13b 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -593,7 +593,7 @@ namespace PaintGlue { return result; } - static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) { + static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) { const int kElegantTop = 2500; const int kElegantBottom = -1000; const int kElegantAscent = 1900; @@ -622,6 +622,17 @@ namespace PaintGlue { metrics->fLeading = size * kElegantLeading / 2048; spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading; } + + if (useLocale) { + minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); + minikin::MinikinExtent extent = + typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint); + metrics->fAscent = std::min(extent.ascent, metrics->fAscent); + metrics->fDescent = std::max(extent.descent, metrics->fDescent); + metrics->fTop = std::min(metrics->fAscent, metrics->fTop); + metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom); + } + return spacing; } @@ -634,7 +645,7 @@ namespace PaintGlue { MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize); SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); metrics.fAscent = extent.ascent; metrics.fDescent = extent.descent; @@ -686,20 +697,21 @@ namespace PaintGlue { } } - static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { + static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj, + jboolean useLocale) { SkFontMetrics metrics; - SkScalar spacing = getMetricsInternal(paintHandle, &metrics); + SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale); GraphicsJNI::set_metrics(env, metricsObj, metrics); return SkScalarToFloat(spacing); } - static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { + static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj, + jboolean useLocale) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, useLocale); return GraphicsJNI::set_metrics_int(env, metricsObj, metrics); } - // ------------------ @CriticalNative --------------------------- static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { @@ -1002,19 +1014,19 @@ namespace PaintGlue { static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); return SkScalarToFloat(metrics.fAscent); } static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); return SkScalarToFloat(metrics.fDescent); } static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); SkScalar position; if (metrics.hasUnderlinePosition(&position)) { return SkScalarToFloat(position); @@ -1026,7 +1038,7 @@ namespace PaintGlue { static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); SkScalar thickness; if (metrics.hasUnderlineThickness(&thickness)) { return SkScalarToFloat(thickness); @@ -1121,9 +1133,9 @@ static const JNINativeMethod methods[] = { {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales}, {"nSetFontFeatureSettings", "(JLjava/lang/String;)V", (void*)PaintGlue::setFontFeatureSettings}, - {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", + {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F", (void*)PaintGlue::getFontMetrics}, - {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", + {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I", (void*)PaintGlue::getFontMetricsInt}, // --------------- @CriticalNative ------------------ diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 90850d36b612..22c586248705 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -26,6 +26,7 @@ #include <gui/TraceUtils.h> #include <include/gpu/ganesh/SkSurfaceGanesh.h> #include <include/gpu/ganesh/vk/GrVkBackendSurface.h> +#include <include/gpu/ganesh/vk/GrVkDirectContext.h> #include <ui/FatVector.h> #include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> @@ -435,7 +436,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options, options.fContextDeleteContext = this; options.fContextDeleteProc = onGrContextReleased; - return GrDirectContext::MakeVulkan(backendContext, options); + return GrDirectContexts::MakeVulkan(backendContext, options); } VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const { diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 230fb07f9f43..bf9419fe6603 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -875,18 +875,7 @@ public final class AudioAttributes implements Parcelable { /** * Sets the attribute describing what is the intended use of the audio signal, * such as alarm or ringtone. - * @param usage one of {@link AttributeSdkUsage#USAGE_UNKNOWN}, - * {@link AttributeSdkUsage#USAGE_MEDIA}, - * {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION}, - * {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING}, - * {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION}, - * {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE}, - * {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT}, - * {@link AttributeSdkUsage#USAGE_ASSISTANT}, - * {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY}, - * {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}, - * {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION}, - * {@link AttributeSdkUsage#USAGE_GAME}. + * @param usage the usage to set. * @return the same Builder instance. */ public Builder setUsage(@AttributeSdkUsage int usage) { diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index a311296dd90c..ceb3858eb0b3 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1284,8 +1284,7 @@ public final class AudioFormat implements Parcelable { * {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}. * <p> For a valid {@link AudioTrack} channel position mask, * the following conditions apply: - * <br> (1) at most {@link AudioSystem#OUT_CHANNEL_COUNT_MAX} channel positions may be - * used; + * <br> (1) at most eight channel positions may be used; * <br> (2) right/left pairs should be matched. * <p> For input or {@link AudioRecord}, the mask should be * {@link AudioFormat#CHANNEL_IN_MONO} or diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index adc0e16448ee..5b880797b7fd 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1473,8 +1473,7 @@ public class AudioManager { * Returns the volume group id associated to the given {@link AudioAttributes}. * * @param attributes The {@link AudioAttributes} to consider. - * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given - * {@link AudioAttributes} if found, + * @return audio volume group id supporting the given {@link AudioAttributes} if found, * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise. */ public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { @@ -1589,7 +1588,7 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param groupId of the audio volume group to consider. * @param direction The direction to adjust the volume. One of * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or * {@link #ADJUST_SAME}. @@ -1633,8 +1632,8 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. - * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id. + * @param groupId of the audio volume group to consider. + * @return The mute state for the given audio volume group id. * @see #adjustVolumeGroupVolume(int, int, int) */ public boolean isVolumeGroupMuted(int groupId) { @@ -4659,24 +4658,24 @@ public class AudioManager { Objects.requireNonNull(afr); Objects.requireNonNull(clientFakeId); int status; - try { - status = getService().requestAudioFocusForTest(afr.getAudioAttributes(), - afr.getFocusGain(), - mICallBack, - mAudioFocusDispatcher, - clientFakeId, "com.android.test.fakeclient", - afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST, - clientFakeUid, clientTargetSdk); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { - // default path with no external focus policy - return status; - } - BlockingFocusResultReceiver focusReceiver; synchronized (mFocusRequestsLock) { + try { + status = getService().requestAudioFocusForTest(afr.getAudioAttributes(), + afr.getFocusGain(), + mICallBack, + mAudioFocusDispatcher, + clientFakeId, "com.android.test.fakeclient", + afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST, + clientFakeUid, clientTargetSdk); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId); } diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java index 0f962f9e9d4b..4e61549a5e5a 100644 --- a/media/java/android/media/AudioMetadata.java +++ b/media/java/android/media/AudioMetadata.java @@ -226,16 +226,15 @@ public final class AudioMetadata { * * An Integer value representing presentation content classifier. * - * @see AudioPresentation.ContentClassifier - * One of {@link AudioPresentation#CONTENT_UNKNOWN}, - * {@link AudioPresentation#CONTENT_MAIN}, - * {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, - * {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, - * {@link AudioPresentation#CONTENT_HEARING_IMPAIRED}, - * {@link AudioPresentation#CONTENT_DIALOG}, - * {@link AudioPresentation#CONTENT_COMMENTARY}, - * {@link AudioPresentation#CONTENT_EMERGENCY}, - * {@link AudioPresentation#CONTENT_VOICEOVER}. + * @see AudioPresentation#CONTENT_UNKNOWN + * @see AudioPresentation#CONTENT_MAIN + * @see AudioPresentation#CONTENT_MUSIC_AND_EFFECTS + * @see AudioPresentation#CONTENT_VISUALLY_IMPAIRED + * @see AudioPresentation#CONTENT_HEARING_IMPAIRED + * @see AudioPresentation#CONTENT_DIALOG + * @see AudioPresentation#CONTENT_COMMENTARY + * @see AudioPresentation#CONTENT_EMERGENCY + * @see AudioPresentation#CONTENT_VOICEOVER */ @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER = createKey("presentation-content-classifier", Integer.class); diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index 73f15f21596c..1e57be2c1e22 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -49,7 +49,7 @@ interface IRingtonePlayer { oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled); /** Used for Notification sound playback. */ - oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa); + oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa, float volume); oneway void stopAsync(); /** Return the title of the media. */ diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 8c635807022b..21690904fe42 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -463,7 +463,7 @@ public final class MediaRouter2 { /** * Returns the current {@link RouteListingPreference} of the target router. * - * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns + * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns * the last {@link RouteListingPreference} set by the process this router was created for. * * @see #setRouteListingPreference(RouteListingPreference) diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 83d2b613f80a..a354f9180e09 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -15,8 +15,15 @@ flag { } flag { - namespace: "media_solutions" - name: "enable_audio_policies_device_and_bluetooth_controller" - description: "Use Audio Policies implementation for device and Bluetooth route controllers." - bug: "280576228" + namespace: "media_solutions" + name: "enable_audio_policies_device_and_bluetooth_controller" + description: "Use Audio Policies implementation for device and Bluetooth route controllers." + bug: "280576228" +} + +flag { + namespace: "media_solutions" + name: "disable_screen_off_broadcast_receiver" + description: "Disables the broadcast receiver that prevents scanning when the screen is off." + bug: "304234628" } diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java index 6e2aaabf4b04..bbbe7f683b05 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -38,7 +38,7 @@ import java.util.List; * of {@link MidiReceiver}s for sending data out the output ports. * * Unlike traditional MIDI byte streams, only complete UMPs should be sent. - * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal. + * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal. * * <p>To extend this class, you must declare the service in your manifest file with * an intent filter with the {@link #SERVICE_INTERFACE} action diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index d294601b44cc..80e22477efed 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -156,4 +156,24 @@ interface IMediaProjectionManager { + ".permission.MANAGE_MEDIA_PROJECTION)") void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult, in @nullable IMediaProjection projection); + + /** + * Notifies system server that we are handling a particular state during the consent flow. + * + * <p>Only used for emitting atoms. + * + * @param hostUid The uid of the process requesting consent to capture, may be an app or + * SystemUI. + * @param state The state that SystemUI is handling during the consent flow. + * Must be a valid + * state defined in the MediaProjectionState enum. + * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. + * Indicates the entry point for requesting the permission. Must be + * a valid state defined + * in the SessionCreationSource enum. + */ + @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); } diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS index cc9be9c5126a..880ec8fdef88 100644 --- a/media/java/android/media/projection/OWNERS +++ b/media/java/android/media/projection/OWNERS @@ -4,3 +4,4 @@ michaelwr@google.com santoscordon@google.com chaviw@google.com nmusgrave@google.com +dakinola@google.com diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java index 078e83222e4e..ec0d7f7a2ce4 100644 --- a/media/java/android/media/tv/SectionRequest.java +++ b/media/java/android/media/tv/SectionRequest.java @@ -81,7 +81,7 @@ public final class SectionRequest extends BroadcastInfoRequest implements Parcel /** * Gets the version number of requested session. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java index f38ea9dfac99..10333fe424a6 100644 --- a/media/java/android/media/tv/SectionResponse.java +++ b/media/java/android/media/tv/SectionResponse.java @@ -76,7 +76,7 @@ public final class SectionResponse extends BroadcastInfoResponse implements Parc /** * Gets the Version number of requested session. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java index d9587f6ac089..06df07fbc899 100644 --- a/media/java/android/media/tv/TableRequest.java +++ b/media/java/android/media/tv/TableRequest.java @@ -129,7 +129,7 @@ public final class TableRequest extends BroadcastInfoRequest implements Parcelab /** * Gets the version number of requested table. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java index c4fc26ef1932..1daf452fa422 100644 --- a/media/java/android/media/tv/TableResponse.java +++ b/media/java/android/media/tv/TableResponse.java @@ -269,7 +269,7 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel /** * Gets the version number of requested table. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 667a9aef59f3..2db4be86bf91 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -939,9 +939,8 @@ public final class TvInputInfo implements Parcelable { type = TYPE_HDMI; isHardwareInput = true; hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo); - isConnectedToHdmiSwitch = - hdmiConnectionRelativePosition - != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; + isConnectedToHdmiSwitch = hdmiConnectionRelativePosition + == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW; } else if (mTvInputHardwareInfo != null) { id = generateInputId(componentName, mTvInputHardwareInfo); type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index c616b84fa6fb..1c25080939da 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -38,6 +38,8 @@ #include <mediadrm/IDrmMetricsConsumer.h> #include <mediadrm/IDrm.h> #include <utils/Vector.h> +#include <map> +#include <string> using ::android::os::PersistableBundle; namespace drm = ::android::hardware::drm; @@ -193,6 +195,11 @@ struct LogMessageFields { jclass classId; }; +struct DrmExceptionFields { + jmethodID init; + jclass classId; +}; + struct fields_t { jfieldID context; jmethodID post_event; @@ -215,6 +222,7 @@ struct fields_t { jclass parcelCreatorClassId; KeyStatusFields keyStatus; LogMessageFields logMessage; + std::map<std::string, DrmExceptionFields> exceptionCtors; }; static fields_t gFields; @@ -245,18 +253,32 @@ jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessag return arrayList; } -int drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) { +void resolveDrmExceptionCtor(JNIEnv *env, const char *className) { + jclass clazz; + jmethodID init; + FIND_CLASS(clazz, className); + GET_METHOD_ID(init, clazz, "<init>", "(Ljava/lang/String;III)V"); + gFields.exceptionCtors[std::string(className)] = { + .init = init, + .classId = static_cast<jclass>(env->NewGlobalRef(clazz)) + }; +} + +void drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) { using namespace android::jnihelp; - jstring _detailMessage = CreateExceptionMsg(env, msg); - int _status = ThrowException(env, className, "(Ljava/lang/String;III)V", - _detailMessage, - err.getCdmErr(), - err.getOemErr(), - err.getContext()); - if (_detailMessage != NULL) { - env->DeleteLocalRef(_detailMessage); + + if (gFields.exceptionCtors.count(std::string(className)) == 0) { + jniThrowException(env, className, msg); + } else { + jstring _detailMessage = CreateExceptionMsg(env, msg); + jobject exception = env->NewObject(gFields.exceptionCtors[std::string(className)].classId, + gFields.exceptionCtors[std::string(className)].init, _detailMessage, + err.getCdmErr(), err.getOemErr(), err.getContext()); + env->Throw(static_cast<jthrowable>(exception)); + if (_detailMessage != NULL) { + env->DeleteLocalRef(_detailMessage); + } } - return _status; } } // namespace anonymous @@ -952,6 +974,10 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage"); gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V"); + + resolveDrmExceptionCtor(env, "android/media/NotProvisionedException"); + resolveDrmExceptionCtor(env, "android/media/ResourceBusyException"); + resolveDrmExceptionCtor(env, "android/media/DeniedByServerException"); } static void android_media_MediaDrm_native_setup( @@ -2192,4 +2218,4 @@ static const JNINativeMethod gMethods[] = { int register_android_media_Drm(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaDrm", gMethods, NELEM(gMethods)); -} +}
\ No newline at end of file diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp index 58bcd1d49c69..e71597a27a39 100644 --- a/omapi/aidl/Android.bp +++ b/omapi/aidl/Android.bp @@ -24,6 +24,11 @@ aidl_interface { backend: { java: { sdk_version: "module_current", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + ], + }, rust: { enabled: true, diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml index 7ca172e2fbe9..4eb8de6be5e1 100644 --- a/packages/CompanionDeviceManager/res/values-pl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml @@ -37,7 +37,7 @@ <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Zezwolić urządzeniu <strong><xliff:g id="DEVICE_NAME">%1$s</xliff:g></strong> na wykonanie tego działania?"</string> <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_NAME">%2$s</xliff:g> o uprawnienia do strumieniowego odtwarzania treści i innych funkcji systemowych na urządzeniach w pobliżu"</string> <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string> - <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak nazwa osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string> + <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak imię i nazwisko osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string> <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string> <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string> <string name="consent_cancel" msgid="5655005528379285841">"Anuluj"</string> diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index 222877bbe9e9..88f1204641ff 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -102,6 +102,8 @@ <item name="android:layout_height">36dp</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> + <item name="android:paddingLeft">6dp</item> + <item name="android:paddingRight">6dp</item> <item name="android:background">@drawable/btn_negative_multiple_devices</item> <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> </style> diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml index 118a77c2f73c..c25fa99513d1 100644 --- a/packages/CredentialManager/res/values-hu/strings.xml +++ b/packages/CredentialManager/res/values-hu/strings.xml @@ -70,7 +70,7 @@ <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Elvetés"</string> <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett azonosítókulcsot használni?"</string> <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Szeretné az elmentett jelszavát használni a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz?"</string> - <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Szeretné használni a következőhöz tartozó bejelentkezési adatait: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Szeretné használni bejelentkezési adatait a következőhöz: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Feloldja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> bejelentkezési lehetőségeit?"</string> <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Mentett azonosítókulcs kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string> <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Mentett jelszó kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string> diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml index ed42f3afcfe0..8125ec6b053f 100644 --- a/packages/CredentialManager/res/values-ka/strings.xml +++ b/packages/CredentialManager/res/values-ka/strings.xml @@ -70,12 +70,12 @@ <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"დახურვა"</string> <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"გსურთ თქვენი დამახსოვრებული წვდომის გასაღების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"გამოიყენებთ შენახულ პაროლს <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string> - <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"გსურთ შესვლის <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის გამოყენება?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"გსურთ შესვლის <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის გამოყენება?"</string> <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"გსურთ შესვლის ვარიანტების განბლოკვა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string> <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"აირჩიეთ შენახული წვდომის გასაღები <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string> <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"აირჩიეთ შენახული პაროლი <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string> <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"აირჩიეთ სისტემაში შესვლის ინფორმაცია <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string> - <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"აირჩიეთ შესვლა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"აირჩიეთ შესვლა <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის"</string> <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"გსურთ აირჩიოთ ვარიანტი <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის?"</string> <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"გსურთ ამ ინფორმაციის გამოყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ში?"</string> <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"სხვა ხერხით შესვლა"</string> diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml index b4c567077859..e2de2efb60d8 100644 --- a/packages/CredentialManager/res/values-ky/strings.xml +++ b/packages/CredentialManager/res/values-ky/strings.xml @@ -70,12 +70,12 @@ <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Жабуу"</string> <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү үчүн сакталган ачкычты колдоносузбу?"</string> <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү колдоносузбу?"</string> - <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандайсызбы?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна төмөнкү аккаунт менен киресизби?"</string> <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү параметрлеринин кулпусу ачылсынбы?"</string> <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган киргизүүчү ачкычты тандаңыз"</string> <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү тандаңыз"</string> <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү маалыматын тандаңыз"</string> - <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандаңыз"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кайсы аккаунт менен киресиз:"</string> <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн параметр тандайсызбы?"</string> <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Бул маалыматты <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда пайдаланасызбы?"</string> <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Башка жол менен кирүү"</string> diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml index 15a668a73d1f..222b8654d619 100644 --- a/packages/CredentialManager/res/values-zh-rCN/strings.xml +++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml @@ -70,7 +70,7 @@ <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"忽略"</string> <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用您为“<xliff:g id="APP_NAME">%1$s</xliff:g>”保存的通行密钥吗?"</string> <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"要使用已保存的密码登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”吗?"</string> - <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"使用您的<xliff:g id="APP_NAME">%1$s</xliff:g>登录凭据?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"是否使用您的<xliff:g id="APP_NAME">%1$s</xliff:g>登录凭据继续?"</string> <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"要解锁“<xliff:g id="APP_NAME">%1$s</xliff:g>”的登录选项吗?"</string> <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"选择一个已保存的通行密钥来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string> <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"选择一个已保存的密码来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string> diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml index f14a5ce3bfd0..c09bf86d3516 100644 --- a/packages/CredentialManager/res/values-zh-rTW/strings.xml +++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string> + <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string> <string name="string_cancel" msgid="6369133483981306063">"取消"</string> <string name="string_continue" msgid="1346732695941131882">"繼續"</string> <string name="string_more_options" msgid="2763852250269945472">"儲存其他方式"</string> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 473d7b6f32df..477e61d2a7b1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -61,14 +61,20 @@ import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import androidx.credentials.provider.RemoteEntry import org.json.JSONObject +import android.credentials.flags.Flags import java.time.Instant + fun getAppLabel( pm: PackageManager, appPackageName: String ): String? { return try { - val pkgInfo = getPackageInfo(pm, appPackageName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, appPackageName) + } else { + pm.getPackageInfo(appPackageName, PackageManager.PackageInfoFlags.of(0)) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) applicationInfo.loadSafeLabel( pm, 0f, @@ -91,7 +97,14 @@ private fun getServiceLabelAndIcon( // Test data has only package name not component name. // For test data usage only. try { - val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + providerFlattenedComponentName, + PackageManager.PackageInfoFlags.of(0) + ) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) providerLabel = applicationInfo.loadSafeLabel( @@ -115,7 +128,14 @@ private fun getServiceLabelAndIcon( // Added for mdoc use case where the provider may not need to register a service and // instead only relies on the registration api. try { - val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + component.packageName, + PackageManager.PackageInfoFlags.of(0) + ) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) providerLabel = applicationInfo.loadSafeLabel( @@ -143,12 +163,12 @@ private fun getPackageInfo( pm: PackageManager, packageName: String ): PackageInfo { - val flags = PackageManager.MATCH_INSTANT + val packageManagerFlags = PackageManager.MATCH_INSTANT return pm.getPackageInfo( packageName, PackageManager.PackageInfoFlags.of( - (flags).toLong()) + (packageManagerFlags).toLong()) ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 2318bb95dabb..9355517d8bf9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -27,14 +27,22 @@ import android.os.Bundle import android.os.CancellationSignal import android.os.OutcomeReceiver import android.service.autofill.AutofillService +import android.service.autofill.Dataset +import android.service.autofill.Field import android.service.autofill.FillCallback import android.service.autofill.FillRequest import android.service.autofill.FillResponse +import android.service.autofill.InlinePresentation +import android.service.autofill.Presentations import android.service.autofill.SaveCallback import android.service.autofill.SaveRequest import android.service.credentials.CredentialProviderService import android.util.Log import android.view.autofill.AutofillId +import org.json.JSONException +import android.widget.inline.InlinePresentationSpec +import androidx.autofill.inline.v1.InlineSuggestionUi +import com.android.credentialmanager.GetFlowUtils import org.json.JSONObject import java.util.concurrent.Executors @@ -49,11 +57,9 @@ class CredentialAutofillService : AutofillService() { private const val SYS_PROVIDER_REQ_KEY = "isSystemProviderRequired" private const val CRED_OPTIONS_KEY = "credentialOptions" private const val TYPE_KEY = "type" + private const val REQ_TYPE_KEY = "get" } - private val credentialManager: CredentialManager = - getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager - override fun onFillRequest( request: FillRequest, cancellationSignal: CancellationSignal, @@ -66,16 +72,24 @@ class CredentialAutofillService : AutofillService() { val getCredRequest: GetCredentialRequest? = getCredManRequest(structure) if (getCredRequest == null) { + Log.i(TAG, "No credential manager request found") callback.onFailure("No credential manager request found") return } + val credentialManager: CredentialManager = + getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse, GetCandidateCredentialsException> { override fun onResult(result: GetCandidateCredentialsResponse) { Log.i(TAG, "getCandidateCredentials onResponse") - val fillResponse: FillResponse? = convertToFillResponse(result, request) - callback.onSuccess(fillResponse) + val fillResponse = convertToFillResponse(result, request) + if (fillResponse != null) { + callback.onSuccess(fillResponse) + } else { + Log.e(TAG, "Failed to create a FillResponse from the CredentialResponse.") + callback.onFailure("No dataset was created from the CredentialResponse") + } } override fun onError(error: GetCandidateCredentialsException) { @@ -97,7 +111,74 @@ class CredentialAutofillService : AutofillService() { getCredResponse: GetCandidateCredentialsResponse, filLRequest: FillRequest ): FillResponse? { - TODO("Not yet implemented") + val providerList = GetFlowUtils.toProviderList( + getCredResponse.candidateProviderDataList, + this@CredentialAutofillService) + var totalEntryCount = 0 + providerList.forEach { provider -> + totalEntryCount += provider.credentialEntryList.size + } + val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest + val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0 + val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs + val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 + var maxItemCount = totalEntryCount + if (inlineMaxSuggestedCount > 0) { + maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount) + } + var i = 0 + val fillResponseBuilder = FillResponse.Builder() + var emptyFillResponse = true + providerList.forEach {provider -> + // TODO(b/299321128): Before iterating the list, sort the list so that + // the relevant entries don't get truncated + provider.credentialEntryList.forEach entryLoop@ {entry -> + val autofillId: AutofillId? = entry.fillInIntent?.getParcelableExtra( + CredentialProviderService.EXTRA_AUTOFILL_ID, + AutofillId::class.java) + val pendingIntent = entry.pendingIntent + if (autofillId == null || pendingIntent == null) { + return@entryLoop + } + var inlinePresentation: InlinePresentation? = null + // Create inline presentation + if (inlinePresentationSpecs != null && i < maxItemCount) { + val spec: InlinePresentationSpec + if (i < inlinePresentationSpecsCount) { + spec = inlinePresentationSpecs[i] + } else { + spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1] + } + val sliceBuilder = InlineSuggestionUi + .newContentBuilder(pendingIntent) + .setTitle(entry.userName) + inlinePresentation = InlinePresentation( + sliceBuilder.build().slice, spec, /* pinned= */ false) + } + i++ + + val dataSetBuilder = Dataset.Builder() + val presentationBuilder = Presentations.Builder() + if (inlinePresentation != null) { + presentationBuilder.setInlinePresentation(inlinePresentation) + } + fillResponseBuilder.addDataset( + dataSetBuilder + .setField( + autofillId, + Field.Builder().setPresentations( + presentationBuilder.build()) + .build()) + .setAuthentication(entry.pendingIntent.intentSender) + .setAuthenticationExtras(entry.fillInIntent.extras) + .build()) + emptyFillResponse = false + } + } + if (emptyFillResponse) { + return null + } + return fillResponseBuilder.build() } override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { @@ -165,8 +246,12 @@ class CredentialAutofillService : AutofillService() { val credentialOptions: MutableList<CredentialOption> = mutableListOf() for (credentialHint in credentialHints) { - convertJsonToCredentialOption(credentialHint, autofillId) - .let { credentialOptions.addAll(it) } + try { + convertJsonToCredentialOption(credentialHint, autofillId) + .let { credentialOptions.addAll(it) } + } catch (e: JSONException) { + Log.i(TAG, "Exception while parsing response: " + e.message) + } } return credentialOptions } @@ -178,7 +263,8 @@ class CredentialAutofillService : AutofillService() { val credentialOptions: MutableList<CredentialOption> = mutableListOf() val json = JSONObject(jsonString) - val options = json.getJSONArray(CRED_OPTIONS_KEY) + val jsonGet = json.getJSONObject(REQ_TYPE_KEY) + val options = jsonGet.getJSONArray(CRED_OPTIONS_KEY) for (i in 0 until options.length()) { val option = options.getJSONObject(i) val candidateBundle = convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY)) diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 4c313b22f71e..3409c29d3c2c 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,6 +16,8 @@ package com.android.externalstorage; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -64,7 +66,19 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; - +import java.util.regex.Pattern; + +/** + * Presents content of the shared (a.k.a. "external") storage. + * <p> + * Starting with Android 11 (R), restricts access to the certain sections of the shared storage: + * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in + * the DocumentsUI by default. + * See <a href="https://developer.android.com/about/versions/11/privacy/storage"> + * Storage updates in Android 11</a>. + * <p> + * Documents ID format: {@code root:path/to/file}. + */ public class ExternalStorageProvider extends FileSystemProvider { private static final String TAG = "ExternalStorage"; @@ -75,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); - // docId format: root:path/to/file + /** + * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and + * {@code /Android/sandbox/} along with all their subdirectories and content. + */ + private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = + Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -278,76 +297,91 @@ public class ExternalStorageProvider extends FileSystemProvider { return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } + /** + * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the + * integrated shared ("external") storage along with all their content and subdirectories as + * hidden. + */ @Override - public Cursor queryChildDocumentsForManage( - String parentDocId, String[] projection, String sortOrder) - throws FileNotFoundException { - return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); + protected boolean shouldHideDocument(@NonNull String documentId) { + // Don't need to hide anything on USB drives. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } + + final String path = getPathFromDocId(documentId); + return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); } /** * Check that the directory is the root of storage or blocked file from tree. + * <p> + * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear + * the UI, but the user <b>WILL NOT</b> be able to select them. * - * @param docId the docId of the directory to be checked + * @param documentId the docId of the directory to be checked * @return true, should be blocked from tree. Otherwise, false. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ @Override - protected boolean shouldBlockFromTree(@NonNull String docId) { - try { - final File dir = getFileForDocId(docId, false /* visible */); - - // the file is null or it is not a directory - if (dir == null || !dir.isDirectory()) { - return false; - } + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { + final File dir = getFileForDocId(documentId, false); + // The file is null or it is not a directory + if (dir == null || !dir.isDirectory()) { + return false; + } - // Allow all directories on USB, including the root. - try { - RootInfo rootInfo = getRootFromDocId(docId); - if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { - return false; - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Failed to determine rootInfo for docId"); - } + // Allow all directories on USB, including the root. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } - final String path = getPathFromDocId(docId); + // Get canonical(!) path. Note that this path will have neither leading nor training "/". + // This the root's path will be just an empty string. + final String path = getPathFromDocId(documentId); - // Block the root of the storage - if (path.isEmpty()) { - return true; - } + // Block the root of the storage + if (path.isEmpty()) { + return true; + } - // Block Download folder from tree - if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // Block /Download/ and /Android/ folders from the tree. + if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || + equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { + return true; + } - // Block /Android - if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // This shouldn't really make a difference, but just in case - let's block hidden + // directories as well. + if (shouldHideDocument(documentId)) { + return true; + } - // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs - if (shouldHide(dir)) { - return true; - } + return false; + } + private boolean isOnRemovableUsbStorage(@NonNull String documentId) { + final RootInfo rootInfo; + try { + rootInfo = getRootFromDocId(documentId); + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"'); return false; - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to determine if " + docId + " should block from tree " + ": " + e); } + + return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0; } + @NonNull @Override - protected String getDocIdForFile(File file) throws FileNotFoundException { + protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); } - private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) + @NonNull + private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir) throws FileNotFoundException { String path = file.getAbsolutePath(); @@ -417,31 +451,33 @@ public class ExternalStorageProvider extends FileSystemProvider { private File getFileForDocId(String docId, boolean visible, boolean mustExist) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return buildFile(root, docId, visible, mustExist); + return buildFile(root, docId, mustExist); } - private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) - throws FileNotFoundException { + private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return Pair.create(root, buildFile(root, docId, visible, true)); + return Pair.create(root, buildFile(root, docId, /* mustExist */ true)); } @VisibleForTesting - static String getPathFromDocId(String docId) throws IOException { + static String getPathFromDocId(String docId) { final int splitIndex = docId.indexOf(':', 1); final String docIdPath = docId.substring(splitIndex + 1); - // Get CanonicalPath and remove the first "/" - final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1); - if (canonicalPath.isEmpty()) { - return canonicalPath; + // Canonicalize path and strip the leading "/" + final String path; + try { + path = new File(docIdPath).getCanonicalPath().substring(1); + } catch (IOException e) { + Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); + return ""; } - // remove trailing "/" - if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') { - return canonicalPath.substring(0, canonicalPath.length() - 1); + // Remove the trailing "/" as well. + if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { + return path.substring(0, path.length() - 1); } else { - return canonicalPath; + return path; } } @@ -460,7 +496,7 @@ public class ExternalStorageProvider extends FileSystemProvider { return root; } - private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) + private File buildFile(RootInfo root, String docId, boolean mustExist) throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); @@ -544,7 +580,7 @@ public class ExternalStorageProvider extends FileSystemProvider { @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { - final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); + final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId); final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; @@ -648,6 +684,13 @@ public class ExternalStorageProvider extends FileSystemProvider { } } + /** + * Print the state into the given stream. + * Gets invoked when you run: + * <pre> + * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider + * </pre> + */ @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); @@ -731,4 +774,8 @@ public class ExternalStorageProvider extends FileSystemProvider { } return bundle; } + + private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { + return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); + } } diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java index 18a8edc5e447..0144b6ea9040 100644 --- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java +++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java @@ -16,47 +16,64 @@ package com.android.externalstorage; +import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID; + import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY; import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.app.Instrumentation; +import android.content.Context; import android.content.pm.ProviderInfo; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ExternalStorageProviderTest { + + @NonNull + private static final Instrumentation sInstrumentation = + InstrumentationRegistry.getInstrumentation(); + @NonNull + private static final Context sTargetContext = sInstrumentation.getTargetContext(); + + private ExternalStorageProvider mExternalStorageProvider; + + @Before + public void setUp() { + mExternalStorageProvider = new ExternalStorageProvider(); + } + + @Test - public void onCreate_shouldUpdateVolumes() throws Exception { - ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider(); - ExternalStorageProvider spyProvider = spy(externalStorageProvider); - ProviderInfo providerInfo = new ProviderInfo(); + public void onCreate_shouldUpdateVolumes() { + final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider); + + final ProviderInfo providerInfo = new ProviderInfo(); providerInfo.authority = AUTHORITY; providerInfo.grantUriPermissions = true; providerInfo.exported = true; - InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - spyProvider.attachInfoForTesting( - InstrumentationRegistry.getTargetContext(), providerInfo); - } - }); + sInstrumentation.runOnMainSync(() -> + spyProvider.attachInfoForTesting(sTargetContext, providerInfo)); verify(spyProvider, atLeast(1)).updateVolumes(); } @Test - public void testGetPathFromDocId() throws Exception { + public void test_getPathFromDocId() { final String root = "root"; final String path = "abc/def/ghi"; String docId = root + ":" + path; @@ -79,4 +96,62 @@ public class ExternalStorageProviderTest { docId = root + ":" + twoDotPath; assertEquals(getPathFromDocId(docId), path); } + + @Test + public void test_shouldHideDocument() { + // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their + // "subtrees". + final String[] shouldHide = { + // "Android/data" and all its subdirectories + "Android/data", + "Android/data/com.my.app", + "Android/data/com.my.app/cache", + "Android/data/com.my.app/cache/image.png", + "Android/data/mydata", + + // "Android/obb" and all its subdirectories + "Android/obb", + "Android/obb/com.my.app", + "Android/obb/com.my.app/file.blob", + + // "Android/sandbox" and all its subdirectories + "Android/sandbox", + "Android/sandbox/com.my.app", + + // Also make sure we are not allowing path traversals + "Android/./data", + "Android/Download/../data", + }; + for (String path : shouldHide) { + final String docId = buildDocId(path); + assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't", + mExternalStorageProvider.shouldHideDocument(docId)); + } + + // Should NOT hide anything else. + final String[] shouldNotHide = { + "Android", + "Android/datadir", + "Documents", + "Download", + "Music", + "Pictures", + }; + for (String path : shouldNotHide) { + final String docId = buildDocId(path); + assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did", + mExternalStorageProvider.shouldHideDocument(docId)); + } + } + + @NonNull + private static String buildDocId(@NonNull String path) { + return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path); + } + + @NonNull + private static String buildDocId(@NonNull String root, @NonNull String path) { + // docId format: root:path/to/file + return root + ':' + path; + } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index d97fb5440cbd..c5ae4a37b355 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -381,7 +381,7 @@ public class PackageInstallerActivity extends AlertActivity { final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1 /* defaultValue */); final SessionInfo info = mInstaller.getSessionInfo(sessionId); - String resolvedPath = info.getResolvedBaseApkPath(); + String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null; if (info == null || !info.isSealed() || resolvedPath == null) { Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); finish(); @@ -609,7 +609,7 @@ public class PackageInstallerActivity extends AlertActivity { CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo); if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label); mAppSnippet = new PackageUtil.AppSnippet(label, - mPm.getApplicationIcon(mPkgInfo.applicationInfo)); + mPm.getApplicationIcon(mPkgInfo.applicationInfo), getBaseContext()); } break; case ContentResolver.SCHEME_FILE: { @@ -647,7 +647,7 @@ public class PackageInstallerActivity extends AlertActivity { mPkgInfo = generateStubPackageInfo(info.getAppPackageName()); mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(), info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon()) - : getPackageManager().getDefaultActivityIcon()); + : getPackageManager().getDefaultActivityIcon(), getBaseContext()); return true; } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 334886fe5561..976a3ad69901 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -18,6 +18,7 @@ package com.android.packageinstaller; import android.app.Activity; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -135,15 +136,20 @@ public class PackageUtil { static final class AppSnippet implements Parcelable { @NonNull public CharSequence label; @Nullable public Drawable icon; - public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) { + public int iconSize; + + AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) { this.label = label; this.icon = icon; + final ActivityManager am = context.getSystemService(ActivityManager.class); + this.iconSize = am.getLauncherLargeIconSize(); } private AppSnippet(Parcel in) { label = in.readString(); Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class); icon = new BitmapDrawable(Resources.getSystem(), bmp); + iconSize = in.readInt(); } @Override @@ -161,6 +167,7 @@ public class PackageUtil { dest.writeString(label.toString()); Bitmap bmp = getBitmapFromDrawable(icon); dest.writeParcelable(bmp, 0); + dest.writeInt(iconSize); } private Bitmap getBitmapFromDrawable(Drawable drawable) { @@ -174,6 +181,14 @@ public class PackageUtil { // bitmap held within drawable.draw(canvas); + // Scale it down if the icon is too large + if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) { + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true); + if (scaledBitmap != bmp) { + bmp.recycle(); + } + return scaledBitmap; + } return bmp; } @@ -241,7 +256,7 @@ public class PackageUtil { } catch (OutOfMemoryError e) { Log.i(LOG_TAG, "Could not load app icon", e); } - return new PackageUtil.AppSnippet(label, icon); + return new PackageUtil.AppSnippet(label, icon, pContext); } private static String findFilePath(File[] files, String postfix) { diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 2d231f2f55da..8964adaf586e 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -11,50 +11,14 @@ android_library { name: "SettingsLib", static_libs: [ - "androidx.annotation_annotation", - "androidx.appcompat_appcompat", - "androidx.coordinatorlayout_coordinatorlayout", - "androidx.core_core", - "androidx.fragment_fragment", - "androidx.lifecycle_lifecycle-runtime", - "androidx.loader_loader", "androidx.localbroadcastmanager_localbroadcastmanager", - "androidx.preference_preference", - "androidx.recyclerview_recyclerview", - "com.google.android.material_material", - "iconloader", + "androidx.room_room-runtime", + "zxing-core", "WifiTrackerLibRes", - "SettingsLibDeviceStateRotationLock", - "SettingsLibDisplayUtils", - "SettingsLibEmergencyNumber", - "SettingsLibSearchWidget", - "SettingsLibUtils", - "SettingsLibWidget", + "iconloader", "setupdesign", - "zxing-core-1.7", - "androidx.room_room-runtime", - "settingslib_flags_lib", - ], - - plugins: ["androidx.room_room-compiler-plugin"], - use_resource_processor: true, - resource_dirs: ["res"], - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], -} -// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to -// set use_resource_processor = true. -// We can remove SettingsLibWidget when all these libraries have its own namespace. -android_library { - name: "SettingsLibWidget", - visibility: ["//visibility:private"], - manifest: "AndroidManifest-SettingsLibWidget.xml", - static_libs: [ "SettingsLibActionBarShadow", "SettingsLibActionButtonsPreference", "SettingsLibAdaptiveIcon", @@ -63,6 +27,9 @@ android_library { "SettingsLibBarChartPreference", "SettingsLibButtonPreference", "SettingsLibCollapsingToolbarBaseActivity", + "SettingsLibDeviceStateRotationLock", + "SettingsLibDisplayUtils", + "SettingsLibEmergencyNumber", "SettingsLibEntityHeaderWidgets", "SettingsLibFooterPreference", "SettingsLibHelpUtils", @@ -72,31 +39,31 @@ android_library { "SettingsLibProfileSelector", "SettingsLibProgressBar", "SettingsLibRestrictedLockUtils", + "SettingsLibSearchWidget", "SettingsLibSelectorWithWidgetPreference", "SettingsLibSettingsSpinner", "SettingsLibSettingsTransition", "SettingsLibTopIntroPreference", "SettingsLibTwoTargetPreference", "SettingsLibUsageProgressBarPreference", + "SettingsLibUtils", + "settingslib_flags_lib", ], - resource_dirs: [], + plugins: ["androidx.room_room-compiler-plugin"], + use_resource_processor: true, + resource_dirs: ["res"], + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], } // NOTE: Keep this module in sync with ./common.mk java_defaults { name: "SettingsLibDefaults", static_libs: [ - "androidx.annotation_annotation", - "androidx.appcompat_appcompat", - "androidx.coordinatorlayout_coordinatorlayout", - "androidx.core_core", - "androidx.fragment_fragment", - "androidx.lifecycle_lifecycle-runtime", - "androidx.loader_loader", - "androidx.localbroadcastmanager_localbroadcastmanager", - "androidx.preference_preference", - "androidx.recyclerview_recyclerview", "SettingsLib", ], } diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml deleted file mode 100644 index 38a7d6ace1f8..000000000000 --- a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest package="com.android.settingslib.widget" /> diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp index 33aa985b32e8..4871ef3097a5 100644 --- a/packages/SettingsLib/MainSwitchPreference/Android.bp +++ b/packages/SettingsLib/MainSwitchPreference/Android.bp @@ -9,6 +9,7 @@ package { android_library { name: "SettingsLibMainSwitchPreference", + use_resource_processor: true, srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 56b3eacf6c1f..600115545a16 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -31,12 +31,11 @@ import android.widget.TextView; import androidx.annotation.ColorInt; import com.android.settingslib.utils.BuildCompatUtils; +import com.android.settingslib.widget.mainswitch.R; import java.util.ArrayList; import java.util.List; -import com.android.settingslib.widget.mainswitch.R; - /** * MainSwitchBar is a View with a customized Switch. * This component is used as the main switch of the page @@ -77,7 +76,7 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec final TypedArray a = context.obtainStyledAttributes( new int[]{android.R.attr.colorAccent}); mBackgroundActivatedColor = a.getColor(0, 0); - mBackgroundColor = context.getColor(R.color.material_grey_600); + mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600); a.recycle(); } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt index 0552c408a8eb..1ad075c11985 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt @@ -31,7 +31,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Launch import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalButton @@ -52,6 +51,7 @@ import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsShape import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.divider +import androidx.compose.material.icons.automirrored.outlined.Launch data class ActionButton( val text: String, @@ -101,7 +101,9 @@ private fun RowScope.ActionButton(actionButton: ActionButton) { modifier = Modifier.size(SettingsDimension.itemIconSize), ) Box( - modifier = Modifier.padding(top = 4.dp).fillMaxHeight(), + modifier = Modifier + .padding(top = 4.dp) + .fillMaxHeight(), contentAlignment = Alignment.Center, ) { Text( @@ -129,7 +131,7 @@ private fun ActionButtonsPreview() { SettingsTheme { ActionButtons( listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {}, ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {}, ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {}, ) 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 62189dccc9bf..6ef45900a103 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 @@ -18,7 +18,6 @@ package com.android.settingslib.spa.widget.scaffold import androidx.appcompat.R import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.FindInPage import androidx.compose.material3.Icon @@ -31,6 +30,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection import com.android.settingslib.spa.framework.compose.LocalNavController +import androidx.compose.material.icons.automirrored.outlined.ArrowBack /** Action that navigates back to last page. */ @Composable @@ -53,7 +53,7 @@ internal fun CollapseAction(onClick: () -> Unit) { private fun BackAction(contentDescription: String, onClick: () -> Unit) { IconButton(onClick) { Icon( - imageVector = Icons.Outlined.ArrowBack, + imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = contentDescription, modifier = Modifier.autoMirrored(), ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt index aa148b022b92..9f7f040be7ce 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt @@ -21,7 +21,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.TabRow +import androidx.compose.material3.PrimaryTabRow import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -43,7 +43,7 @@ fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit val coroutineScope = rememberCoroutineScope() val pagerState = rememberPagerState { titles.size } - TabRow( + PrimaryTabRow( selectedTabIndex = pagerState.currentPage, modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd), containerColor = Color.Transparent, diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt index f59b0decf1e5..8d9bac64b078 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt @@ -17,8 +17,8 @@ package com.android.settingslib.spa.widget.button import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.outlined.Launch import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -43,7 +43,10 @@ class ActionButtonsTest { composeTestRule.setContent { ActionButtons( listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton( + text = "Open", + imageVector = Icons.AutoMirrored.Outlined.Launch + ) {}, ) ) } @@ -57,7 +60,7 @@ class ActionButtonsTest { composeTestRule.setContent { ActionButtons( listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) { + ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) { clicked = true }, ) @@ -74,7 +77,10 @@ class ActionButtonsTest { composeTestRule.setContent { ActionButtons( listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton( + text = "Open", + imageVector = Icons.AutoMirrored.Outlined.Launch + ) {}, ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {}, ) ) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt index dfd8f6b8e373..352503742b9c 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt @@ -18,8 +18,10 @@ package com.android.settingslib.spaprivileged.model.enterprise import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER +import android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER import android.content.Context +import android.content.pm.UserInfo import com.android.settingslib.R class EnterpriseRepository(private val context: Context) { @@ -30,8 +32,10 @@ class EnterpriseRepository(private val context: Context) { fun getEnterpriseString(updatableStringId: String, resId: Int): String = checkNotNull(resources.getString(updatableStringId) { context.getString(resId) }) - fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) { + fun getProfileTitle(userInfo: UserInfo): String = if (userInfo.isManagedProfile) { getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work) + } else if (userInfo.isPrivateProfile) { + getEnterpriseString(PRIVATE_CATEGORY_HEADER, R.string.category_private) } else { getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt index 6a76c93ac9a9..5447f21fd417 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt @@ -45,7 +45,7 @@ fun UserProfilePager(content: @Composable (userGroup: UserGroup) -> Unit) { val enterpriseRepository = EnterpriseRepository(context) userGroups.map { userGroup -> enterpriseRepository.getProfileTitle( - isManagedProfile = userGroup.userInfos.first().isManagedProfile, + userGroup.userInfos.first(), ) } } diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig index d1bcb5746414..4936f882f91e 100644 --- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig +++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig @@ -5,4 +5,11 @@ flag { namespace: "media_solutions" description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation." bug: "192657812" +} + +flag { + name: "enable_tv_media_output_dialog" + namespace: "tv_system_ui" + description: "Gates all the changes for the tv specific media output dialog" + bug: "303205631" }
\ No newline at end of file diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index d9596569a0da..431fd44cd952 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -18,18 +18,5 @@ # to the corresponding module. # NOTE: keep this file and ./Android.bp in sync. -LOCAL_STATIC_JAVA_LIBRARIES += \ - androidx.annotation_annotation - LOCAL_STATIC_ANDROID_LIBRARIES += \ - androidx.appcompat_appcompat \ - androidx.coordinatorlayout_coordinatorlayout \ - androidx.core_core \ - androidx.fragment_fragment \ - androidx.lifecycle_lifecycle-runtime \ - androidx.loader_loader \ - androidx.localbroadcastmanager_localbroadcastmanager \ - androidx.preference_preference \ - androidx.recyclerview_recyclerview \ SettingsLib - diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 53db19207947..afdb92b1bbab 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi twee stawe."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi drie stawe."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-sein vol."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Gekoppel aan jou toestel."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Oop netwerk"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Veilige netwerk"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android-bedryfstelsel"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 77c9a970ce43..ea8491b2138c 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ሁለት የWiFi አሞሌዎች።"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ሦስት የWiFi አሞሌዎች።"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"የWiFi ምልክት ሙሉ ነው።"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ከመሣሪያዎ ጋር ተገናኝቷል።"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"አውታረ መረብ ክፈት"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ደህንነቱ የተጠበቀ አውታረ መረብ"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android ስርዓተ ክወና"</string> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 30d011aa2b58..c29e6911396b 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"إشارة Wi-Fi تتكون من شريطين."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"إشارة Wi-Fi تتكون من ثلاثة أشرطة."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"إشارة Wi-Fi كاملة."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"تم الاتصال بجهازك."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"شبكة مفتوحة"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"شبكة محمية بكلمة مرور"</string> <string name="process_kernel_label" msgid="950292573930336765">"نظام التشغيل Android"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index 31aff6a136f6..8eff4f6eba44 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ৱাই-ফাইৰ দুডাল দণ্ড।"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ৱাই-ফাইৰ তিনিডাল দণ্ড।"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ৱাই-ফাই সংকেত সৰ্বোচ্চ।"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"আপোনাৰ ডিভাইচটোৰ সৈতে সংযোগ কৰা আছে।"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"মুক্ত নেটৱৰ্ক"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"সুৰক্ষিত নেটৱৰ্ক"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index c675ce797984..dfd1b7b83204 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi iki xətdir."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi üç xətdir."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi siqnalı tamdır."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Cihaza qoşuldu."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Açıq şəbəkə"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Təhlükəsiz şəbəkə"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index f7be60fe8be2..528e96e4c747 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi signal ima dve crte."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi signal ima tri crte."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi signal je najjači."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezano je sa uređajem."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Bezbedna mreža"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index d2f2851f58a0..52614b7df160 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Два слупкi Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Тры слупкi Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Поўны сігнал Wi-Fi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Устаноўлена падключэнне да прылады."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Адкрытая сетка"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Бяспечная сетка"</string> <string name="process_kernel_label" msgid="950292573930336765">"АС Android"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 2540c129dd53..b28ae6742f68 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi е с две чертички."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi е с три чертички."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Сигналът за Wi-Fi е пълен."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Установена е връзка с устройството ви."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Защитена мрежа"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android (ОС)"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 745b9446a1ab..c91f14ca2fad 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ওয়াই ফাই এ দুইটি দণ্ড৷"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ওয়াই ফাই এ তিনটি দণ্ড৷"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ওয়াই ফাই এ সম্পূর্ণ সিগন্যাল৷"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"আপনার ডিভাইসের সাথে কানেক্ট করা হয়েছে।"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"খোলা নেটওয়ার্ক"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"সুরক্ষিত নেটওয়ার্ক"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index cab0841dd423..aa1073d74d40 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi signal ima dvije crte."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi signal ima tri crte."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi signal je pun."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezani ste s uređajem."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sigurna mreža"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index a65b9c2b60d8..ab6393a7d407 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Senyal Wi-Fi: dues barres."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Senyal Wi-Fi: tres barres."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Senyal Wi-Fi: complet."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"S\'ha connectat al teu dispositiu."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Xarxa oberta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Xarxa segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 96c02aed1a65..e7d524317bae 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi – dvě čárky."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi – tři čárky."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi – plný signál."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Připojeno k vašemu zařízení."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Nezabezpečená síť"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Zabezpečená síť"</string> <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 1e4c7b7b04fb..1612ac030205 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi har to bjælker."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi har tre bjælker."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi har fuldt signal."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Der er oprettet forbindelse til din enhed."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Åbent netværk"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sikkert netværk"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index 37f7e5aa732a..73a44f140cbf 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WLAN: zwei Balken"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WLAN: drei Balken"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WLAN: volle Signalstärke"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Mit deinem Gerät verbunden."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Offenes Netzwerk"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sicheres Netzwerk"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 84b8bf221c40..7dfd679ffacf 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Δύο γραμμές Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Τρεις γραμμές Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Άριστο σήμα Wi-Fi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Συνδέθηκε στη συσκευή σας."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ανοικτό δίκτυο"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Ασφαλές δίκτυο"</string> <string name="process_kernel_label" msgid="950292573930336765">"Λειτουργικό σύστημα Android"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 7298c022e702..aead40d12072 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 7298c022e702..aead40d12072 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 7298c022e702..aead40d12072 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 86541fd5b294..291a68bc14fc 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dos barras de Wi-Fi"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de Wi-Fi"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Señal de Wi-Fi excelente"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Se estableció conexión con el dispositivo."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Red abierta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Red segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index ef9cc03421d2..b4bc31913902 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dos barras de Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Señal de Wi-Fi al máximo."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado a tu dispositivo."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Red abierta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Red segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index c8d806b21bd4..e5cbaf61fbb5 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi: kaks pulka."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi: kolm pulka."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi-signaal on tugev."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Seadmega ühendatud."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Avatud võrk"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Turvaline võrk"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml index 95adf968af12..00f43a3bf51b 100644 --- a/packages/SettingsLib/res/values-eu/arrays.xml +++ b/packages/SettingsLib/res/values-eu/arrays.xml @@ -264,7 +264,7 @@ <string-array name="debug_hw_overdraw_entries"> <item msgid="1968128556747588800">"Desaktibatuta"</item> <item msgid="3033215374382962216">"Erakutsi gainidatzi diren eremuak"</item> - <item msgid="3474333938380896988">"Erakutsi daltonismorako eremuak"</item> + <item msgid="3474333938380896988">"Erakutsi deuteranomaliarako eremuak"</item> </string-array> <string-array name="app_process_limit_entries"> <item msgid="794656271086646068">"Muga estandarra"</item> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 7d76514f2ef0..90d45eb44fdc 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi sarearen bi barra."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi sarearen hiru barra."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi sarearen seinalea osoa."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Gailura konektatuta."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Sare irekia"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sare segurua"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android sistema eragilea"</string> @@ -368,7 +367,7 @@ <string name="debug_hw_overdraw" msgid="8944851091008756796">"Araztu GPU gainidazketa"</string> <string name="disable_overlays" msgid="4206590799671557143">"Desgaitu HW gainjartzeak"</string> <string name="disable_overlays_summary" msgid="1954852414363338166">"Erabili beti GPUa pantaila-muntaietarako"</string> - <string name="simulate_color_space" msgid="1206503300335835151">"Simulatu kolore-eremua"</string> + <string name="simulate_color_space" msgid="1206503300335835151">"Simulatu kolore-espazioa"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Gaitu OpenGL aztarnak"</string> <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Desgaitu USB bidez audioa bideratzeko aukera"</string> <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Desgaitu USB bidezko audio-gailuetara automatikoki bideratzeko aukera"</string> @@ -441,7 +440,7 @@ <string name="picture_color_mode_desc" msgid="151780973768136200">"Erabili sRGB"</string> <string name="daltonizer_mode_disabled" msgid="403424372812399228">"Desgaituta"</string> <string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Ikusmen-monokromia"</string> - <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Daltonismoa (gorri-berdeak)"</string> + <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomalia (gorri-berdeak)"</string> <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (urdin-horia)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index beb0b968b338..f3f97c460780 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"دو نوار برای Wi‑Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"سه نوار برای Wi‑Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"قدرت سیگنال Wi‑Fi کامل است."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"به دستگاهتان متصل شد."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"شبکه باز"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"شبکه ایمن"</string> <string name="process_kernel_label" msgid="950292573930336765">"سیستمعامل Android"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 88723162ae7d..7f3d0f286695 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-signaali – kaksi palkkia"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-signaali – kolme palkkia"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Vahva Wi-Fi-signaali"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Yhdistetty laitteeseesi."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Avoin verkko"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Suojattu verkko"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android-käyttöjärjestelmä"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 98e3753376e3..b7c3a81b47cd 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi : deux barres."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi : trois barres."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi : signal complet."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connecté à votre appareil."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Réseau ouvert"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Réseau sécurisé"</string> <string name="process_kernel_label" msgid="950292573930336765">"Système d\'exploitation Android"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 4a64041192a3..a9e0c6a5ccb5 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Signal Wi-Fi moyen"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Signal Wi-Fi bon"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Signal Wi-Fi excellent"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connecté à votre appareil."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Réseau ouvert"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Réseau sécurisé"</string> <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index 26738e6f54e7..09c2969bac2d 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dúas barras de wifi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de wifi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal completo de wifi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conexión establecida co teu dispositivo."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index b620d4184b8e..575c67510e89 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi બે બાર."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ત્રણ બાર."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"પૂર્ણ Wifi સિગ્નલ."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"તમારા ડિવાઇસ સાથે કનેક્ટેડ છે."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"નેટવર્ક ખોલો"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"સુરક્ષિત નેટવર્ક"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 3811167c9c49..655fac041c9b 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"वाई-फ़ाई की दो पट्टी मिल रही हैं."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"वाई-फ़ाई की एक पट्टी मिल रही है."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"पूरे वाई-फ़ाई सिग्नल मिल रहे हैं."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"आपके डिवाइस से कनेक्ट हो गया है."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"खुला नेटवर्क"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> @@ -598,7 +597,7 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"प्रोफ़ाइल की जानकारी"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"इससे पहले कि आप कोई प्रतिबंधित प्रोफ़ाइल बनाएं, आपको अपने ऐप्लिकेशन और व्यक्तिगत डेटा की सुरक्षा करने के लिए एक स्क्रीन लॉक सेट करना होगा."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करें"</string> - <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर जाएं"</string> + <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर स्विच करें"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नए उपयोगकर्ता को जोड़ा जा रहा है…"</string> <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"नया मेहमान खाता बनाया जा रहा है…"</string> <string name="add_user_failed" msgid="4809887794313944872">"नया उपयोगकर्ता जोड़ा नहीं जा सका"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 71a2daeb901c..f0e16a47346f 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi signal ima dva stupca."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi signal ima tri stupca."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal je pun."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezano s vašim uređajem."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sigurna mreža"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> @@ -619,7 +618,7 @@ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Time će se izbrisati aplikacije i podaci iz trenutačne gostujuće sesije."</string> <string name="grant_admin" msgid="4323199171790522574">"Da, dodijeli status administratora"</string> <string name="not_grant_admin" msgid="3557849576157702485">"Ne, nemoj dodijeliti status administratora"</string> - <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izlaz"</string> + <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izađi"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Želite li spremiti aktivnosti gosta?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Možete spremiti aktivnosti iz ove sesije ili izbrisati sve aplikacije i podatke"</string> <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Izbriši"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 5fac75da75ab..420e0e969fd9 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-jel: két sáv."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-jel: három sáv."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-jel: teljes."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Csatlakoztatva az eszközhöz."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Nyílt hálózat"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Biztonságos hálózat"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index e40c5e164110..705fcf6637b2 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-ի ուժգնությունը՝ երկու գիծ:"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-ի ուժգնությունը՝ երեք գիծ:"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-ի ազդանշանը ուժեղ է:"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Միացած է ձեր սարքին։"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Բաց ցանց"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Անվտանգ ցանց"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index ca1b6be5b478..e33dd05264c3 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi dua baris"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi tiga baris."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinyal Wi-Fi penuh."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Terhubung ke perangkat Anda."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Jaringan terbuka"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Jaringan aman"</string> <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 1f2dcfb7c790..e1fb248604c2 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: Tvö strik."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: Þrjú strik."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Fullur Wi-Fi sendistyrkur."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Tengt við tækið þitt."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Opið net"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Öruggt net"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android stýrikerfið"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index e255c85a7d69..b2cfd371fe25 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: due barre."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: tre barre."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Segnale Wi-Fi completo."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connessione al dispositivo effettuata."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rete aperta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rete protetta"</string> <string name="process_kernel_label" msgid="950292573930336765">"Sistema operativo Android"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index edcac10e8f24..9ca74778d978 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"שני פסים של Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"שלושה פסים של Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"אות Wi-Fi מלא."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"מחובר למכשיר שלך."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"רשת פתוחה"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"רשת מאובטחת"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index bf21de291e6a..dcce2b4436aa 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fiはレベル2です。"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fiはレベル3です。"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fiの電波はフルです。"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"デバイスに接続しました。"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"オープンネットワーク"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"保護されたネットワーク"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 9000de4c990b..eb32e1ce2a8d 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi სიგნალი ორ ზოლზეა."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi სიგნალი სამ ზოლზეა."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi სიგნალი სრულია."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"დაკავშირებულია თქვენს მოწყობილობასთან."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ღია ქსელი"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"დაცული ქსელი"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 610fed1f6550..73a8efde7a97 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi сигналы — екі жолақ."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi сигналы — үш жолақ."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi сигналы толық."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Құрылғыңызға жалғанды."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ашық желі"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Қауіпсіз желі"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index b7999c73f33a..1c32e626f1b6 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi ពីរកាំ"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi បីកាំ"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"សេវា Wifi ពេញ"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"បានភ្ជាប់ទៅឧបករណ៍របស់អ្នក។"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"បើកបណ្ដាញ"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"បណ្តាញដែលមានសុវត្ថិភាព"</string> <string name="process_kernel_label" msgid="950292573930336765">"ប្រព័ន្ធប្រតិបត្តិការ Android"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index c788dd5fc8bd..ede347db975f 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ವೈಫೈ ಎರಡು ಪಟ್ಟಿಗಳು."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ವೈಫೈ ಮೂರು ಪಟ್ಟಿಗಳು."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ವೈಫೈ ಸಿಗ್ನಲ್ ಪೂರ್ತಿ ಇದೆ."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ನೆಟ್ವರ್ಕ್ ತೆರೆಯಿರಿ"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ಸುರಕ್ಷಿತ ನೆಟ್ವರ್ಕ್"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 2c51d1f9edd1..78bd616dffe2 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 신호 막대가 두 개입니다."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 신호 막대가 세 개입니다."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 신호가 강합니다."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"기기에 연결되었습니다."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"개방형 네트워크"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"보안 네트워크"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 14814f4ed6e8..a0b91230713d 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: эки таякча."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: үч таякча."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi: күчтүү сигнал."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Түзмөгүңүзгө туташты."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ачык тармак"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Коопсуз тармак"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 6430a9889e1a..96b2dc1a27cb 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ສັນຍານ Wi-Fi ສອງຂີດ."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ສາມຂີດ."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ສັນຍານ Wi-Fi ເຕັມ"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ເຊື່ອມຕໍ່ກັບອຸປະກອນຂອງທ່ານແລ້ວ."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ເຄືອຂ່າຍເປີດ"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ເຄືອຂ່າຍເຂົ້າລະຫັດ"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 3642a9091545..69630f436704 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dvi „Wi-Fi“ signalo juostos."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Trys „Wi-Fi“ signalo juostos."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Stiprus „Wi-Fi“ signalas."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Prisijungta prie įrenginio."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Atviras tinklas"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Saugus tinklas"</string> <string name="process_kernel_label" msgid="950292573930336765">"„Android“ OS"</string> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index c3163b8efe1e..4f76ceeb2cca 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: divas joslas"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: trīs joslas"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Pilna piekļuve Wi-Fi signālam"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Izveidots savienojums ar ierīci."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Atvērts tīkls"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Drošs tīkls"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 017fed7a5524..1c80c6502fc1 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Две црти на Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Три црти на Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Полн сигнал на Wi-Fi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Поврзано со уредот."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Заклучена мрежа"</string> <string name="process_kernel_label" msgid="950292573930336765">"Оперативен систем Android"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index 3ef3f63d32b7..31e3c83fb32f 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"വൈഫൈ സിഗ്നൽ രണ്ട് ബാറുകൾ."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"വൈഫൈ സിഗ്നൽ മൂന്ന് ബാറുകൾ."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"വൈഫൈ മികച്ച സിഗ്നൽ."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"നിങ്ങളുടെ ഉപകരണത്തിലേക്ക് കണക്റ്റ് ചെയ്തു."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ഓപ്പൺ നെറ്റ്വര്ക്ക്"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"സുരക്ഷിത നെറ്റ്വര്ക്ക്"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index ccd1d4179ffb..5a636d9c8260 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi сүлжээний дохио хоёр баганатай байна."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi сүлжээний дохио гурван баганатай байна."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi-н дохио дүүрэн байна."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Таны төхөөрөмжид холбогдсон."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Нээлттэй сүлжээ"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Аюулгүй сүлжээ"</string> <string name="process_kernel_label" msgid="950292573930336765">"Андройд OS"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index a34a9d327969..4d78e571b47d 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"वाय-फाय दोन बार."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"वाय-फाय तीन बार."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"वाय-फाय सिग्नल संपूर्ण आहे."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"तुमच्या डिव्हाइसशी कनेक्ट केले आहे."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"नेटवर्क उघडा"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index b7d273409390..2ae69bdff241 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi dua bar."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi tiga bar."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Isyarat Wi-Fi penuh."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Disambungkan kepada peranti anda."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rangkaian terbuka"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rangkaian selamat"</string> <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 0e43f6516f25..7d6f2a75ba5a 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi ၂ ဘား"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi ၃ ဘား"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi အပြည့်ရှိ"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"သင့်စက်သို့ ချိတ်ဆက်ထားသည်။"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"အများသုံး ကွန်ရက်"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"လုံခြုံသည့် ကွန်ရက်"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 43824c3ebc1a..9e550fb288e9 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi-signal med to stolper."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi-signal med tre stolper."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi-signalet er ved full styrke."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Koblet til enheten."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Åpent nettverk"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sikkert nettverk"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android-operativsystem"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index e879849f5f29..1f6ad5bb863c 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi दुई पट्टि।"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi तीन बारहरू।"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"पूर्ण Wi-Fi सिंग्नल।"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"तपाईंको डिभाइसमा कनेक्ट गरिएको छ।"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"खुला नेटवर्क"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index dedacff6daf3..6a6f184f77d1 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: twee streepjes."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: drie streepjes."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifii-signaal is op volledige sterkte."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Verbonden met je apparaat."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open netwerk"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Beveiligd netwerk"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android-besturingssysteem"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index cbd9ffd50771..980a37413f8b 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ୱାଇ-ଫାଇର ଦୁଇଟି ବାର୍ ଅଛି।"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ୱାଇ-ଫାଇର ତିନୋଟି ବାର୍।"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ୱାଇ-ଫାଇର ସଙ୍କେତ ସର୍ବୋଚ୍ଚ।"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ଆପଣଙ୍କ ଡିଭାଇସ ସହ କନେକ୍ଟ କରାଯାଇଛି।"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ଖୋଲା ନେଟୱର୍କ"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ସୁରକ୍ଷିତ ନେଟ୍ୱର୍କ"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index e4814acdfb0c..dd3fa15440e1 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi ਦੋ ਬਾਰ।"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ਤਿੰਨ ਬਾਰ।"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi ਸਿਗਨਲ ਪੂਰਾ।"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ।"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ਖੁੱਲ੍ਹਾ ਨੈੱਟਵਰਕ"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ਸੁਰੱਖਿਅਤ ਨੈੱਟਵਰਕ"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index f5dc9cc7e202..f4ebd29b1276 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: dwa paski."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: trzy paski."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: pełna moc sygnału."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Połączono z urządzeniem."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Sieć otwarta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sieć zabezpieczona"</string> <string name="process_kernel_label" msgid="950292573930336765">"System operacyjny Android"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 4e96bc615c3d..bb6c7d85fe62 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal Wi-Fi cheio."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado ao dispositivo."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"Sistema operacional Android"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 0c4abdbefda2..6bd5c2abccda 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal de Wi-Fi completo."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Com ligação ao dispositivo."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 4e96bc615c3d..bb6c7d85fe62 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal Wi-Fi cheio."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado ao dispositivo."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"Sistema operacional Android"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 529a6c646979..f4399dc4e829 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Semnal Wi-Fi: două bare."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Semnal Wi-Fi: trei bare."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Semnal Wi-Fi: complet."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectată la dispozitiv."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rețea nesecurizată"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Securizează rețeaua"</string> <string name="process_kernel_label" msgid="950292573930336765">"Sistem de operare Android"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 87a81ee73faf..5d45e29ef9b6 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: два деления"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: три деления"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: надежный сигнал"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Подключено к точке доступа на вашем устройстве."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Открытая сеть"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Защищенная сеть"</string> <string name="process_kernel_label" msgid="950292573930336765">"ОС Android"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index 9015a428de38..996507d8c2f6 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi තීරු දෙකයි."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi තීරු තුනයි."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi සංඥාව පිරී ඇත."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ඔබේ උපාංගයට සම්බන්ධයි."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"විවෘත ජාලය"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ආරක්ෂිත ජාලය"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 8c6bf4fcb887..a51acd4fb227 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dve čiarky signálu Wi‑Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tri čiarky signálu Wi‑Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Plný signál Wi‑Fi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Pripojené k vášmu zariadeniu."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorená sieť"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Zabezpečená sieť"</string> <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index cc19abe8be24..0d7188dcbc50 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dve črtici signala Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tri črtice signala Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Poln signal Wi-Fi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezava z napravo je vzpostavljena."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Odprto omrežje"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Varno omrežje"</string> <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 43a2c4c19fd1..c2bcce79b061 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi ka dy vija."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: tre vija."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi ka sinjal të plotë."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Lidhur me pajisjen tënde"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rrjet i hapur"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rrjet i sigurt"</string> <string name="process_kernel_label" msgid="950292573930336765">"Sistemi operativ Android"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 00a98bca2383..09ff994477e1 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi сигнал има две црте."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi сигнал има три црте."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi сигнал је најјачи."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Повезано је са уређајем."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Безбедна мрежа"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android ОС"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 01b462da0165..fdd9d8c17d57 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: två staplar."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: tre staplar."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Full signalstyrka för wifi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Ansluten till enheten."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Öppet nätverk"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Säkert nätverk"</string> <string name="process_kernel_label" msgid="950292573930336765">"Operativsystemet Android"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 9453e5f84c6c..bd4175289585 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Vipima mtandao viwili vya Wifi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Vipima mtandao vitatu vya Wifi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Nguvu kamili ya mtandao wa Wifi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Imeunganishwa kwenye kifaa chako."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Mtandao unaotumiwa na mtu yeyote"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Mtandao salama"</string> <string name="process_kernel_label" msgid="950292573930336765">"Mfumo wa Uendeshaji wa Android"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 836b4da9b8a7..fd379f0a80be 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"வைஃபை சிக்னல்: இரண்டு கோடுகள்."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"வைஃபை சிக்னல்: மூன்று கோடுகள்."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"வைஃபை சிக்னல் முழுமையாக உள்ளது."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"உங்கள் சாதனத்துடன் இணைக்கப்பட்டது."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"கடவுச்சொல் தேவைப்படாத திறந்த நெட்வொர்க்"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"கடவுச்சொல் தேவைப்படும் பாதுகாப்பான நெட்வொர்க்"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 34b826441dbc..3e9d8be3cc1b 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi సిగ్నల్ రెండు బార్లు ఉంది."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi సిగ్నల్ మూడు బార్లు ఉంది."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi సిగ్నల్ పూర్తిగా ఉంది."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"మీ పరికరానికి కనెక్ట్ చేయబడింది."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ఓపెన్ నెట్వర్క్"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"సురక్షిత నెట్వర్క్"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index da3a5c5d09c6..e67f37172bd7 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"สัญญาณ Wi-Fi 2 ขีด"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"สัญญาณ Wi-Fi 3 ขีด"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"สัญญาณ Wi-Fi เต็ม"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"เชื่อมต่อกับอุปกรณ์แล้ว"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"เครือข่ายแบบเปิด"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"เครือข่ายที่ปลอดภัย"</string> <string name="process_kernel_label" msgid="950292573930336765">"ระบบปฏิบัติการ Android"</string> @@ -598,7 +597,7 @@ <string name="profile_info_settings_title" msgid="105699672534365099">"ข้อมูลโปรไฟล์"</string> <string name="user_need_lock_message" msgid="4311424336209509301">"ก่อนที่คุณจะสามารถสร้างโปรไฟล์ที่ถูกจำกัดได้ คุณจะต้องตั้งค่าล็อกหน้าจอเพื่อปกป้องแอปและข้อมูลส่วนตัวของคุณ"</string> <string name="user_set_lock_button" msgid="1427128184982594856">"ตั้งค่าล็อก"</string> - <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น <xliff:g id="USER_NAME">%s</xliff:g>"</string> + <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น<xliff:g id="USER_NAME">%s</xliff:g>"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string> <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"กำลังสร้างผู้ใช้ชั่วคราวใหม่…"</string> <string name="add_user_failed" msgid="4809887794313944872">"สร้างผู้ใช้ใหม่ไม่ได้"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index f6d2256c3e4e..440bbe768c05 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"May dalawang bar ang Wifi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"May tatlong bar ang Wifi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Puno ang signal ng Wifi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Nakakonekta sa iyong device."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Bukas na network"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Ligtas na network"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index acd3d00e56e6..b38012fbbd8c 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Kablosuz sinyal gücü iki çubuk."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Kablosuz sinyal gücü üç çubuk."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Kablosuz sinyal gücü tam."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Cihazınıza bağlandı."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Açık ağ"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Güvenli ağ"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index dd5898adc3c5..4f0854790e92 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Дві смужки сигналу Wi-Fi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Три смужки сигналу Wi-Fi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Максимальний сигнал Wi-Fi."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Підключено до пристрою."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Відкрита мережа"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Захищена мережа"</string> <string name="process_kernel_label" msgid="950292573930336765">"ОС Android"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index 182bd0496527..ce67d1516273 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi دو بارز۔"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi تین بارز۔"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi سگنل پورا ہے۔"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"آپ کے آلے سے منسلک ہے۔"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"اوپن نیٹ ورک"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"محفوظ نیٹ ورک"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index b831a4b40bb9..77da981b29a9 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: ikkita ustun"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: uchta ustun"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: signal to‘liq"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Qurilmaga ulandi."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ochiq tarmoq"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Xavfsiz tarmoq"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 3e94593351a2..bf510f6522f3 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Tín hiệu Wi-Fi hai vạch."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tín hiệu Wi-Fi ba vạch."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Tín hiệu Wi-Fi đủ."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Đã kết nối với thiết bị của bạn."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Mạng mở"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Mạng bảo mật"</string> <string name="process_kernel_label" msgid="950292573930336765">"Hệ điều hành Android"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 53389c790974..8e3145ace05a 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WLAN 信号强度为两格。"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WLAN 信号强度为三格。"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WLAN 信号满格。"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已连接到您的设备。"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"开放网络"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全网络"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android 操作系统"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index daedba67c378..aa9f21f28b91 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 訊號兩格。"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 訊號三格。"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 訊號滿格。"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已連接裝置。"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"開放式網絡"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全網絡"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android 作業系統"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 94ebd9852e85..3c65a4d7d5ee 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 訊號強度兩格。"</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 訊號強度三格。"</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 訊號強度滿格。"</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已連上裝置。"</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"開放式網路"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全網路"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android 作業系統"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 92a4b81e80f7..08b04cc4b1bf 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -157,8 +157,7 @@ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Amabha amabili we-Wifi."</string> <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Amabha amathathu we-Wifi."</string> <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Isiginali ye-Wifi igcwele."</string> - <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) --> - <skip /> + <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Ixhume kudivayisi yakho."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Vula inethiwekhi"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Inethiwekhi evikelekile"</string> <string name="process_kernel_label" msgid="950292573930336765">"I-Android OS"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 49bd9d994088..96876747c082 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -518,6 +518,8 @@ <string name="category_personal">Personal</string> <!-- Header for items under the work user [CHAR LIMIT=30] --> <string name="category_work">Work</string> + <!-- Header for items under the private profile user [CHAR LIMIT=30] --> + <string name="category_private">Private</string> <!-- Header for items under the clone user [CHAR LIMIT=30] --> <string name="category_clone">Clone</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 412a3424bbd1..ce0772ff84a1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.launcher3.icons.BaseIconFactory.IconOptions; import com.android.launcher3.icons.IconFactory; +import com.android.launcher3.util.UserIconInfo; import com.android.settingslib.drawable.UserIconDrawable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.settingslib.utils.BuildCompatUtils; @@ -67,8 +68,7 @@ public class Utils { static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled"; - @VisibleForTesting - static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = + public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = "incompatible_charger_warning_disabled"; private static Signature[] sSystemSignature; @@ -598,15 +598,25 @@ public class Utils { /** Get the corresponding adaptive icon drawable. */ public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) { - UserManager um = context.getSystemService(UserManager.class); - boolean isClone = um.getProfiles(user.getIdentifier()).stream() - .anyMatch(profile -> - profile.isCloneProfile() && profile.id == user.getIdentifier()); + int userType = UserIconInfo.TYPE_MAIN; + try { + UserInfo ui = context.getSystemService(UserManager.class).getUserInfo( + user.getIdentifier()); + if (ui != null) { + if (ui.isCloneProfile()) { + userType = UserIconInfo.TYPE_CLONED; + } else if (ui.isManagedProfile()) { + userType = UserIconInfo.TYPE_WORK; + } + } + } catch (Exception e) { + // Ignore + } try (IconFactory iconFactory = IconFactory.obtain(context)) { return iconFactory .createBadgedIconBitmap( icon, - new IconOptions().setUser(user).setIsCloneProfile(isClone)) + new IconOptions().setUser(new UserIconInfo(user, userType))) .newIcon(context); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java deleted file mode 100644 index a78440c271c9..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.deviceinfo; - -import android.content.Context; -import android.os.UserManager; - -import com.android.settingslib.Utils; -import com.android.settingslib.core.AbstractPreferenceController; - -public abstract class AbstractSimStatusImeiInfoPreferenceController - extends AbstractPreferenceController { - public AbstractSimStatusImeiInfoPreferenceController(Context context) { - super(context); - } - - @Override - public boolean isAvailable() { - return mContext.getSystemService(UserManager.class).isAdminUser() - && !Utils.isWifiOnly(mContext); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 7f1f3f613bce..2032328cbc04 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -110,7 +110,8 @@ public class BatteryStatus { } /** - * Determine whether the device is plugged in wireless. */ + * Determine whether the device is plugged in wireless. + */ public boolean isPluggedInWireless() { return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; } @@ -185,6 +186,22 @@ public class BatteryStatus { return status == BATTERY_STATUS_FULL || level >= 100; } + /** + * Whether or not the device is charged. Note that some devices never return 100% for battery + * level, so this allows either battery level or status to determine if the battery is charged. + * + * @param status the value from extra {@link BatteryManager.EXTRA_STATUS} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param level the value from extra {@link BatteryManager.EXTRA_LEVEL} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param scale the value from extra {@link BatteryManager.EXTRA_SCALE} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + */ + public static boolean isCharged(int status, int level, int scale) { + var batteryLevel = getBatteryLevel(level, scale); + return isCharged(status, batteryLevel); + } + /** Gets the battery level from the intent. */ public static int getBatteryLevel(Intent batteryChangedIntent) { if (batteryChangedIntent == null) { @@ -193,6 +210,14 @@ public class BatteryStatus { final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN); final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + return getBatteryLevel(level, scale); + } + + /** + * Gets the battery level from the value of {@link Intent.BATTERY_CHANGED_INTENT}'s EXTRA_LEVEL + * and EXTRA_SCALE. + */ + public static int getBatteryLevel(int level, int scale) { return scale == 0 ? BATTERY_LEVEL_UNKNOWN : Math.round((level / (float) scale) * 100f); @@ -253,11 +278,22 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * SEVERE_LOW_BATTERY_THRESHOLD} + * SEVERE_LOW_BATTERY_THRESHOLD} */ public static boolean isSevereLowBattery(Intent batteryChangedIntent) { - int level = getBatteryLevel(batteryChangedIntent); - return level <= SEVERE_LOW_BATTERY_THRESHOLD; + int batteryLevel = getBatteryLevel(batteryChangedIntent); + return isSevereLowBattery(batteryLevel); + } + + /** + * Whether the battery is severe low or not. + * + * @param batteryLevel the value of battery level + * @return {@code true} if the battery level is less or equal to {@link + * SEVERE_LOW_BATTERY_THRESHOLD} + */ + public static boolean isSevereLowBattery(int batteryLevel) { + return batteryLevel <= SEVERE_LOW_BATTERY_THRESHOLD; } /** @@ -265,11 +301,21 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * EXTREME_LOW_BATTERY_THRESHOLD} + * EXTREME_LOW_BATTERY_THRESHOLD} */ public static boolean isExtremeLowBattery(Intent batteryChangedIntent) { int level = getBatteryLevel(batteryChangedIntent); - return level <= EXTREME_LOW_BATTERY_THRESHOLD; + return isExtremeLowBattery(level); + } + + /** + * Whether the battery is extreme low or not. + * + * @return {@code true} if the {@code batteryLevel} is less or equal to + * {@link EXTREME_LOW_BATTERY_THRESHOLD} + */ + public static boolean isExtremeLowBattery(int batteryLevel) { + return batteryLevel <= EXTREME_LOW_BATTERY_THRESHOLD; } /** @@ -277,7 +323,7 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell - * defend, or temp defend + * defend, or temp defend */ public static boolean isBatteryDefender(Intent batteryChangedIntent) { int chargingStatus = @@ -298,9 +344,8 @@ public class BatteryStatus { } /** - * Gets the max charging current and max charging voltage form {@link - * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link - * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}. + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. * * @param context the application context * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED} @@ -308,7 +353,29 @@ public class BatteryStatus { * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} */ public static int getChargingSpeed(Context context, Intent batteryChangedIntent) { - final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent); + final int maxChargingMicroCurrent = + batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); + int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateChargingSpeed(context, maxChargingMicroCurrent, maxChargingMicroVolt); + } + + /** + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. + * + * @param maxChargingMicroCurrent the max charging micro current that is retrieved form the + * extra of {@link Intent.Action_BATTERY_CHANGED} + * @param maxChargingMicroVolt the max charging micro voltage that is retrieved form the extra + * of {@link Intent.Action_BATTERY_CHANGED} + * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link + * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} + */ + public static int calculateChargingSpeed( + Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt) { + final int maxChargingMicroWatt = + calculateMaxChargingMicroWatt(maxChargingMicroCurrent, maxChargingMicroVolt); + if (maxChargingMicroWatt <= 0) { return CHARGING_UNKNOWN; } else if (maxChargingMicroWatt @@ -326,6 +393,12 @@ public class BatteryStatus { final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateMaxChargingMicroWatt(maxChargingMicroAmp, maxChargingMicroVolt); + } + + private static int calculateMaxChargingMicroWatt(int maxChargingMicroAmp, + int maxChargingMicroVolt) { if (maxChargingMicroVolt <= 0) { maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 41afc7b8b194..a63bbdf36fa8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -26,6 +26,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; @@ -51,6 +52,34 @@ public class PhoneMediaDevice extends MediaDevice { private final DeviceIconUtil mDeviceIconUtil; + /** Returns the device name for the given {@code routeInfo}. */ + public static String getSystemRouteNameFromType( + @NonNull Context context, @NonNull MediaRoute2Info routeInfo) { + CharSequence name; + switch (routeInfo.getType()) { + case TYPE_WIRED_HEADSET: + case TYPE_WIRED_HEADPHONES: + case TYPE_USB_DEVICE: + case TYPE_USB_HEADSET: + case TYPE_USB_ACCESSORY: + name = context.getString(R.string.media_transfer_wired_usb_device_name); + break; + case TYPE_DOCK: + name = context.getString(R.string.media_transfer_dock_speaker_device_name); + break; + case TYPE_BUILTIN_SPEAKER: + name = context.getString(R.string.media_transfer_this_device_name); + break; + case TYPE_HDMI: + name = context.getString(R.string.media_transfer_external_device_name); + break; + default: + name = context.getString(R.string.media_transfer_default_device_name); + break; + } + return name.toString(); + } + PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) { this(context, info, packageName, null); } @@ -69,29 +98,7 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getName() { - CharSequence name; - switch (mRouteInfo.getType()) { - case TYPE_WIRED_HEADSET: - case TYPE_WIRED_HEADPHONES: - case TYPE_USB_DEVICE: - case TYPE_USB_HEADSET: - case TYPE_USB_ACCESSORY: - name = mContext.getString(R.string.media_transfer_wired_usb_device_name); - break; - case TYPE_DOCK: - name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); - break; - case TYPE_BUILTIN_SPEAKER: - name = mContext.getString(R.string.media_transfer_this_device_name); - break; - case TYPE_HDMI: - name = mContext.getString(R.string.media_transfer_external_device_name); - break; - default: - name = mContext.getString(R.string.media_transfer_default_device_name); - break; - } - return name.toString(); + return getSystemRouteNameFromType(mContext, mRouteInfo); } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java index 2b1e8080d389..507dcbc1d8a2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java @@ -55,8 +55,7 @@ public class CollapsingCoordinatorLayoutTest { @Test public void onCreate_userAddedChildViewsBeMovedToContentFrame() { CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout(); - View contentFrameView = - layout.findViewById(com.android.settingslib.widget.R.id.content_frame); + View contentFrameView = layout.findViewById(R.id.content_frame); TextView textView = contentFrameView.findViewById(com.android.settingslib.robotests.R.id.text_hello_world); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java deleted file mode 100644 index 52d243a14e2f..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.deviceinfo; - -import static com.google.common.truth.Truth.assertThat; - -import static org.robolectric.shadow.api.Shadow.extract; - -import android.os.UserManager; -import android.telephony.TelephonyManager; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class, - SimStatusImeiInfoPreferenceControllerTest.ShadowTelephonyManager.class}) -public class SimStatusImeiInfoPreferenceControllerTest { - - private AbstractSimStatusImeiInfoPreferenceController mController; - - @Before - public void setUp() { - mController = new AbstractSimStatusImeiInfoPreferenceController( - RuntimeEnvironment.application) { - @Override - public String getPreferenceKey() { - return null; - } - }; - } - - @Test - public void testIsAvailable_isAdminAndHasMobile_shouldReturnTrue() { - ShadowUserManager userManager = - extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); - userManager.setIsAdminUser(true); - ShadowTelephonyManager telephonyManager = - extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class)); - telephonyManager.setDataCapable(true); - - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void testIsAvailable_isAdminButNoMobile_shouldReturnFalse() { - ShadowUserManager userManager = - extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); - userManager.setIsAdminUser(true); - ShadowTelephonyManager telephonyManager = - extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class)); - telephonyManager.setDataCapable(false); - - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void testIsAvailable_isNotAdmin_shouldReturnFalse() { - ShadowUserManager userManager = - extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); - userManager.setIsAdminUser(false); - - assertThat(mController.isAvailable()).isFalse(); - } - - @Implements(UserManager.class) - public static class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { - - private boolean mAdminUser; - - public void setIsAdminUser(boolean isAdminUser) { - mAdminUser = isAdminUser; - } - - @Implementation - public boolean isAdminUser() { - return mAdminUser; - } - } - - @Implements(TelephonyManager.class) - public static class ShadowTelephonyManager - extends org.robolectric.shadows.ShadowTelephonyManager { - private boolean mDataCapable = false; - private void setDataCapable(boolean capable) { - mDataCapable = capable; - } - - @Implementation - public boolean isDataCapable() { - return mDataCapable; - } - } -} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java index 6195d754f8f0..71545b75989a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java @@ -36,10 +36,10 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.ShapeDrawable; import android.os.Bundle; -import com.android.settingslib.widget.adaptiveicon.R; import com.android.settingslib.drawer.ActivityTile; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.Tile; +import com.android.settingslib.widget.adaptiveicon.R; import org.junit.Before; import org.junit.Test; @@ -105,15 +105,15 @@ public class AdaptiveIconTest { icon.setBackgroundColor(mContext, tile); - assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor( - com.android.settingslib.widget.R.color.homepage_generic_icon_background)); + assertThat(icon.mBackgroundColor).isEqualTo( + mContext.getColor(R.color.homepage_generic_icon_background)); } @Test public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, - com.android.settingslib.widget.R.color.bt_outline_color); + R.color.bt_outline_color); doReturn(Icon.createWithResource(mContext, com.android.settingslib.R.drawable.ic_system_update)) .when(tile).getIcon(mContext); @@ -121,8 +121,7 @@ public class AdaptiveIconTest { new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); icon.setBackgroundColor(mContext, tile); - assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor( - com.android.settingslib.widget.R.color.bt_outline_color)); + assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(R.color.bt_outline_color)); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java index ccbe4f03e80c..0ce83c6220ff 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java @@ -58,16 +58,14 @@ public class FooterPreferenceTest { @Test public void setLearnMoreText_shouldSetAsTextInLearnMore() { final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)); mFooterPreference.setLearnMoreText("Custom learn more"); mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */); mFooterPreference.onBindViewHolder(holder); - assertThat(((TextView) holder.findViewById( - com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString()) - .isEqualTo("Custom learn more"); + TextView learnMoreView = (TextView) holder.findViewById(R.id.settingslib_learn_more); + assertThat(learnMoreView.getText().toString()).isEqualTo("Custom learn more"); } @Test @@ -95,8 +93,7 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(R.layout.preference_footer, null))); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null); Throwable actualThrowable = null; @@ -112,10 +109,8 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(com.android.settingslib.widget.R.layout.preference_footer, null))); - when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more)) - .thenReturn(null); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); + when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null); Throwable actualThrowable = null; try { @@ -130,8 +125,7 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(com.android.settingslib.widget.R.layout.preference_footer, null))); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null); Throwable actualThrowable = null; diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java deleted file mode 100644 index 0b9ba8d044ce..000000000000 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.testutils.shadow; - -import static android.os.Build.VERSION_CODES.O; - -import android.app.ActivityManager; -import android.app.IActivityManager; - -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.util.ReflectionHelpers; - -@Implements(ActivityManager.class) -public class ShadowActivityManager { - private static int sCurrentUserId = 0; - private static int sUserSwitchedTo = -1; - - @Resetter - public static void reset() { - sCurrentUserId = 0; - sUserSwitchedTo = 0; - } - - @Implementation - protected static int getCurrentUser() { - return sCurrentUserId; - } - - @Implementation - protected boolean switchUser(int userId) { - sUserSwitchedTo = userId; - return true; - } - - @Implementation(minSdk = O) - protected static IActivityManager getService() { - return ReflectionHelpers.createNullProxy(IActivityManager.class); - } - - public boolean getSwitchUserCalled() { - return sUserSwitchedTo != -1; - } - - public int getUserSwitchedTo() { - return sUserSwitchedTo; - } - - public static void setCurrentUser(int userId) { - sCurrentUserId = userId; - } - - public static ShadowActivityManager getShadow() { - return (ShadowActivityManager) Shadow.extract( - RuntimeEnvironment.application.getSystemService(ActivityManager.class)); - } -} diff --git a/packages/SettingsProvider/OWNERS b/packages/SettingsProvider/OWNERS index 5ade9716c8cb..86ae5818e91c 100644 --- a/packages/SettingsProvider/OWNERS +++ b/packages/SettingsProvider/OWNERS @@ -1,5 +1 @@ -hackbod@android.com -hackbod@google.com -narayan@google.com -svetoslavganov@google.com include /PACKAGE_MANAGER_OWNERS diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 1d25ac78e7f9..e5dbe5f14cf4 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -69,6 +69,7 @@ public class GlobalSettings { Settings.Global.PRIVATE_DNS_SPECIFIER, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, Settings.Global.ZEN_DURATION, + Settings.Global.REVERSE_CHARGING_AUTO_ON, Settings.Global.CHARGING_VIBRATION_ENABLED, Settings.Global.AWARE_ALLOWED, Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP, // moved to secure diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index ba4ad365e1b3..c6e9c03d5968 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -143,6 +143,7 @@ public class SecureSettings { Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, + Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED, @@ -245,6 +246,9 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED, - Settings.Secure.HUB_MODE_TUTORIAL_STATE + Settings.Secure.HUB_MODE_TUTORIAL_STATE, + Settings.Secure.STYLUS_BUTTONS_ENABLED, + Settings.Secure.STYLUS_HANDWRITING_ENABLED, + Settings.Secure.DEFAULT_NOTE_TASK_PROFILE }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 248c60cb4fe9..fe39c4febc80 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -101,5 +101,10 @@ public class SystemSettings { Settings.System.CAMERA_FLASH_NOTIFICATION, Settings.System.SCREEN_FLASH_NOTIFICATION, Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, + Settings.System.PEAK_REFRESH_RATE, + Settings.System.MIN_REFRESH_RATE, + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + Settings.System.NOTIFICATION_COOLDOWN_ALL, + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index ba06185e5663..7e8fe7e09d74 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -171,6 +171,7 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.WIFI_SCAN_THROTTLE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.APP_AUTO_RESTRICTION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.ZEN_DURATION, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(Global.REVERSE_CHARGING_AUTO_ON, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 19fde758da5d..0727677b1a72 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -212,6 +212,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put( Secure.ENABLED_NOTIFICATION_LISTENERS, @@ -394,5 +395,9 @@ public class SecureSettingsValidators { BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.HUB_MODE_TUTORIAL_STATE, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.STYLUS_BUTTONS_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED, + new DiscreteValueValidator(new String[] {"-1", "0", "1"})); + VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 29f27f74bca4..eba74ab14f3d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -21,6 +21,7 @@ import static android.provider.settings.validators.SettingsValidators.ANY_STRING import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.URI_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR; @@ -30,6 +31,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.hardware.display.ColorDisplayManager; import android.os.BatteryManager; +import android.provider.Settings.Global; import android.provider.Settings.System; import android.util.ArrayMap; @@ -236,5 +238,10 @@ public class SystemSettingsValidators { VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(System.PEAK_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); + VALIDATORS.put(System.MIN_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); + VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 34d3d446530b..46cd725ad582 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -121,6 +121,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; +import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FrameworkStatsLog; import com.android.providers.settings.SettingsState.Setting; @@ -3878,7 +3879,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 222; + private static final int SETTINGS_VERSION = 223; private final int mUserId; @@ -5935,10 +5936,6 @@ public class SettingsProvider extends ContentProvider { if (currentVersion == 218) { // Version 219: Removed - // TODO(b/211737588): Back up the Smooth Display setting - // Future upgrades to the `peak_refresh_rate` and `min_refresh_rate` settings - // should account for the database in a non-upgraded and upgraded (change id: - // Ib2cb2dd100f06f5452083b7606109a486e795a0e) state. currentVersion = 219; } @@ -6004,6 +6001,56 @@ public class SettingsProvider extends ContentProvider { currentVersion = 222; } + // Version 222: Set peak refresh rate and min refresh rate to infinity if it's + // meant to be the highest possible refresh rate. This is needed so that we can + // back up and restore those settings on other devices. Other devices might have + // different highest possible refresh rates. + if (currentVersion == 222) { + final SettingsState systemSettings = getSystemSettingsLocked(userId); + final Setting peakRefreshRateSetting = + systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE); + final Setting minRefreshRateSetting = + systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE); + float highestRefreshRate = RefreshRateSettingsUtils + .findHighestRefreshRateForDefaultDisplay(getContext()); + + if (!peakRefreshRateSetting.isNull()) { + try { + float peakRefreshRate = + Float.parseFloat(peakRefreshRateSetting.getValue()); + if (Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) { + systemSettings.insertSettingLocked( + Settings.System.PEAK_REFRESH_RATE, + String.valueOf(Float.POSITIVE_INFINITY), + /* tag= */ null, + /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } catch (NumberFormatException e) { + // Do nothing. Leave the value as is. + } + } + + if (!minRefreshRateSetting.isNull()) { + try { + float minRefreshRate = + Float.parseFloat(minRefreshRateSetting.getValue()); + if (Math.round(minRefreshRate) == Math.round(highestRefreshRate)) { + systemSettings.insertSettingLocked( + Settings.System.MIN_REFRESH_RATE, + String.valueOf(Float.POSITIVE_INFINITY), + /* tag= */ null, + /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } catch (NumberFormatException e) { + // Do nothing. Leave the value as is. + } + } + + currentVersion = 223; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9ddc976af7e2..7bca944033d9 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -98,8 +98,6 @@ public class SettingsBackupTest { Settings.System.VOLUME_VOICE, // deprecated since API 2? Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug? Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only - Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities - Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities Settings.System.SCREEN_BRIGHTNESS_FLOAT, Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, @@ -735,7 +733,6 @@ public class SettingsBackupTest { Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, Settings.Secure.CONTENT_CAPTURE_ENABLED, Settings.Secure.DEFAULT_INPUT_METHOD, - Settings.Secure.DEFAULT_NOTE_TASK_PROFILE, Settings.Secure.DEVICE_PAIRED, Settings.Secure.DIALER_DEFAULT_APPLICATION, Settings.Secure.DISABLED_PRINT_SERVICES, @@ -805,8 +802,6 @@ public class SettingsBackupTest { Settings.Secure.SLEEP_TIMEOUT, Settings.Secure.SMS_DEFAULT_APPLICATION, Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q - Settings.Secure.STYLUS_BUTTONS_ENABLED, - Settings.Secure.STYLUS_HANDWRITING_ENABLED, Settings.Secure.TRUST_AGENTS_INITIALIZED, Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e40fcb2a633b..4954cb482120 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -60,7 +60,9 @@ systemui_compose_java_defaults { // except for SystemUI-core. // Copied from compose/features/Android.bp. static_libs: [ + "CommunalLayoutLib", "PlatformComposeCore", + "PlatformComposeSceneTransitionLayout", "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml index f6dcdd3dc0bd..b314c8eaea1d 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml @@ -8,7 +8,7 @@ <string name="a11y_settings_label" msgid="3977714687248445050">"Erabilerraztasun-&#173;ezarpenak"</string> <string name="power_label" msgid="7699720321491287839">"Bateria"</string> <string name="power_utterance" msgid="7444296686402104807">"Bateria kontrolatzeko aukerak"</string> - <string name="recent_apps_label" msgid="6583276995616385847">"Azken aplikazioak"</string> + <string name="recent_apps_label" msgid="6583276995616385847">"Azkenaldiko aplikazioak"</string> <string name="lockscreen_label" msgid="648347953557887087">"Pantaila blokeatua"</string> <string name="quick_settings_label" msgid="2999117381487601865">"Ezarpen bizkorrak"</string> <string name="notifications_label" msgid="6829741046963013567">"Jakinarazpenak"</string> diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index c1390b252418..dc4208e87207 100644 --- a/packages/SystemUI/aconfig/Android.bp +++ b/packages/SystemUI/aconfig/Android.bp @@ -2,8 +2,7 @@ aconfig_declarations { name: "com_android_systemui_flags", package: "com.android.systemui", srcs: [ - "systemui.aconfig", - "accessibility.aconfig", + "*.aconfig", ], } diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig new file mode 100644 index 000000000000..2c6ff979cc7f --- /dev/null +++ b/packages/SystemUI/aconfig/communal.aconfig @@ -0,0 +1,8 @@ +package: "com.android.systemui" + +flag { + name: "communal_hub" + namespace: "communal" + description: "Enables the communal hub experience" + bug: "304584416" +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 18117a890298..2509cfd4af40 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -21,3 +21,11 @@ flag { "(containing the \"Clear all\" button). Should not bring any behavior changes" bug: "293167744" } + +flag { + name: "notification_lifetime_extension_refactor" + namespace: "systemui" + description: "Enables moving notification lifetime extension management from SystemUI to " + "Notification Manager Service" + bug: "299448097" +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 4aac27932924..4ea57a8cc007 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -893,7 +893,7 @@ class ActivityLaunchAnimator( return } - Log.i(TAG, "Remote animation timed out") + Log.wtf(TAG, "Remote animation timed out") timedOut = true if (DEBUG_LAUNCH_ANIMATION) { diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp new file mode 100644 index 000000000000..88dad6623d03 --- /dev/null +++ b/packages/SystemUI/communal/layout/Android.bp @@ -0,0 +1,35 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "CommunalLayoutLib", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.arch.core_core-runtime", + "androidx.compose.animation_animation-graphics", + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "jsr330", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", + ], + manifest: "AndroidManifest.xml", + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml new file mode 100644 index 000000000000..141be0762ae9 --- /dev/null +++ b/packages/SystemUI/communal/layout/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest package="com.android.systemui.communal.layout" /> diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt new file mode 100644 index 000000000000..df87d19db5a6 --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt @@ -0,0 +1,66 @@ +/* + * 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.systemui.communal.layout + +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard + +/** Computes the arrangement of cards. */ +class CommunalLayoutEngine { + companion object { + /** + * Determines the size that each card should be rendered in, and distributes the cards into + * columns. + * + * Returns a nested list where the outer list contains columns, and the inner list contains + * cards in each column. + * + * Currently treats the first supported size as the size to be rendered in, ignoring other + * supported sizes. + */ + fun distributeCardsIntoColumns( + cards: List<CommunalGridLayoutCard>, + ): List<List<CommunalGridLayoutCardInfo>> { + val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>() + + var capacityOfLastColumn = 0 + for (card in cards) { + val cardSize = card.supportedSizes.first() + if (capacityOfLastColumn >= cardSize.value) { + // Card fits in last column + capacityOfLastColumn -= cardSize.value + } else { + // Create a new column + result.add(arrayListOf()) + capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value + } + + result.last().add(CommunalGridLayoutCardInfo(card, cardSize)) + } + + return result + } + } + + /** + * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the + * card should be rendered in. + */ + data class CommunalGridLayoutCardInfo( + val card: CommunalGridLayoutCard, + val size: CommunalGridLayoutCard.Size, + ) +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt new file mode 100644 index 000000000000..4ed78b3b95ed --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt @@ -0,0 +1,64 @@ +/* + * 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.systemui.communal.layout.ui.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.communal.layout.CommunalLayoutEngine +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig + +/** + * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size + * and follows a specific order based on its priority, ensuring a seamless layout without any gaps. + */ +@Composable +fun CommunalGridLayout( + modifier: Modifier, + layoutConfig: CommunalGridLayoutConfig, + communalCards: List<CommunalGridLayoutCard>, +) { + val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards) + LazyRow( + modifier = modifier.height(layoutConfig.gridHeight), + horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (column in columns) { + item { + Column( + modifier = Modifier.width(layoutConfig.cardWidth), + verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (cardInfo in column) { + Row( + modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)), + ) { + cardInfo.card.Content(Modifier.fillMaxSize()) + } + } + } + } + } + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt new file mode 100644 index 000000000000..ac8aa67fa4bf --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt @@ -0,0 +1,64 @@ +/* + * 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.systemui.communal.layout.ui.compose.config + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** A card that hosts content to be rendered in the communal grid layout. */ +abstract class CommunalGridLayoutCard { + /** + * Content to be hosted by the card. + * + * To host non-Compose views, see + * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose. + */ + @Composable abstract fun Content(modifier: Modifier) + + /** + * Sizes supported by the card. + * + * If multiple sizes are available, they should be ranked in order of preference, from most to + * least preferred. + */ + abstract val supportedSizes: List<Size> + + /** + * Priority of the content hosted by the card. + * + * The value of priority is relative to other cards. Cards with a higher priority are generally + * ordered first. + */ + open val priority: Int = 0 + + /** + * Size of the card. + * + * @param value A numeric value that represents the size. Must be less than or equal to + * [Size.FULL]. + */ + enum class Size(val value: Int) { + /** The card takes up full height of the grid layout. */ + FULL(value = 6), + + /** The card takes up half of the vertical space of the grid layout. */ + HALF(value = 3), + + /** The card takes up a third of the vertical space of the grid layout. */ + THIRD(value = 2), + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt new file mode 100644 index 000000000000..143df838169b --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt @@ -0,0 +1,82 @@ +/* + * 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.systemui.communal.layout.ui.compose.config + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.times + +/** + * Configurations of the communal grid layout. + * + * The communal grid layout follows Material Design's responsive layout grid (see + * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided + * up by columns and gutters, and each card occupies one or multiple columns. + */ +data class CommunalGridLayoutConfig( + /** + * Size in dp of each grid column. + * + * Every card occupies one or more grid columns, which means that the width of each card is + * influenced by the size of the grid columns. + */ + val gridColumnSize: Dp, + + /** + * Size in dp of each grid gutter. + * + * A gutter is the space between columns that helps separate content. This is, therefore, also + * the size of the gaps between cards, both horizontally and vertically. + */ + val gridGutter: Dp, + + /** + * Height in dp of the grid layout. + * + * Cards with a full size take up the entire height of the grid layout. + */ + val gridHeight: Dp, + + /** + * Number of grid columns that each card occupies. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val gridColumnsPerCard: Int, +) { + /** + * Width in dp of each card. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1) + + /** Returns the height of a card in dp, based on its size. */ + fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp { + return when (cardSize) { + CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1) + CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2) + CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3) + } + } + + /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */ + private fun cardHeightBy(denominator: Int): Dp { + return (gridHeight - (denominator - 1) * gridGutter) / denominator + } +} diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp new file mode 100644 index 000000000000..a60b1de5b501 --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/Android.bp @@ -0,0 +1,47 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "CommunalLayoutLibTests", + srcs: [ + "**/*.kt", + ], + static_libs: [ + "CommunalLayoutLib", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "frameworks-base-testutils", + "junit", + "kotlinx_coroutines_test", + "mockito-target-extended-minus-junit4", + "platform-test-annotations", + "testables", + "truth-prebuilt", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + manifest: "AndroidManifest.xml", +} diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml new file mode 100644 index 000000000000..b19007c1ff1b --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.communal.layout.tests"> + + <application android:debuggable="true" android:largeHeap="true"> + <uses-library android:name="android.test.mock" /> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.testing.TestableInstrumentation" + android:targetPackage="com.android.systemui.communal.layout.tests" + android:label="Tests for CommunalLayoutLib"> + </instrumentation> + +</manifest> diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml new file mode 100644 index 000000000000..1352b238f6fe --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration description="Runs tests for CommunalLayoutLib"> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="CommunalLayoutLibTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="CommunalLayoutLibTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.systemui.communal.layout.tests" /> + <option name="runner" value="android.testing.TestableInstrumentation" /> + <option name="hidden-api-checks" value="false"/> + </test> + +</configuration> diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt new file mode 100644 index 000000000000..fdf65f5d5cc7 --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt @@ -0,0 +1,99 @@ +package com.android.systemui.communal.layout + +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalLayoutEngineTest { + @Test + fun distribution_fullLayout() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + @Test + fun distribution_layoutWithGaps() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.THIRD, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + private fun assertDistribution( + cards: List<CommunalGridLayoutCard>, + expected: List<List<CommunalGridLayoutCard.Size>>, + ) { + val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards) + + for (c in expected.indices) { + for (r in expected[c].indices) { + assertThat(result[c][r].size).isEqualTo(expected[c][r]) + } + } + } + + private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard { + return object : CommunalGridLayoutCard() { + override val supportedSizes = listOf(size) + + @Composable + override fun Content(modifier: Modifier) { + Card(modifier = modifier, content = {}) + } + } + } +} diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt new file mode 100644 index 000000000000..946eeecbec5d --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt @@ -0,0 +1,63 @@ +package com.android.systemui.communal.layout.ui.compose.config + +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalGridLayoutConfigTest { + @Test + fun cardWidth() { + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 1, + ) + .cardWidth + ) + .isEqualTo(5.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 2, + ) + .cardWidth + ) + .isEqualTo(13.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 3, + ) + .cardWidth + ) + .isEqualTo(21.dp) + } + + @Test + fun cardHeight() { + val config = + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 2.dp, + gridHeight = 10.dp, + gridColumnsPerCard = 3, + ) + + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp) + } +} diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 5b4a8fb6ab7a..3d670b809d15 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -64,6 +64,12 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun createCommunalView( + context: Context, + ): View { + throwComposeUnavailableError() + } + private fun throwComposeUnavailableError(): Nothing { error( "Compose is not available. Make sure to check isComposeAvailable() before calling any" + diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index ac599897553a..7b11ac7f4e1e 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -30,6 +30,7 @@ import com.android.compose.theme.PlatformTheme import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider +import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -93,6 +94,12 @@ object ComposeFacade : BaseComposeFacade { } } + override fun createCommunalView( + context: Context, + ): View { + return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } } + } + // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index e4426fe97859..796abf4b52d6 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -33,6 +33,7 @@ android_library { static_libs: [ "SystemUI-core", "PlatformComposeCore", + "PlatformComposeSceneTransitionLayout", "androidx.compose.runtime_runtime", "androidx.compose.animation_animation-graphics", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt new file mode 100644 index 000000000000..4d2978df7b1b --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -0,0 +1,22 @@ +package com.android.systemui.communal.ui.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun CommunalHub(modifier: Modifier = Modifier) { + Box( + modifier = modifier.fillMaxSize().background(Color.White), + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "Hello Communal!", + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 0d2ba2824846..d1c12ac85cc5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -16,14 +16,8 @@ package com.android.systemui.communal.ui.compose -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Direction @@ -51,13 +45,6 @@ class CommunalScene @Inject constructor() : ComposableScene { @Composable override fun SceneScope.Content(modifier: Modifier) { - Box( - modifier = modifier.fillMaxSize().background(Color.White), - ) { - Text( - modifier = Modifier.align(Alignment.Center), - text = "Hello Communal!", - ) - } + CommunalHub(modifier) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index e12b7eae96e7..73cb72ca062e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -47,10 +47,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.compose.theme.LocalAndroidColorScheme -import com.android.systemui.res.R import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.res.R /** * Compose the screen associated to a [PeopleViewModel]. @@ -86,9 +86,9 @@ fun PeopleScreen( modifier = Modifier.fillMaxSize(), ) { if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) { - PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked) + PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel.onTileClicked) } else { - PeopleScreenEmpty(viewModel::onUserJourneyCancelled) + PeopleScreenEmpty(viewModel.onUserJourneyCancelled) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 0da562bcb3bb..2e93a09deb30 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -161,8 +161,7 @@ private fun SceneTransitionObservableTransitionState.toModel(): ObservableTransi fromScene = fromScene.toModel().key, toScene = toScene.toModel().key, progress = progress, - isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = isUserInputOngoing, + isUserInputDriven = isUserInputDriven, ) } } diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp new file mode 100644 index 000000000000..050d1d5651ad --- /dev/null +++ b/packages/SystemUI/compose/scene/Android.bp @@ -0,0 +1,39 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "PlatformComposeSceneTransitionLayout", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + kotlincflags: ["-Xjvm-default=all"], + use_resource_processor: true, +} diff --git a/packages/SystemUI/compose/scene/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml new file mode 100644 index 000000000000..81131bb689e4 --- /dev/null +++ b/packages/SystemUI/compose/scene/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.compose.animation.scene"> + + +</manifest> diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS new file mode 100644 index 000000000000..33a59c2bcab3 --- /dev/null +++ b/packages/SystemUI/compose/scene/OWNERS @@ -0,0 +1,13 @@ +set noparent + +# Bug component: 1184816 + +jdemeulenaere@google.com +omarmt@google.com + +# SysUI Dr No's. +# Don't send reviews here. +dsandler@android.com +cinek@google.com +juliacr@google.com +pixel@google.com
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING new file mode 100644 index 000000000000..f644a23ba0a3 --- /dev/null +++ b/packages/SystemUI/compose/scene/TEST_MAPPING @@ -0,0 +1,48 @@ +{ + "presubmit": [ + { + "name": "PlatformComposeSceneTransitionLayoutTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "PlatformComposeCoreTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeFeaturesTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt index 566967f920d3..041fc48dd09e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt @@ -17,12 +17,10 @@ package com.android.compose.animation.scene import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.DisposableEffectResult -import androidx.compose.runtime.DisposableEffectScope import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.lerp import androidx.compose.ui.unit.Dp @@ -45,6 +43,20 @@ fun SceneScope.animateSharedIntAsState( } /** + * Animate a shared Int value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedIntAsState( + value: Int, + debugName: String, + canOverflow: Boolean = true, +): State<Int> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + +/** * Animate a shared Float value. * * @see SceneScope.animateSharedValueAsState @@ -60,6 +72,20 @@ fun SceneScope.animateSharedFloatAsState( } /** + * Animate a shared Float value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedFloatAsState( + value: Float, + debugName: String, + canOverflow: Boolean = true, +): State<Float> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + +/** * Animate a shared Dp value. * * @see SceneScope.animateSharedValueAsState @@ -75,6 +101,20 @@ fun SceneScope.animateSharedDpAsState( } /** + * Animate a shared Dp value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedDpAsState( + value: Dp, + debugName: String, + canOverflow: Boolean = true, +): State<Dp> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + +/** * Animate a shared Color value. * * @see SceneScope.animateSharedValueAsState @@ -88,6 +128,19 @@ fun SceneScope.animateSharedColorAsState( return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false) } +/** + * Animate a shared Color value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedColorAsState( + value: Color, + debugName: String, +): State<Color> { + return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false) +} + @Composable internal fun <T> animateSharedValueAsState( layoutImpl: SceneTransitionLayoutImpl, @@ -98,33 +151,22 @@ internal fun <T> animateSharedValueAsState( lerp: (T, T, Float) -> T, canOverflow: Boolean, ): State<T> { - val sharedValue = remember(key) { Element.SharedValue(key, value) } + val sharedValue = + Snapshot.withoutReadObservation { + element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) { + Element.SharedValue(key, value) + } as Element.SharedValue<T> + } + if (value != sharedValue.value) { sharedValue.value = value } - DisposableEffect(element, scene, sharedValue) { - addSharedValueToElement(element, scene, sharedValue) - } - return remember(layoutImpl, element, sharedValue, lerp, canOverflow) { derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) } } } -private fun <T> DisposableEffectScope.addSharedValueToElement( - element: Element, - scene: Scene, - sharedValue: Element.SharedValue<T>, -): DisposableEffectResult { - val sceneValues = - element.sceneValues[scene.key] ?: error("Element $element is not present in $scene") - val sharedValues = sceneValues.sharedValues - - sharedValues[sharedValue.key] = sharedValue - return onDispose { sharedValues.remove(sharedValue.key) } -} - private fun <T> computeValue( layoutImpl: SceneTransitionLayoutImpl, element: Element, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 60c3fd3c4cdb..88944f10eab9 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -108,7 +108,7 @@ private fun CoroutineScope.animate( ) { val fromScene = layoutImpl.state.transitionState.currentScene val isUserInput = - (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput + (layoutImpl.state.transitionState as? TransitionState.Transition)?.isUserInputDriven ?: false val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec @@ -119,23 +119,9 @@ private fun CoroutineScope.animate( val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { - OneOffTransition( - fromScene = target, - toScene = fromScene, - currentScene = target, - isUserInput, - isUserInputOngoing = false, - animatable, - ) + OneOffTransition(target, fromScene, currentScene = target, isUserInput, animatable) } else { - OneOffTransition( - fromScene = fromScene, - toScene = target, - currentScene = target, - isUserInput, - isUserInputOngoing = false, - animatable, - ) + OneOffTransition(fromScene, target, currentScene = target, isUserInput, animatable) } // Change the current layout state to use this new transition. @@ -156,8 +142,7 @@ private class OneOffTransition( override val fromScene: SceneKey, override val toScene: SceneKey, override val currentScene: SceneKey, - override val isInitiatedByUserInput: Boolean, - override val isUserInputOngoing: Boolean, + override val isUserInputDriven: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition { override val progress: Float diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 3bcd920fb02b..ce96bbfc7976 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -59,6 +60,17 @@ internal class Element(val key: ElementKey) { /** The mapping between a scene and the values/state this element has in that scene, if any. */ val sceneValues = SnapshotStateMap<SceneKey, TargetValues>() + /** + * The movable content of this element, if this element is composed using + * [SceneScope.MovableElement]. + */ + val movableContent by + // This is only accessed from the composition (main) thread, so no need to use the default + // lock of lazy {} to synchronize. + lazy(mode = LazyThreadSafetyMode.NONE) { + movableContentOf { content: @Composable () -> Unit -> content() } + } + override fun toString(): String { return "Element(key=$key)" } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt index 98dbb67d7c66..98dbb67d7c66 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt index b7acc48e2865..bc015eedb1b4 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt @@ -22,7 +22,7 @@ import androidx.annotation.VisibleForTesting * A base class to create unique keys, associated to an [identity] that is used to check the * equality of two key instances. */ -sealed class Key(val name: String, val identity: Any) { +sealed class Key(val debugName: String, val identity: Any) { override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false @@ -34,7 +34,7 @@ sealed class Key(val name: String, val identity: Any) { } override fun toString(): String { - return "Key(name=$name)" + return "Key(debugName=$debugName)" } } @@ -49,7 +49,7 @@ class SceneKey( val rootElementKey = ElementKey(name, identity) override fun toString(): String { - return "SceneKey(name=$name)" + return "SceneKey(debugName=$debugName)" } } @@ -71,7 +71,7 @@ class ElementKey( } override fun toString(): String { - return "ElementKey(name=$name)" + return "ElementKey(debugName=$debugName)" } companion object { @@ -89,6 +89,6 @@ class ElementKey( /** Key for a shared value of an element. */ class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) { override fun toString(): String { - return "ValueKey(name=$name)" + return "ValueKey(debugName=$debugName)" } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt new file mode 100644 index 000000000000..6dbeb69ff450 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -0,0 +1,200 @@ +/* + * 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.compose.animation.scene + +import android.graphics.Picture +import android.util.Log +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.Snapshot +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.drawscope.draw +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntSize + +private const val TAG = "MovableElement" + +@Composable +internal fun MovableElement( + layoutImpl: SceneTransitionLayoutImpl, + scene: Scene, + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, +) { + Box(modifier.element(layoutImpl, scene, key)) { + // Get the Element from the map. It will always be the same and we don't want to recompose + // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we + // disable read observation during the look-up in that map. + val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) } + val movableElementScope = + remember(layoutImpl, element, scene) { + MovableElementScopeImpl(layoutImpl, element, scene) + } + + // The [Picture] to which we save the last drawing commands of this element. This is + // necessary because the content of this element might not be composed in this scene, in + // which case we still need to draw it. + val picture = remember { Picture() } + + if (shouldComposeMovableElement(layoutImpl, scene.key, element)) { + Box( + Modifier.drawWithCache { + val width = size.width.toInt() + val height = size.height.toInt() + + onDrawWithContent { + // Save the draw commands into [picture] for later to draw the last content + // even when this movable content is not composed. + val pictureCanvas = Canvas(picture.beginRecording(width, height)) + draw(this, this.layoutDirection, pictureCanvas, this.size) { + this@onDrawWithContent.drawContent() + } + picture.endRecording() + + // Draw the content. + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + } + ) { + element.movableContent { movableElementScope.content() } + } + } else { + // If we are not composed, we draw the previous drawing commands at the same size as the + // movable content when it was composed in this scene. + val sceneValues = element.sceneValues.getValue(scene.key) + + Spacer( + Modifier.layout { measurable, _ -> + val size = + sceneValues.targetSize.takeIf { it != Element.SizeUnspecified } + ?: IntSize.Zero + val placeable = + measurable.measure(Constraints.fixed(size.width, size.height)) + layout(size.width, size.height) { placeable.place(0, 0) } + } + .drawBehind { + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + ) + } + } +} + +private fun shouldComposeMovableElement( + layoutImpl: SceneTransitionLayoutImpl, + scene: SceneKey, + element: Element, +): Boolean { + val transitionState = layoutImpl.state.transitionState + + // If we are idle, there is only one [scene] that is composed so we can compose our movable + // content here. + if (transitionState is TransitionState.Idle) { + check(transitionState.currentScene == scene) + return true + } + + val fromScene = (transitionState as TransitionState.Transition).fromScene + val toScene = transitionState.toScene + if (fromScene == toScene) { + check(fromScene == scene) + return true + } + + val fromReady = layoutImpl.isSceneReady(fromScene) + val toReady = layoutImpl.isSceneReady(toScene) + + val otherScene = + when (scene) { + fromScene -> toScene + toScene -> fromScene + else -> + error( + "shouldComposeMovableElement(scene=$scene) called with fromScene=$fromScene " + + "and toScene=$toScene" + ) + } + + val isShared = otherScene in element.sceneValues + + if (isShared && !toReady && !fromReady) { + // This should usually not happen given that fromScene should be ready, but let's log a + // warning here in case it does so it helps debugging flicker issues caused by this part of + // the code. + Log.w( + TAG, + "MovableElement $element might have to be composed for the first time in both " + + "fromScene=$fromScene and toScene=$toScene. This will probably lead to a flicker " + + "where the size of the element will jump from IntSize.Zero to its actual size " + + "during the transition." + ) + } + + // Element is not shared in this transition. + if (!isShared) { + return true + } + + // toScene is not ready (because we are composing it for the first time), so we compose it there + // first. This is the most common scenario when starting a transition that has a shared movable + // element. + if (!toReady) { + return scene == toScene + } + + // This should usually not happen, but if we are also composing for the first time in fromScene + // then we should compose it there only. + if (!fromReady) { + return scene == fromScene + } + + // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless + // it is a background) given that this is the one that is going to be drawn. + val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex + return if (element.key.isBackground) { + !isHighestScene + } else { + isHighestScene + } +} + +private class MovableElementScopeImpl( + private val layoutImpl: SceneTransitionLayoutImpl, + private val element: Element, + private val scene: Scene, +) : MovableElementScope { + @Composable + override fun <T> animateSharedValueAsState( + value: T, + debugName: String, + lerp: (start: T, stop: T, fraction: Float) -> T, + canOverflow: Boolean, + ): State<T> { + val key = remember { ValueKey(debugName) } + return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index 1b79dbdee510..ccdec6ea8c5e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -52,14 +52,7 @@ sealed class ObservableTransitionState { * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, + val isUserInputDriven: Boolean, ) : ObservableTransitionState() } @@ -80,8 +73,7 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans fromScene = state.fromScene, toScene = state.toScene, progress = snapshotFlow { state.progress }, - isInitiatedByUserInput = state.isInitiatedByUserInput, - isUserInputOngoing = snapshotFlow { state.isUserInputOngoing }, + isUserInputDriven = state.isUserInputDriven, ) } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 3985233bd197..3fd6828fca6b 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -90,4 +90,13 @@ private class SceneScopeImpl( canOverflow, ) } + + @Composable + override fun MovableElement( + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, + ) { + MovableElement(layoutImpl, scene, key, modifier, content) + } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 58c7bdbf3d71..74e66d2a9949 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -85,6 +85,13 @@ interface SceneTransitionLayoutScope { ) } +/** + * A DSL marker to prevent people from nesting calls to Modifier.element() inside a MovableElement, + * which is not supported. + */ +@DslMarker annotation class ElementDsl + +@ElementDsl interface SceneScope { /** * Tag an element identified by [key]. @@ -95,12 +102,37 @@ interface SceneScope { * Additionally, this [key] will be used to detect elements that are shared between scenes to * automatically interpolate their size, offset and [shared values][animateSharedValueAsState]. * + * Note that shared elements tagged using this function will be duplicated in each scene they + * are part of, so any **internal** state (e.g. state created using `remember { + * mutableStateOf(...) }`) will be lost. If you need to preserve internal state, you should use + * [MovableElement] instead. + * + * @see MovableElement + * * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable * constraint. */ @Composable fun Modifier.element(key: ElementKey): Modifier /** + * Create a *movable* element identified by [key]. + * + * This creates an element that will be automatically shared when present in multiple scenes and + * that can be transformed during transitions, the same way that [element] does. The major + * difference with [element] is that elements created with [MovableElement] will be "moved" and + * composed only once during transitions (as opposed to [element] that duplicates shared + * elements) so that any internal state is preserved during and after the transition. + * + * @see element + */ + @Composable + fun MovableElement( + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, + ) + + /** * Animate some value of a shared element. * * @param value the value of this shared value in the current scene. @@ -126,6 +158,19 @@ interface SceneScope { ): State<T> } +// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey +// arguments to allow sharing values inside a movable element. +@ElementDsl +interface MovableElementScope { + @Composable + fun <T> animateSharedValueAsState( + value: T, + debugName: String, + lerp: (start: T, stop: T, fraction: Float) -> T, + canOverflow: Boolean, + ): State<T> +} + /** An action performed by the user. */ sealed interface UserAction diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index b3a7a8e9f874..4952270cb5f2 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -199,4 +199,6 @@ internal class SceneTransitionLayoutImpl( return readyScenes.containsKey(transition.fromScene) && readyScenes.containsKey(transition.toScene) } + + internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene) } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index b9f83c545122..7a21211c3dde 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -70,9 +70,6 @@ sealed interface TransitionState { val progress: Float /** Whether the transition was triggered by user input rather than being programmatic. */ - val isInitiatedByUserInput: Boolean - - /** Whether user input is currently driving the transition. */ - val isUserInputOngoing: Boolean + val isUserInputDriven: Boolean } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 75dcb2e44c13..75dcb2e44c13 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index e275fcaf4572..1cbfe3057ff0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -66,7 +66,7 @@ internal fun Modifier.swipeToScene( // swipe in the other direction. val startDragImmediately = state == transition && - !transition.isUserInputOngoing && + transition.isAnimatingOffset && !currentScene.shouldEnableSwipes(orientation.opposite()) // The velocity threshold at which the intent of the user is to swipe up or down. It is the same @@ -126,7 +126,7 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition override val progress: Float get() { - val offset = if (isUserInputOngoing) dragOffset else offsetAnimatable.value + val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset if (distance == 0f) { // This can happen only if fromScene == toScene. error( @@ -137,15 +137,16 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition return offset / distance } - override val isInitiatedByUserInput = true - - var _isUserInputOngoing by mutableStateOf(false) - override val isUserInputOngoing: Boolean - get() = _isUserInputOngoing + override val isUserInputDriven = true /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) + /** + * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture. + */ + var isAnimatingOffset by mutableStateOf(false) + /** The animatable used to animate the offset once the user lifted its finger. */ val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold) @@ -208,11 +209,9 @@ private fun onDragStarted( transition: SwipeTransition, orientation: Orientation, ) { - transition._isUserInputOngoing = true - if (layoutImpl.state.transitionState == transition) { // This [transition] was already driving the animation: simply take over it. - if (!transition.isUserInputOngoing) { + if (transition.isAnimatingOffset) { // Stop animating and start from where the current offset. Setting the animation job to // `null` will effectively cancel the animation. transition.stopOffsetAnimation() @@ -457,29 +456,30 @@ private fun CoroutineScope.animateOffset( ) { transition.startOffsetAnimation { launch { - if (transition.isUserInputOngoing) { - transition.offsetAnimatable.snapTo(transition.dragOffset) - } - transition._isUserInputOngoing = false - - transition.offsetAnimatable.animateTo( - targetOffset, - // TODO(b/290184746): Make this spring spec configurable. - spring( - stiffness = Spring.StiffnessMediumLow, - visibilityThreshold = OffsetVisibilityThreshold - ), - initialVelocity = initialVelocity, - ) + if (!transition.isAnimatingOffset) { + transition.offsetAnimatable.snapTo(transition.dragOffset) + } + transition.isAnimatingOffset = true + + transition.offsetAnimatable.animateTo( + targetOffset, + // TODO(b/290184746): Make this spring spec configurable. + spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = OffsetVisibilityThreshold + ), + initialVelocity = initialVelocity, + ) - // Now that the animation is done, the state should be idle. Note that if the state - // was changed since this animation started, some external code changed it and we - // shouldn't do anything here. Note also that this job will be cancelled in the case - // where the user intercepts this swipe. - if (layoutImpl.state.transitionState == transition) { - layoutImpl.state.transitionState = TransitionState.Idle(targetScene) + // Now that the animation is done, the state should be idle. Note that if the state + // was changed since this animation started, some external code changed it and we + // shouldn't do anything here. Note also that this job will be cancelled in the case + // where the user intercepts this swipe. + if (layoutImpl.state.transitionState == transition) { + layoutImpl.state.transitionState = TransitionState.Idle(targetScene) + } } - } + .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } } } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 49669775fedd..49669775fedd 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index f1c27178391c..f1c27178391c 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 95385d51cb25..95385d51cb25 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt index a1d63193bc73..a1d63193bc73 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index 840800d838db..840800d838db 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt index 17032dc288e0..17032dc288e0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt index 62d67f03f1d0..62d67f03f1d0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt index 233ae597090b..233ae597090b 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 2ef8d56c6bc6..2ef8d56c6bc6 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index 864b937a3fe0..864b937a3fe0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt index 27f0948d5377..27f0948d5377 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt index 135a6e4ec4e4..135a6e4ec4e4 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt index cea8d9a65b43..cea8d9a65b43 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt index 793a9a59405a..793a9a59405a 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt index 741f00d9f19b..741f00d9f19b 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt index eb1a634ff491..eb1a634ff491 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp new file mode 100644 index 000000000000..b53fae24865c --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/Android.bp @@ -0,0 +1,50 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "PlatformComposeSceneTransitionLayoutTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "PlatformComposeSceneTransitionLayout", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + + "truth", + ], + + kotlincflags: ["-Xjvm-default=all"], + use_resource_processor: true, +} diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml new file mode 100644 index 000000000000..1a9172ee20e0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.compose.animation.scene.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.compose.animation.scene.tests" + android:label="Tests for SceneTransitionLayout"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt new file mode 100644 index 000000000000..7b7695eebd2f --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt @@ -0,0 +1,218 @@ +/* + * 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.compose.animation.scene + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.lerp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.ui.util.lerp +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AnimatedSharedAsStateTest { + @get:Rule val rule = createComposeRule() + + private data class Values( + val int: Int, + val float: Float, + val dp: Dp, + val color: Color, + ) + + private fun lerp(start: Values, stop: Values, fraction: Float): Values { + return Values( + int = lerp(start.int, stop.int, fraction), + float = lerp(start.float, stop.float, fraction), + dp = lerp(start.dp, stop.dp, fraction), + color = lerp(start.color, stop.color, fraction), + ) + } + + @Composable + private fun SceneScope.Foo( + targetValues: Values, + onCurrentValueChanged: (Values) -> Unit, + ) { + val key = TestElements.Foo + Box(Modifier.element(key)) { + val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key) + val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key) + val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key) + val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key) + + // Make sure we read the values during composition, so that we recompose and call + // onCurrentValueChanged() with the latest values. + val currentValues = Values(int, float, dp, color) + SideEffect { onCurrentValueChanged(currentValues) } + } + } + + @Composable + private fun SceneScope.MovableFoo( + targetValues: Values, + onCurrentValueChanged: (Values) -> Unit, + ) { + val key = TestElements.Foo + MovableElement(key = key, Modifier) { + val int by + animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName) + val float by + animateSharedFloatAsState( + targetValues.float, + debugName = TestValues.Value2.debugName + ) + val dp by + animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName) + val color by + animateSharedColorAsState( + targetValues.color, + debugName = TestValues.Value4.debugName + ) + + // Make sure we read the values during composition, so that we recompose and call + // onCurrentValueChanged() with the latest values. + val currentValues = Values(int, float, dp, color) + SideEffect { onCurrentValueChanged(currentValues) } + } + } + + @Test + fun animateSharedValues() { + val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red) + val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue) + + var lastValueInFrom = fromValues + var lastValueInTo = toValues + + rule.testTransition( + fromSceneContent = { + Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it }) + }, + toSceneContent = { + Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it }) + }, + transition = { + // The transition lasts 64ms = 4 frames. + spec = tween(durationMillis = 16 * 4, easing = LinearEasing) + }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + assertThat(lastValueInFrom).isEqualTo(fromValues) + + // to was not composed yet, so lastValueInTo was not set yet. + assertThat(lastValueInTo).isEqualTo(toValues) + } + + at(16) { + // Given that we use Modifier.element() here, animateSharedXAsState is composed in + // both scenes and values should be interpolated with the transition fraction. + val expectedValues = lerp(fromValues, toValues, fraction = 0.25f) + assertThat(lastValueInFrom).isEqualTo(expectedValues) + assertThat(lastValueInTo).isEqualTo(expectedValues) + } + + at(32) { + val expectedValues = lerp(fromValues, toValues, fraction = 0.5f) + assertThat(lastValueInFrom).isEqualTo(expectedValues) + assertThat(lastValueInTo).isEqualTo(expectedValues) + } + + at(48) { + val expectedValues = lerp(fromValues, toValues, fraction = 0.75f) + assertThat(lastValueInFrom).isEqualTo(expectedValues) + assertThat(lastValueInTo).isEqualTo(expectedValues) + } + + after { + assertThat(lastValueInFrom).isEqualTo(toValues) + assertThat(lastValueInTo).isEqualTo(toValues) + } + } + } + + @Test + fun movableAnimateSharedValues() { + val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red) + val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue) + + var lastValueInFrom = fromValues + var lastValueInTo = toValues + + rule.testTransition( + fromSceneContent = { + MovableFoo( + targetValues = fromValues, + onCurrentValueChanged = { lastValueInFrom = it } + ) + }, + toSceneContent = { + MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it }) + }, + transition = { + // The transition lasts 64ms = 4 frames. + spec = tween(durationMillis = 16 * 4, easing = LinearEasing) + }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + assertThat(lastValueInFrom).isEqualTo(fromValues) + + // to was not composed yet, so lastValueInTo was not set yet. + assertThat(lastValueInTo).isEqualTo(toValues) + } + + at(16) { + // Given that we use MovableElement here, animateSharedXAsState is composed only + // once, in the highest scene (in this case, in toScene). + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f)) + } + + at(32) { + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f)) + } + + at(48) { + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f)) + } + + after { + assertThat(lastValueInFrom).isEqualTo(fromValues) + assertThat(lastValueInTo).isEqualTo(toValues) + } + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt new file mode 100644 index 000000000000..4204cd5f0da0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -0,0 +1,207 @@ +/* + * 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.compose.animation.scene + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasParent +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.test.assertSizeIsEqualTo +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MovableElementTest { + @get:Rule val rule = createComposeRule() + + /** An element that displays a counter that is incremented whenever this element is clicked. */ + @Composable + private fun Counter(modifier: Modifier = Modifier) { + var count by remember { mutableIntStateOf(0) } + Box(modifier.fillMaxSize().clickable { count++ }) { Text("count: $count") } + } + + @Composable + private fun SceneScope.MovableCounter(key: ElementKey, modifier: Modifier) { + MovableElement(key, modifier) { Counter() } + } + + @Test + fun modifierElementIsDuplicatedDuringTransitions() { + rule.testTransition( + fromSceneContent = { + Box(Modifier.element(TestElements.Foo).size(50.dp)) { Counter() } + }, + toSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) { Counter() } }, + transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + // Click 3 times on the counter. + rule.onNodeWithText("count: 0").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 1").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 2").assertIsDisplayed().performClick() + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(50.dp, 50.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + at(32) { + // In the middle of the transition, there are 2 copies of the counter: the previous + // one from scene A (equal to 3) and the new one from scene B (equal to 0). + rule + .onNode( + hasText("count: 3") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) + ) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + rule + .onNode( + hasText("count: 0") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) + ) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + // There are exactly 2 counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(2) + } + + after { + // At the end of the transition, only the counter from scene B is composed. + rule + .onNodeWithText("count: 0") + .assertIsDisplayed() + .assertSizeIsEqualTo(100.dp, 100.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + } + } + + @Test + fun movableElementIsMovedAndComposedOnlyOnce() { + rule.testTransition( + fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) }, + toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) }, + transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + // Click 3 times on the counter. + rule.onNodeWithText("count: 0").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 1").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 2").assertIsDisplayed().performClick() + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(50.dp, 50.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + at(32) { + // During the transition, there is a single counter that is moved, with the current + // value. + rule + .onNode(hasText("count: 3")) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + after { + // At the end of the transition, the counter still has the current value. + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(100.dp, 100.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + } + } +} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt index 04b3f8a1dfe7..04b3f8a1dfe7 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 328866ea76ca..5afd420a5e16 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -117,7 +117,7 @@ class SceneTransitionLayoutTest { .size(size) .background(Color.Red) .element(TestElements.Foo) - .testTag(TestElements.Foo.name) + .testTag(TestElements.Foo.debugName) ) { // Offset the single child of Foo by some animated shared offset. val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo) @@ -129,7 +129,7 @@ class SceneTransitionLayoutTest { } .size(30.dp) .background(Color.Blue) - .testTag(TestElements.Bar.name) + .testTag(TestElements.Bar.debugName) ) } } diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 53ed2b5d3317..df3b72aa5533 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -63,7 +63,7 @@ class SwipeToSceneTest { { currentScene = it }, EmptyTestTransitions, state = layoutState, - modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.name), + modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName), ) { scene( TestScenes.SceneA, @@ -122,8 +122,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isTrue() + assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating back to A (currentScene = SceneA) given // that 55dp < positional threshold. @@ -135,8 +134,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isFalse() + assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene A. rule.waitForIdle() @@ -158,8 +156,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isTrue() + assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating to C (currentScene = SceneC) given // that 56dp >= positional threshold. @@ -171,8 +168,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isFalse() + assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt index 268057fd2f2c..e0ae1be69aaf 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt @@ -22,13 +22,13 @@ 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.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.SemanticsNodeInteractionCollection import androidx.compose.ui.test.hasParent import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.onAllNodesWithTag -import androidx.compose.ui.test.onNodeWithTag @DslMarker annotation class TransitionTestDsl @@ -63,6 +63,8 @@ interface TransitionTestBuilder { @TransitionTestDsl interface TransitionTestAssertionScope { + fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher + /** * Assert on [element]. * @@ -130,15 +132,19 @@ fun ComposeContentTestRule.testTransition( val test = transitionTest(builder) val assertionScope = object : TransitionTestAssertionScope { + override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher { + return if (scene == null) { + hasTestTag(element.testTag) + } else { + hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag)) + } + } + override fun onElement( element: ElementKey, scene: SceneKey? ): SemanticsNodeInteraction { - return if (scene == null) { - onNodeWithTag(element.testTag) - } else { - onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))) - } + return onNode(isElement(element, scene)) } override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection { diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt index 83572620c88a..b4c393e9bfbe 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt @@ -37,6 +37,9 @@ object TestElements { /** Value keys that can be reused by tests. */ object TestValues { val Value1 = ValueKey("Value1") + val Value2 = ValueKey("Value2") + val Value3 = ValueKey("Value3") + val Value4 = ValueKey("Value4") } // We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index fa94b25028a2..fa94b25028a2 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt index 8ef6757d33bd..8ef6757d33bd 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt index d1205e727cf9..d1205e727cf9 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt index 2a27763f1d5c..2a27763f1d5c 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt index 384355ca951f..384355ca951f 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt index 2af363860272..e94eff32c30c 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt @@ -32,7 +32,6 @@ import com.android.compose.animation.scene.TestElements import com.android.compose.animation.scene.TestScenes import com.android.compose.animation.scene.inScene import com.android.compose.animation.scene.testTransition -import com.android.compose.modifiers.size import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.onEach import org.junit.Rule diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt index 1d559fd6bd8a..1d559fd6bd8a 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt index 03d231a7fcc6..03d231a7fcc6 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt index 8e2b77a2f2a0..8e2b77a2f2a0 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt index d6f64bfe4974..d6f64bfe4974 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt index fbd1b512c50a..fbd1b512c50a 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt index bf7bf98878e6..bf7bf98878e6 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index be1e6554baf1..445bdc2c1936 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -2,45 +2,17 @@ # Needed to ensure callback field references are kept in their respective # owning classes when the downstream callback registrars only store weak refs. -# TODO(b/264686688): Handle these cases with more targeted annotations. --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - private com.android.keyguard.KeyguardUpdateMonitorCallback *; - private com.android.systemui.privacy.PrivacyConfig$Callback *; - private com.android.systemui.privacy.PrivacyItemController$Callback *; - private com.android.systemui.settings.UserTracker$Callback *; - private com.android.systemui.statusbar.phone.StatusBarWindowCallback *; - private com.android.systemui.util.service.Observer$Callback *; - private com.android.systemui.util.service.ObservableServiceConnection$Callback *; -} -# Note that these rules are temporary companions to the above rules, required -# for cases like Kotlin where fields with anonymous types use the anonymous type -# rather than the supertype. --if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyConfig$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyItemController$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.settings.UserTracker$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.util.service.Observer$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +# Note that we restrict this to SysUISingleton classes, as other registering +# classes should either *always* unregister or *never* register from their +# constructor. We also keep callback class names for easier debugging. +-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } --if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +-if class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml index 69f533c3bd47..67b4e4bc322b 100644 --- a/packages/SystemUI/res-keyguard/values-bn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml @@ -125,7 +125,7 @@ <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string> <string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string> <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"চালিয়ে যেতে আপনার ডিভাইস আনলক করুন"</string> - <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"পরে ইনস্টল আপডেট করতে পিন লিখুন"</string> + <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"পরে আপডেট ইনস্টল করতে পিন লিখুন"</string> <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"পরে আপডেট ইনস্টল করতে পাসওয়ার্ড লিখুন"</string> <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"পরে আপডেট ইনস্টল করতে প্যাটার্ন আঁকুন"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"ডিভাইস আপডেট করা হয়েছে। চালিয়ে যেতে পিন লিখুন।"</string> diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml index e075d850ba65..573638bcc135 100644 --- a/packages/SystemUI/res-keyguard/values-cs/strings.xml +++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml @@ -125,9 +125,9 @@ <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string> <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Pokud chcete pokračovat, odemkněte zařízení"</string> - <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Pokud aktualizaci chcete nainstalovat později, zadejte PIN"</string> - <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Pokud aktualizaci chcete nainstalovat později, zadejte heslo"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Pokud aktualizaci chcete nainstalovat později, zadejte gesto"</string> + <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Zadejte PIN a aktualizaci nainstalujte později"</string> + <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Zadejte heslo a aktualizaci nainstalujte později"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Zadejte gesto a aktualizaci nainstalujte později"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte PIN."</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte heslo."</string> <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte gesto."</string> diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml index 117f7a92cf01..5c5f264fe508 100644 --- a/packages/SystemUI/res-keyguard/values-de/strings.xml +++ b/packages/SystemUI/res-keyguard/values-de/strings.xml @@ -125,9 +125,9 @@ <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Gerät entsperren, um fortzufahren"</string> - <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Gib deine PIN ein, um das Update später zu installieren"</string> - <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Gib dein Passwort ein, um das Update später zu installieren"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Zeichne dein Muster, um das Update später zu installieren"</string> + <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"PIN eingeben, um Update später zu installieren"</string> + <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Passwort eingeben, um Update später zu installieren"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Muster zeichnen, um Update später zu installieren"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Gerät aktualisiert. Gib deine PIN ein, um fortzufahren."</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Gerät aktualisiert. Gib dein Passwort ein, um fortzufahren."</string> <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Gerät aktualisiert. Zeichne dein Muster, um fortzufahren."</string> diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml index cd7637c1b1bd..3a01da53dfce 100644 --- a/packages/SystemUI/res-keyguard/values-el/strings.xml +++ b/packages/SystemUI/res-keyguard/values-el/strings.xml @@ -125,9 +125,9 @@ <string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string> <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ξεκλειδώστε τη συσκευή σας για να συνεχίσετε"</string> - <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Εισαγάγετε το PIN για να εγκαταστήσετε την ενημέρωση αργότερα"</string> + <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Εισαγωγή PIN για εγκατάσταση ενημέρωσης αργότερα"</string> <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Εισαγ. τον κωδ. πρόσβασης για να εγκαταστήσετε την ενημέρωση αργότερα"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Σχεδιάστε το μοτίβο για να εγκαταστήσετε την ενημέρωση αργότερα"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Σχεδιάστε το μοτίβο για εγκατάσταση της ενημέρωσης αργότερα"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Η συσκευή ενημερώθηκε. Εισαγάγετε το PIN για να συνεχίσετε."</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Η συσκευή ενημερώθηκε. Εισαγάγ. τον κωδ. πρόσβασης για να συνεχίσετε."</string> <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Η συσκευή ενημερώθηκε. Σχεδιάστε το μοτίβο για να συνεχίσετε."</string> diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml index 4815815a1dc9..ae3f04a290e4 100644 --- a/packages/SystemUI/res-keyguard/values-fa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml @@ -125,9 +125,9 @@ <string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string> <string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string> <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"برای ادامه، قفل دستگاهتان را باز کنید"</string> - <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"برای نصب بهروزرسانی در فرصتی دیگر، پین را وارد کنید"</string> - <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"برای نصب بهروزرسانی در فرصتی دیگر، گذرواژه را وارد کنید"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"برای نصب بهروزرسانی در فرصتی دیگر، الگو را وارد کنید"</string> + <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"پین را وارد کنید و بهروزرسانی را در فرصتی دیگر انجام دهید"</string> + <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"گذرواژه را وارد کنید و بهروزرسانی را در فرصتی دیگر انجام دهید"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"الگو را وارد کنید و بهروزرسانی را در فرصتی دیگر انجام دهید"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"دستگاه بهروز شد. برای ادامه، پین را وارد کنید."</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"دستگاه بهروز شد. برای ادامه، گذرواژه را وارد کنید."</string> <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"دستگاه بهروز شد. برای ادامه، الگو را وارد کنید."</string> diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml index 02d41d8a22be..050df9983725 100644 --- a/packages/SystemUI/res-keyguard/values-fi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml @@ -127,7 +127,7 @@ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Jatka avaamalla laitteen lukitus"</string> <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Jos haluat asentaa päivityksen myöhemmin, lisää PIN-koodi"</string> <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Jos haluat asentaa päivityksen myöhemmin, lisää salasana"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Jos haluat asentaa päivityksen myöhemmin, piirrä kuvio"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Salli päivitys myöhemmin piirtämällä kuvio"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Laite päivitetty. Jatka lisäämällä PIN-koodi."</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Laite päivitetty. Jatka lisäämällä salasana."</string> <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Laite päivitetty. Jatka piirtämällä kuvio."</string> diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml index e5be788f7676..4309b56adc88 100644 --- a/packages/SystemUI/res-keyguard/values-ro/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml @@ -127,7 +127,7 @@ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Deblochează dispozitivul pentru a continua"</string> <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Introdu codul PIN pentru a instala actualizarea mai târziu"</string> <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Introdu parola pentru a instala actualizarea mai târziu"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează modelul pentru a instala actualizarea mai târziu"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează pentru a instala actualizarea mai târziu"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Dispozitivul s-a actualizat. Introdu codul PIN pentru a continua."</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Dispozitivul s-a actualizat. Introdu parola pentru a continua."</string> <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitivul s-a actualizat. Desenează modelul pentru a continua."</string> diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml index 59261a3d414b..4c65832162ba 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml @@ -125,9 +125,9 @@ <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string> <string name="clock_title_analog" msgid="8409262532900918273">"指针"</string> <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解锁设备才能继续操作"</string> - <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"需要输入 PIN 码才能稍后安装更新"</string> - <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"需要输入密码才能稍后安装更新"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"需要绘制解锁图案才能稍后安装更新"</string> + <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"请输入 PIN 码,系统稍后会安装更新"</string> + <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"请输入密码,系统稍后会安装更新"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"请绘制解锁图案,系统稍后会安装更新"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"设备已更新。您需要输入 PIN 码才能继续。"</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"设备已更新。您需要输入密码才能继续。"</string> <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"设备已更新。您需要绘制解锁图案才能继续。"</string> diff --git a/packages/SystemUI/res/color/qs_tile_ripple_color.xml b/packages/SystemUI/res/color/qs_tile_ripple_color.xml new file mode 100644 index 000000000000..c1062548aa89 --- /dev/null +++ b/packages/SystemUI/res/color/qs_tile_ripple_color.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?android:attr/colorControlHighlight" android:state_hovered="true" + android:state_pressed="true" /> + <!-- RippleDrawable has default way of handling hover state with highlighting but it's not + consistent with our approach so we make highlighting invisible and instead do custom handling + of hover state on a different level --> + <item android:color="@color/transparent" android:state_hovered="true" /> + <item android:color="?android:attr/colorControlHighlight" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_tile_background.xml b/packages/SystemUI/res/drawable/qs_tile_background.xml index 265f575fc99c..ef3c61bda15a 100644 --- a/packages/SystemUI/res/drawable/qs_tile_background.xml +++ b/packages/SystemUI/res/drawable/qs_tile_background.xml @@ -15,9 +15,25 @@ ~ limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?android:attr/colorControlHighlight"> + android:color="@color/qs_tile_ripple_color"> <item android:id="@android:id/mask" - android:drawable="@drawable/qs_tile_background_shape" /> - <item android:id="@id/background" - android:drawable="@drawable/qs_tile_background_shape"/> + android:drawable="@drawable/qs_tile_background_shape" + /> + <item android:id="@id/background"> + <layer-list> + <item + android:id="@+id/qs_tile_background_base" + android:drawable="@drawable/qs_tile_background_shape" /> + <item android:id="@+id/qs_tile_background_overlay"> + <selector> + <item + android:state_hovered="true" + android:drawable="@drawable/qs_tile_background_shape" /> + <item + android:state_focused="true" + android:drawable="@drawable/qs_tile_background_shape" /> + </selector> + </item> + </layer-list> + </item> </ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 1c7e9977afe5..6d779438ad6c 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -27,7 +27,6 @@ <ImageView android:id="@+id/bluetooth_device_icon" - android:contentDescription="@string/accessibility_bluetooth_device_icon" android:layout_width="24dp" android:layout_height="24dp" app:layout_constraintStart_toStartOf="parent" @@ -39,8 +38,12 @@ android:layout_width="0dp" android:id="@+id/bluetooth_device_name" style="@style/BluetoothTileDialog.DeviceName" + android:textDirection="locale" + android:textAlignment="gravity" android:paddingStart="20dp" android:paddingTop="10dp" + android:maxLines="1" + android:ellipsize="end" app:layout_constraintWidth_percent="0.7" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" @@ -55,6 +58,8 @@ style="@style/BluetoothTileDialog.DeviceSummary" android:paddingStart="20dp" android:paddingBottom="10dp" + android:maxLines="1" + android:ellipsize="end" app:layout_constraintWidth_percent="0.7" app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" @@ -66,6 +71,7 @@ android:id="@+id/gear_icon" android:layout_width="0dp" android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_gear" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name" app:layout_constraintEnd_toEndOf="@+id/gear_icon_image" app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index 16aeb951822c..5d986e00deed 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -27,6 +27,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="24dp" + android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical|center_horizontal" android:text="@string/quick_settings_bluetooth_label" @@ -58,9 +59,12 @@ style="@style/BluetoothTileDialog.Device" android:layout_width="0dp" android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" android:gravity="center_vertical" android:layout_marginTop="4dp" android:text="@string/turn_on_bluetooth" + android:clickable="false" android:textAppearance="@style/TextAppearance.Dialog.Body.Message" android:textSize="16sp" app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle" @@ -84,53 +88,17 @@ app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title" app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" /> - <androidx.constraintlayout.widget.Group - android:id="@+id/pair_new_device_layout_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="ic_add,pair_new_device_text" /> - - <ImageView - android:id="@+id/ic_add" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginStart="36dp" - android:gravity="center_vertical" - android:importantForAccessibility="no" - android:src="@drawable/ic_add" - app:layout_constraintBottom_toTopOf="@id/device_list" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/pair_new_device_text" - app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title" - android:tint="?android:attr/textColorPrimary" /> - - <TextView - android:id="@+id/pair_new_device_text" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="@dimen/bluetooth_dialog_device_height" - android:gravity="center_vertical" - android:layout_marginStart="0dp" - android:paddingStart="20dp" - android:text="@string/pair_new_bluetooth_devices" - android:textSize="14sp" - android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintBottom_toTopOf="@id/device_list" - app:layout_constraintStart_toEndOf="@+id/ic_add" - app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title" - app:layout_constraintEnd_toEndOf="parent" /> - <androidx.recyclerview.widget.RecyclerView android:id="@+id/device_list" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="20dp" android:nestedScrollingEnabled="false" android:overScrollMode="never" android:scrollbars="vertical" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" + app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle" app:layout_constraintBottom_toTopOf="@+id/see_all_text" /> <androidx.constraintlayout.widget.Group @@ -148,7 +116,7 @@ android:importantForAccessibility="no" android:gravity="center_vertical" android:src="@drawable/ic_arrow_forward" - app:layout_constraintBottom_toTopOf="@+id/done_button" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/see_all_text" app:layout_constraintTop_toBottomOf="@id/device_list" /> @@ -157,18 +125,59 @@ android:id="@+id/see_all_text" style="@style/BluetoothTileDialog.Device" android:layout_width="0dp" - android:layout_height="@dimen/bluetooth_dialog_device_height" + android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" android:gravity="center_vertical" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/see_all_bluetooth_devices" android:textSize="14sp" android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintBottom_toTopOf="@+id/done_button" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" app:layout_constraintStart_toEndOf="@+id/ic_arrow" app:layout_constraintTop_toBottomOf="@id/device_list" app:layout_constraintEnd_toEndOf="parent" /> + <androidx.constraintlayout.widget.Group + android:id="@+id/pair_new_device_layout_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + + <ImageView + android:id="@+id/ic_add" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginStart="36dp" + android:gravity="center_vertical" + android:importantForAccessibility="no" + android:src="@drawable/ic_add" + app:layout_constraintBottom_toTopOf="@id/done_button" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/pair_new_device_text" + app:layout_constraintTop_toBottomOf="@id/see_all_text" + android:tint="?android:attr/textColorPrimary" /> + + <TextView + android:id="@+id/pair_new_device_text" + style="@style/BluetoothTileDialog.Device" + android:layout_width="0dp" + android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:layout_marginStart="0dp" + android:paddingStart="20dp" + android:text="@string/pair_new_bluetooth_devices" + android:textSize="14sp" + android:textAppearance="@style/TextAppearance.Dialog.Title" + app:layout_constraintBottom_toTopOf="@id/done_button" + app:layout_constraintStart_toEndOf="@+id/ic_add" + app:layout_constraintTop_toBottomOf="@id/see_all_text" + app:layout_constraintEnd_toEndOf="parent" /> + <Button android:id="@+id/done_button" style="@style/Widget.Dialog.Button" @@ -184,5 +193,5 @@ android:text="@string/inline_done_button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/see_all_text" /> + app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" /> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml index a51c55ee965f..8cfcb689eced 100644 --- a/packages/SystemUI/res/layout/connected_display_dialog.xml +++ b/packages/SystemUI/res/layout/connected_display_dialog.xml @@ -15,8 +15,9 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/cd_bottom_sheet" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" android:paddingHorizontal="@dimen/dialog_side_padding" @@ -26,11 +27,14 @@ <ImageView android:id="@+id/connected_display_dialog_icon" - android:layout_width="@dimen/screenrecord_logo_size" - android:layout_height="@dimen/screenrecord_logo_size" + android:layout_width="@dimen/connected_display_dialog_logo_size" + android:layout_height="@dimen/connected_display_dialog_logo_size" + android:background="@drawable/circular_background" + android:backgroundTint="?androidprv:attr/materialColorPrimary" android:importantForAccessibility="no" + android:padding="6dp" android:src="@drawable/stat_sys_connected_display" - android:tint="?androidprv:attr/materialColorPrimary" /> + android:tint="?androidprv:attr/materialColorOnPrimary" /> <TextView android:id="@+id/connected_display_dialog_title" diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index ec006c553b94..16eba220cf5d 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -403,7 +403,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="start|center_vertical" - android:orientation="vertical"> + android:orientation="horizontal"> <Button android:id="@+id/apm_button" android:layout_width="wrap_content" @@ -414,12 +414,7 @@ style="@style/Widget.Dialog.Button.BorderButton" android:clickable="true" android:focusable="true"/> - </LinearLayout> - <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center_vertical"> <Button android:id="@+id/share_wifi_button" android:layout_width="wrap_content" @@ -430,8 +425,14 @@ android:ellipsize="end" android:clickable="true" android:focusable="true" - android:layout_alignParentLeft="true" android:visibility="gone"/> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_gravity="end|center_vertical"> <Button android:id="@+id/done_button" android:layout_width="wrap_content" @@ -441,9 +442,8 @@ android:maxLines="1" android:ellipsize="end" android:clickable="true" - android:focusable="true" - android:layout_alignParentRight="true"/> - </RelativeLayout> + android:focusable="true"/> + </LinearLayout> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml index b00908fd2bfa..c1bac3151049 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml @@ -15,7 +15,7 @@ --> <!-- Extends Framelayout --> -<com.android.systemui.statusbar.notification.row.FooterView +<com.android.systemui.statusbar.notification.footer.ui.view.FooterView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" @@ -76,4 +76,4 @@ /> </androidx.constraintlayout.widget.ConstraintLayout> </com.android.systemui.statusbar.AlphaOptimizedFrameLayout> -</com.android.systemui.statusbar.notification.row.FooterView> +</com.android.systemui.statusbar.notification.footer.ui.view.FooterView> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 937e97a5fc2b..24846d9ae16e 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stel versteknotasapp in Instellings"</string> <string name="install_app" msgid="5066668100199613936">"Installeer app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Sinkroniseer wedersyds na eksterne skerm?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Sinkroniseer skerm wedersyds"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Maak toe"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoon en kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Onlangse appgebruik"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sien onlangse toegang"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index de2fda491f78..23def2894368 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"በቅንብሮች ውስጥ ነባሪ የማስታወሻዎች መተግበሪያን ያቀናብሩ"</string> <string name="install_app" msgid="5066668100199613936">"መተግበሪያን ጫን"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ወደ ውጫዊ ማሳያ ይንጸባረቅ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ማሳያን አንጸባርቅ"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"አሰናብት"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"ማይክሮፎን እና ካሜራ"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"የቅርብ ጊዜ የመተግበሪያ አጠቃቀም"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"የቅርብ ጊዜ መዳረሻን አሳይ"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 79b671ccd4e9..80d63a23fd94 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"يمكنك ضبط تطبيق تدوين الملاحظات التلقائي في \"الإعدادات\"."</string> <string name="install_app" msgid="5066668100199613936">"تثبيت التطبيق"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"هل تريد بث محتوى جهازك على الشاشة الخارجية؟"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"بث المحتوى على الشاشة"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"إغلاق"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"الميكروفون والكاميرا"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"آخر استخدام في التطبيقات"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"عرض آخر استخدام في التطبيقات"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index ac30f182f521..c84577373919 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ছেটিঙত টোকাৰ ডিফ’ল্ট এপ্ ছেট কৰক"</string> <string name="install_app" msgid="5066668100199613936">"এপ্টো ইনষ্টল কৰক"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"বাহ্যিক ডিছপ্লে’লৈ মিৰ’ৰ কৰিবনে?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ডিছপ্লে’ মিৰ’ৰ কৰক"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"অগ্ৰাহ্য কৰক"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্ৰ’ফ’ন আৰু কেমেৰা"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"শেহতীয়া এপৰ ব্যৱহাৰ"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"শেহতীয়া এক্সেছ চাওক"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 3ca61dc4f575..5aa26bac0687 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlər tətbiqi ayarlayın"</string> <string name="install_app" msgid="5066668100199613936">"Tətbiqi quraşdırın"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Xarici displeyə əks etdirilsin?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Displeyi əks etdirin"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"İmtina edin"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon və kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son tətbiq istifadəsi"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son girişə baxın"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index b51d4b355b19..cd36884e4740 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string> <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li da preslikate na spoljnji ekran?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno koristila aplikacija"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 35a84b0afee1..1b03c8ba09f6 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайце ў Наладах стандартную праграму для нататак"</string> <string name="install_app" msgid="5066668100199613936">"Усталяваць праграму"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Адлюстраваць на знешнім дысплеі?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Адлюстраваць дысплэй"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыць"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрафон і камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нядаўна выкарыстоўваліся праграмамі"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Паглядзець нядаўні доступ"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 6b212e565a59..4ed1ad944045 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартно приложение за бележки от настройките"</string> <string name="install_app" msgid="5066668100199613936">"Инсталиране на приложението"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се дублира ли на външния екран?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Огледално копиране на дисплея"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Отхвърляне"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Скорошно използване на приложението"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Вижте скорошния достъп"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 5b5ac73430e3..426d38d68a0e 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"\'সেটিংস\' থেকে ডিফল্ট নোট নেওয়ার অ্যাপ সেট করুন"</string> <string name="install_app" msgid="5066668100199613936">"অ্যাপ ইনস্টল করুন"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"এক্সটার্নাল ডিসপ্লে আয়না?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ডিসপ্লে দেখান"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"বাতিল করুন"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্রোফোন ও ক্যামেরা"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"সম্প্রতি ব্যবহার করা অ্যাপ"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"সাম্প্রতিক অ্যাক্সেস দেখুন"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 33330b737edc..4eed7b8965ba 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string> <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Preslikati na vanjski ekran?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno korištenje aplikacije"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 45bb34253a86..b55a79ad8dd7 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string> <string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vols replicar-ho a la pantalla externa?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Duplica la pantalla"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Ignora"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Micròfon i càmera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ús recent de l\'aplicació"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Mostra l\'accés recent"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 0d7d23a34ef7..05b04191f6c7 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string> <string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Zavřít"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon a fotoaparát"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávné použití aplikacemi"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobrazit nedávný přístup"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 4f6b88ec74b3..5971034b991c 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string> <string name="install_app" msgid="5066668100199613936">"Installer app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du spejle til ekstern skærm?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Spejl skærm"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Luk"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Seneste brug af apps"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se seneste adgang"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 07aa14f400dc..c773f71d7786 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string> <string name="install_app" msgid="5066668100199613936">"App installieren"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Auf externen Bildschirm spiegeln?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Bildschirm spiegeln"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Schließen"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon & Kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kürzliche App-Nutzung"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kürzliche Zugriffe ansehen"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 2a8e51cfbc7f..b6c95aa50bfd 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ορίστε την προεπιλεγμένη εφαρμογή σημειώσεων στις Ρυθμίσεις"</string> <string name="install_app" msgid="5066668100199613936">"Εγκατάσταση εφαρμογής"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Κατοπτρισμός σε εξωτερική οθόνη;"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Κατοπτρισμός οθόνης"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Παράβλεψη"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Μικρόφωνο και Κάμερα"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Πρόσφατη χρήση εφαρμογής"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Εμφάνιση πρόσφατης πρόσβασης"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 8647adb2934f..6c2b2307fccf 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string> <string name="install_app" msgid="5066668100199613936">"Install app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 8647adb2934f..6c2b2307fccf 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string> <string name="install_app" msgid="5066668100199613936">"Install app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 8647adb2934f..6c2b2307fccf 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string> <string name="install_app" msgid="5066668100199613936">"Install app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index d3ea858c3137..52514d22f927 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string> <string name="install_app" msgid="5066668100199613936">"Instalar app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Quieres duplicar a la pantalla externa?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Duplicar pantalla"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Descartar"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente en apps"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver accesos recientes"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 284fad4d5b89..4a3c06412802 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string> <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Replicar en pantalla externa?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Replicar pantalla"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Cerrar"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente en aplicaciones"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso reciente"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 2b79ee5f09c1..08b489d11d7e 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string> <string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kas peegeldada välisekraanile?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Ekraani peegeldamine"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Loobu"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ja kaamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Rakenduste hiljutine kasutamine"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kuva hiljutine juurdepääs"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 68698ea2cc14..19495bccb4f8 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -676,8 +676,8 @@ <string name="group_system_go_back" msgid="8838454003680364227">"Atzera: itzuli aurreko egoerara (atzera egiteko botoia)"</string> <string name="group_system_access_home_screen" msgid="1857344316928441909">"Atzitu hasierako pantaila"</string> <string name="group_system_overview_open_apps" msgid="6897128761003265350">"Ikusi irekitako aplikazioen ikuspegi orokorra"</string> - <string name="group_system_cycle_forward" msgid="9202444850838205990">"Joan azken aplikazioetako batetik bestera (aurrera)"</string> - <string name="group_system_cycle_back" msgid="5163464503638229131">"Joan azken aplikazioetako batetik bestera (atzera)"</string> + <string name="group_system_cycle_forward" msgid="9202444850838205990">"Joan azkenaldian erabilitako aplikazio batetik bestera (aurrera)"</string> + <string name="group_system_cycle_back" msgid="5163464503638229131">"Joan azkenaldian erabilitako aplikazio batetik bestera (atzera)"</string> <string name="group_system_access_all_apps_search" msgid="488070738028991753">"Atzitu aplikazio guztien zerrenda eta bilatu (adibidez, bilatzeko aukeraren edo Exekutatzeko tresna aplikazioaren bidez)"</string> <string name="group_system_hide_reshow_taskbar" msgid="3809304065624351131">"Ezkutatu eta erakutsi (berriro) zereginen barra"</string> <string name="group_system_access_system_settings" msgid="7961639365383008053">"Atzitu sistemaren ezarpenak"</string> @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string> <string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kanpoko pantailan islatu nahi duzu?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Islatu pantaila"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Baztertu"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonoa eta kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikazioen azken erabilera"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ikusi azkenaldiko sarbidea"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 719924037534..68f6c1d9dd88 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"برنامه پیشفرض یادداشت را در «تنظیمات» تنظیم کنید"</string> <string name="install_app" msgid="5066668100199613936">"نصب برنامه"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"در نمایشگر خارجی پخش شود؟"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"بازتاباندن صفحهنمایش"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"بستن"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"میکروفون و دوربین"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"استفاده اخیر از برنامه"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"دیدن دسترسی اخیر"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 073ea60b28f4..934abad4b225 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string> <string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Peilataanko ulkoiselle näytölle?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Peilaa näyttö"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Ohita"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni ja kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Sovellusten viimeaikainen käyttö"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Katso viimeaikainen käyttö"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 52bcd7652af3..fe90569169d9 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string> <string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer l\'écran sur un moniteur externe?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone et appareil photo"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente par les applications"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Afficher l\'accès récent"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 4362d3c227fb..27a4bb6a0e1f 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string> <string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirroring sur écran externe ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Micro et caméra"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente par les applis"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consulter les accès récents"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 1b836a8ea688..2056c2fba14b 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string> <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Queres proxectar contido nunha pantalla externa?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Replicar pantalla"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Pechar"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono e cámara"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente por parte de aplicacións"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso recente"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 132ee2e73513..84be50a481e8 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"સેટિંગમાં નોંધની ડિફૉલ્ટ ઍપ સેટ કરો"</string> <string name="install_app" msgid="5066668100199613936">"ઍપ ઇન્સ્ટૉલ કરો"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"શું બાહ્ય ડિસ્પ્લે પર મિરર કરીએ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"મિરર ડિસ્પ્લે"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"છોડી દો"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"માઇક્રોફોન અને કૅમેરા"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"તાજેતરનો ઍપનો વપરાશ"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"તાજેતરનો ઍક્સેસ મેનેજ કરો"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index f942de505920..2b0019f5274e 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग में जाकर, नोट लेने की सुविधा देने वाले ऐप्लिकेशन को डिफ़ॉल्ट के तौर पर सेट करें"</string> <string name="install_app" msgid="5066668100199613936">"ऐप्लिकेशन इंस्टॉल करें"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाहरी डिसप्ले को अन्य डिवाइस पर दिखाना है?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"मिरर डिसप्ले"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"खारिज करें"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफ़ोन और कैमरा"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"हाल ही में इस्तेमाल करने वाला ऐप्लिकेशन"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हाल में ऐक्सेस करने वाले ऐप"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index f7c68e160b63..23899fcb2110 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string> <string name="install_app" msgid="5066668100199613936">"Instalacija"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li zrcaliti na vanjski zaslon?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Zrcaljenje zaslona"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna upotreba aplikacije"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Pogledajte nedavni pristup"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index c1b38d904c4d..e5b17d9c6517 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítő alkalmazást a Beállításokban"</string> <string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tükrözi a kijelzőt a külső képernyőre?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Kijelző tükrözése"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Elvetés"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon és kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Legutóbbi alkalmazáshasználat"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Legutóbbi hozzáférés"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 7139a1f844da..4a1c291e3d74 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Կարգավորեք նշումների կանխադրված հավելված Կարգավորումներում"</string> <string name="install_app" msgid="5066668100199613936">"Տեղադրել հավելվածը"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Հայելապատճենե՞լ արտաքին էկրանին"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Հայելապատճենել էկրանը"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Փակել"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Խոսափող և տեսախցիկ"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Հավելվածի վերջին օգտագործումը"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Տեսնել վերջին օգտագործումը"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 44aafde6f132..18a76686154f 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string> <string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Cerminkan ke layar eksternal?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Cerminkan layar"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Tutup"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon & Kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan aplikasi baru-baru ini"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaru"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 1fda572ae10e..70bed4665e17 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string> <string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spegla yfir á ytri skjá?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Spegla skjá"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Hunsa"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Hljóðnemi og myndavél"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nýlega notað af forriti"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sjá nýlegan aðgang"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index ed8885f6e360..baffdb9778cd 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string> <string name="install_app" msgid="5066668100199613936">"Installa app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vuoi eseguire il mirroring al display esterno?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Esegui il mirroring del display"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Chiudi"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfono e fotocamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente da app"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vedi accesso recente"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 51f5452d843f..968a98287275 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"צריך להגדיר את אפליקציית ברירת המחדל לפתקים ב\'הגדרות\'"</string> <string name="install_app" msgid="5066668100199613936">"התקנת האפליקציה"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"לשקף למסך חיצוני?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"תצוגת מראה"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"סגירה"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"מיקרופון ומצלמה"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"נעשה שימוש לאחרונה באפליקציות"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"צפייה בהרשאות הגישה האחרונות"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index e77aed9b1be7..798d42add510 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"[設定] でデフォルトのメモアプリを設定してください"</string> <string name="install_app" msgid="5066668100199613936">"アプリをインストール"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"外部ディスプレイにミラーリングしますか?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ディスプレイをミラーリングする"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"閉じる"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"マイクとカメラ"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近のアプリの使用状況"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"最近のアクセスを表示"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 8d7deec249f8..f21a2a42536b 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"დააყენეთ ნაგულისხმევი შენიშვნების აპი პარამეტრებში"</string> <string name="install_app" msgid="5066668100199613936">"აპის ინსტალაცია"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"აირეკლოს გარე ეკრანზე?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ეკრანის არეკვლა"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"დახურვა"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"მიკროფონი და კამერა"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"აპის ბოლოდროინდელი გამოყენება"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ბოლო წვდომის ნახვა"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index fc4fe8a2a26d..746c02ed780f 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден әдепкі жазба қолданбасын орнатыңыз."</string> <string name="install_app" msgid="5066668100199613936">"Қолданбаны орнату"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Сыртқы экран арқылы да көрсету керек пе?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Айна дисплей"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Қабылдамау"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон және камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Соңғы рет қолданбаның датчикті пайдалануы"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Соңғы рет пайдаланғандар"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 10c291dec4ab..9f3b99121b19 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"កំណត់កម្មវិធីកំណត់ចំណាំលំនាំដើមនៅក្នុងការកំណត់"</string> <string name="install_app" msgid="5066668100199613936">"ដំឡើងកម្មវិធី"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"បញ្ចាំងទៅឧបករណ៍បញ្ចាំងខាងក្រៅឬ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"បញ្ចាំងទៅផ្ទាំងអេក្រង់"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ច្រានចោល"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"មីក្រូហ្វូន និងកាមេរ៉ា"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"ការប្រើប្រាស់កម្មវិធីថ្មីៗនេះ"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"មើលការចូលប្រើនាពេលថ្មីៗនេះ"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 77169809b914..79eef7295bc0 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಡೀಫಾಲ್ಟ್ ಟಿಪ್ಪಣಿಗಳ ಆ್ಯಪ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string> <string name="install_app" msgid="5066668100199613936">"ಆ್ಯಪ್ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ಬಾಹ್ಯ ಡಿಸ್ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಬೇಕೆ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ಮಿರರ್ ಡಿಸ್ಪ್ಲೇ"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ವಜಾಗೊಳಿಸಿ"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಕ್ಯಾಮರಾ"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"ಇತ್ತೀಚಿನ ಆ್ಯಪ್ ಬಳಕೆ"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ಇತ್ತೀಚಿನ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನೋಡಿ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index c2fbf093160c..7985e4c7e3af 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"설정에서 기본 메모 앱 설정"</string> <string name="install_app" msgid="5066668100199613936">"앱 설치"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"외부 디스플레이로 미러링하시겠습니까?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"디스플레이 미러링"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"닫기"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"마이크 및 카메라"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"최근 앱 사용"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"최근 액세스 보기"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 8ab4cb7d9128..ded1f752ef87 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден демейки кыска жазуулар колдонмосун тууралаңыз"</string> <string name="install_app" msgid="5066668100199613936">"Колдонмону орнотуу"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Тышкы экранга чыгарасызбы?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Тышкы экран"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Жабуу"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон жана камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Жакында колдонмолордо иштетилген"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Акыркы пайдалануусун көрүү"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index a1585f2d69d7..1e61b8a85fb1 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ຕັ້ງຄ່າແອັບຈົດບັນທຶກເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string> <string name="install_app" msgid="5066668100199613936">"ຕິດຕັ້ງແອັບ"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ສາຍໃສ່ຈໍສະແດງຜົນພາຍນອກບໍ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ຈໍສະແດງຜົນແບບສະທ້ອນ"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ປິດໄວ້"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"ໄມໂຄຣໂຟນ ແລະ ກ້ອງຖ່າຍຮູບ"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"ການໃຊ້ແອັບຫຼ້າສຸດ"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ເບິ່ງສິດເຂົ້າເຖິງຫຼ້າສຸດ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index c5f649c82dc0..06c1369d29ad 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytąją užrašų programą Nustatymuose"</string> <string name="install_app" msgid="5066668100199613936">"Įdiegti programą"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Bendrinti ekrano vaizdą išoriniame ekrane?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Bendrinti ekrano vaizdą"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Atsisakyti"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonas ir fotoaparatas"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Pastarasis programos naudojimas"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Žr. pastarąją prieigą"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index d49360919a32..fdf30285cee4 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Iestatījumos iestatiet noklusējuma piezīmju lietotni."</string> <string name="install_app" msgid="5066668100199613936">"Instalēt lietotni"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vai spoguļot ārējā displejā?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Spoguļot displeju"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Nerādīt"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofons un kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nesen izmantoja lietotnes"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Skatīt neseno piekļuvi"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 46193b3a6855..80ef7bb64d07 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Поставете стандардна апликација за белешки во „Поставки“"</string> <string name="install_app" msgid="5066668100199613936">"Инсталирајте ја апликацијата"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се синхронизира на надворешниот екран?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Отфрли"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Неодамнешно користење на апликација"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Видете го скорешниот пристап"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index d63153882e72..5b1c80f37a18 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ക്രമീകരണത്തിൽ കുറിപ്പുകൾക്കുള്ള ഡിഫോൾട്ട് ആപ്പ് സജ്ജീകരിക്കുക"</string> <string name="install_app" msgid="5066668100199613936">"ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യൂ"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ബാഹ്യ ഡിസ്പ്ലേയിലേക്ക് മിറർ ചെയ്യണോ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"മിറർ ഡിസ്പ്ലേ"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ഡിസ്മിസ് ചെയ്യുക"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"മൈക്രോഫോണും ക്യാമറയും"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"അടുത്തിടെയുള്ള ആപ്പ് ഉപയോഗം"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"അടുത്തിടെയുള്ള ആക്സസ് കാണുക"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index b16b22a4e041..3f65931f276e 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Тохиргоонд тэмдэглэлийн өгөгдмөл апп тохируулна уу"</string> <string name="install_app" msgid="5066668100199613936">"Аппыг суулгах"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Гадны дэлгэцэд тусгал үүсгэх үү?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Дэлгэцийн тусгал үүсгэх"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Хаах"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон болон камер"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Аппын саяхны ашиглалт"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Саяхны хандалтыг харах"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 16fac23db53f..238aacab4d32 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग्ज मध्ये डीफॉल्ट टिपा अॅप सेट करा"</string> <string name="install_app" msgid="5066668100199613936">"अॅप इंस्टॉल करा"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेवर मिरर करायचे आहे का?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर करा"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"डिसमिस करा"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"मायक्रोफोन आणि कॅमेरा"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"अलीकडील अॅप वापर"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"अलीकडील अॅक्सेस पहा"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 7cafdcde5e79..8c764f1e7f75 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string> <string name="install_app" msgid="5066668100199613936">"Pasang apl"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Paparkan pada paparan luaran?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Segerakkan paparan"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Ketepikan"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon & Kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan apl terbaharu"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaharu"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 620124eb5cec..883b9e95144a 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ဆက်တင်များတွင် မူရင်းမှတ်စုများအက်ပ် သတ်မှတ်ပါ"</string> <string name="install_app" msgid="5066668100199613936">"အက်ပ် ထည့်သွင်းရန်"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ပြင်ပဖန်သားပြင်သို့ စကရင်ပွားမလား။"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ဖန်သားပြင်ကို စကရင်ပွားရန်"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ပယ်ရန်"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"မိုက်ခရိုဖုန်းနှင့် ကင်မရာ"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"လတ်တလော အက်ပ်အသုံးပြုမှု"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"လတ်တလောအသုံးပြုမှုကို ကြည့်ရန်"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 722cc12d05f4..53a4c527cca6 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string> <string name="install_app" msgid="5066668100199613936">"Installer appen"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du speile til en ekstern skjerm?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Speil skjermen"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Lukk"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nylig appbruk"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se nylig tilgang"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 74e5c6f328f8..3b31b3adfd64 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिङमा गई नोट बनाउने डिफल्ट एप तोक्नुहोस्"</string> <string name="install_app" msgid="5066668100199613936">"एप इन्स्टल गर्नुहोस्"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेमा मिरर गर्ने हो?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर गर्नुहोस्"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"खारेज गर्नुहोस्"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफोन तथा क्यामेरा"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"एपको हालसालैको प्रयोग"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हालसालै एक्सेस गर्ने एप हेर्नुहोस्"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 6d97a9ffc805..664af5ec84eb 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string> <string name="install_app" msgid="5066668100199613936">"App installeren"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Sluiten"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfoon en camera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app-gebruik"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Recente toegang bekijken"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index a6e14f990820..db9e0c5debd8 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ସେଟିଂସରେ ଡିଫଲ୍ଟ ନୋଟ୍ସ ଆପ ସେଟ କରନ୍ତୁ"</string> <string name="install_app" msgid="5066668100199613936">"ଆପ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମିରର କରିବେ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ଡିସପ୍ଲେ ମିରର କରନ୍ତୁ"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ଖାରଜ କରନ୍ତୁ"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"ମାଇକ୍ରୋଫୋନ ଏବଂ କେମେରା"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"ବର୍ତ୍ତମାନର ଆପ ବ୍ୟବହାର"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ବର୍ତ୍ତମାନର ଆକ୍ସେସ ଦେଖନ୍ତୁ"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index faca7b13eae1..591c5e781382 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੋਟ ਐਪ ਨੂੰ ਸੈੱਟ ਕਰੋ"</string> <string name="install_app" msgid="5066668100199613936">"ਐਪ ਸਥਾਪਤ ਕਰੋ"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ਕੀ ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰਨਾ ਹੈ?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰੋ"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ਖਾਰਜ ਕਰੋ"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਕੈਮਰਾ"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"ਹਾਲ ਹੀ ਵਿੱਚ ਵਰਤੀ ਗਈ ਐਪ"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ਹਾਲੀਆ ਪਹੁੰਚ ਦੇਖੋ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 3bb539a21f6f..195122113140 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyślną aplikację do obsługi notatek w Ustawieniach"</string> <string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikację"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Powielić na wyświetlaczu zewnętrznym?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Powielaj obraz"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Zamknij"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i aparat"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikacje korzystające w ostatnim czasie"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobacz ostatni dostęp"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 8f93b73c3f85..693a3a1b7030 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string> <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 02ee09836610..c4f1f6790907 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string> <string name="install_app" msgid="5066668100199613936">"Instalar app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para o ecrã externo?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Espelhar ecrã"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorar"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmara"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilização recente da app"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acesso recente"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 8f93b73c3f85..693a3a1b7030 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string> <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 14cdeaccdc5c..a942332e2472 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setează aplicația prestabilită de note din Setări"</string> <string name="install_app" msgid="5066668100199613936">"Instalează aplicația"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Oglindești pe ecranul extern?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Afișare în oglindă"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Închide"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfon și cameră"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilizare recentă în aplicații"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vezi accesarea recentă"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index c5379c20b7ba..68601d6a71ce 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартное приложение для заметок в настройках."</string> <string name="install_app" msgid="5066668100199613936">"Установить приложение"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублировать на внешний дисплей?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Дублировать дисплей"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыть"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавнее использование приложениями"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Посмотреть недавний доступ"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index d53f0eba6e4d..86cb3c324143 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"සැකසීම් තුළ පෙරනිමි සටහන් යෙදුම සකසන්න"</string> <string name="install_app" msgid="5066668100199613936">"යෙදුම ස්ථාපනය කරන්න"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"බාහිර සංදර්ශකයට දර්පණය කරන්න ද?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"සංදර්ශකය දර්පණය කරන්න"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"අස් කරන්න"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"මයික්රොෆෝනය සහ කැමරාව"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"මෑත යෙදුම් භාවිතය"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"මෑත ප්රවේශය බලන්න"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 7fa0d077918f..56b9f7549038 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string> <string name="install_app" msgid="5066668100199613936">"Inštalovať aplikáciu"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Chcete zrkadliť na externú obrazovku?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Zrkadliť obrazovku"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Zavrieť"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofón a fotoaparát"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávne využitie aplikácie"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobraziť nedávny prístup"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 87c18f4cabed..b72f750eae7c 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string> <string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite zrcaliti v zunanji zaslon?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Zrcali zaslon"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Opusti"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon in fotoaparat"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna uporaba v aplikacijah"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ogled nedavnih dostopov"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 3e1471a68af2..92f6f9c27e77 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string> <string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Të pasqyrohet në ekranin e jashtëm?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Pasqyro ekranin"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Hiq"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni dhe kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Përdorimi i fundit i aplikacionit"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Shiko qasjen e fundit"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 07897d720128..7c0d9be76436 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Подесите подразумевану апликацију за белешке у Подешавањима"</string> <string name="install_app" msgid="5066668100199613936">"Инсталирај апликацију"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Желите ли да пресликате на спољњи екран?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Одбаци"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавно користила апликација"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Прикажи недавни приступ"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index aee168e24329..323ce11f853d 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string> <string name="install_app" msgid="5066668100199613936">"Installera appen"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vill du spegla till extern skärm?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Spegla skärm"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorera"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon och kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Senaste appanvändning"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se senaste åtkomst"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index be3baddbe912..304910a46526 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string> <string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ungependa kuonyesha kwenye skrini ya nje?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Akisi skrini"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Ondoa"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Maikrofoni na Kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Matumizi ya programu hivi majuzi"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Angalia ufikiaji wa majuzi"</string> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index ea3c012afc43..1f671ac4c875 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -37,6 +37,9 @@ <bool name="config_use_large_screen_shade_header">true</bool> + <!-- Whether to show bottom sheets edge to edge --> + <bool name="config_edgeToEdgeBottomSheetDialog">false</bool> + <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a string with two parts: the ID of the slot and the comma-delimited list of affordance IDs, separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 785c4d5af8b6..3ac5e2e7c8cc 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"குறிப்பு எடுப்பதற்கான இயல்புநிலை ஆப்ஸை அமைப்புகளில் அமையுங்கள்"</string> <string name="install_app" msgid="5066668100199613936">"ஆப்ஸை நிறுவுங்கள்"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"வெளிப்புறக் காட்சிக்கு மிரர் செய்யவா?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"டிஸ்பிளேயை மிரர் செய்"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"வேண்டாம்"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"மைக்ரோஃபோனும் கேமராவும்"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"சமீபத்திய ஆப்ஸ் பயன்பாடு"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"சமீபத்திய அணுகலைக் காட்டு"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 96d77b7237ba..0526f95a8e84 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"సెట్టింగ్లలో ఆటోమేటిక్గా ఉండేలా ఒక నోట్స్ యాప్ను సెట్ చేసుకోండి"</string> <string name="install_app" msgid="5066668100199613936">"యాప్ను ఇన్స్టాల్ చేయండి"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"బాహ్య డిస్ప్లేను మిర్రర్ చేయాలా?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"మిర్రర్ డిస్ప్లే"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"విస్మరించండి"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"మైక్రోఫోన్ & కెమెరా"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"ఇటీవలి యాప్ వినియోగం"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ఇటీవలి యాక్సెస్ను చూడండి"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index f8fbfbe3c7d3..8949360f9508 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"กำหนดแอปการจดบันทึกเริ่มต้นในการตั้งค่า"</string> <string name="install_app" msgid="5066668100199613936">"ติดตั้งแอป"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"มิเรอร์ไปยังจอแสดงผลภายนอกไหม"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"มิเรอร์จอแสดงผล"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"ปิด"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"ไมโครโฟนและกล้อง"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"การใช้แอปครั้งล่าสุด"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ดูการเข้าถึงล่าสุด"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 435398db067d..e5c11df40e99 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string> <string name="install_app" msgid="5066668100199613936">"I-install ang app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"I-mirror sa external na display?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"I-mirror ang display"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"I-dismiss"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikropono at Camera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kamakailang paggamit ng app"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Tingnan ang kamakailang access"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index d0ddbec98faf..3552d9214fb0 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string> <string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Harici ekrana yansıtılsın mı?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Ekranı yansıt"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Kapat"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ve Kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son uygulama kullanımı"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son erişimi göster"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 1489dc77a9f1..2e6aea9d59d3 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Призначте стандартний додаток для нотаток у налаштуваннях"</string> <string name="install_app" msgid="5066668100199613936">"Установити додаток"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублювати на зовнішньому екрані?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Дублювати екран"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Закрити"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрофон і камера"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нещодавнє використання додатками"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Переглянути нещодавній доступ"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 9d69a5e13319..2e2fc418116d 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ترتیبات میں ڈیفالٹ نوٹس ایپ سیٹ کریں"</string> <string name="install_app" msgid="5066668100199613936">"ایپ انسٹال کریں"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"بیرونی ڈسپلے پر مرر کریں؟"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"ڈسپلے کو دو طرفہ مطابقت پذیر بنائیں"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"برخاست کریں"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"مائیکروفون اور کیمرا"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"حالیہ ایپ کا استعمال"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"حالیہ رسائی دیکھیں"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 31b216b8977f..a8654d5604b1 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string> <string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tashqi displeyda aks ettirilsinmi?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Displeyni akslantirish"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Yopish"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon va kamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ilovadan oxirgi foydalanish"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Oxirgi ruxsatni koʻrish"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 814e0a6087eb..54437456188f 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Đặt ứng dụng ghi chú mặc định trong phần Cài đặt"</string> <string name="install_app" msgid="5066668100199613936">"Cài đặt ứng dụng"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Đồng bộ hoá hai chiều sang màn hình ngoài?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Phản chiếu màn hình"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Đóng"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrô và máy ảnh"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Hoạt động sử dụng gần đây của ứng dụng"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Xem hoạt động truy cập gần đây"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 8b2caf028b5e..44c163b03bcb 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在设置中设置默认记事应用"</string> <string name="install_app" msgid="5066668100199613936">"安装应用"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"镜像到外接显示屏?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"镜像显示"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"关闭"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"麦克风和摄像头"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期应用对手机传感器的使用情况"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期使用情况"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 8a160c366f74..f45b2fe1fc39 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設筆記應用程式"</string> <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要鏡像投射至外部顯示屏嗎?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期應用程式使用情況"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 38b81a0a4dee..5a5bb9d19c3f 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設記事應用程式"</string> <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要以鏡像方式投放至外部螢幕嗎?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近曾使用感應器的應用程式"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index fc704172a295..8b6bfae60199 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -1183,10 +1183,8 @@ <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string> <string name="install_app" msgid="5066668100199613936">"Faka i-app"</string> <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Fanisa nesibonisi sangaphandle?"</string> - <!-- no translation found for mirror_display (2515262008898122928) --> - <skip /> - <!-- no translation found for dismiss_dialog (2195508495854675882) --> - <skip /> + <string name="mirror_display" msgid="2515262008898122928">"Isibonisi sokufanisa"</string> + <string name="dismiss_dialog" msgid="2195508495854675882">"Chitha"</string> <string name="privacy_dialog_title" msgid="7839968133469098311">"Imakrofoni Nekhamera"</string> <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ukusetshenziswa kwakamuva kwe-app"</string> <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Bona ukufinyelela kwakamuva"</string> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 1add90ff4083..6856717653bd 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -947,6 +947,9 @@ <!-- Flag controlling whether visual query attention detection has been enabled. --> <bool name="config_enableVisualQueryAttentionDetection">false</bool> + <!-- Whether to show bottom sheets edge to edge --> + <bool name="config_edgeToEdgeBottomSheetDialog">true</bool> + <!-- Whether the scene container framework is enabled. diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0ee5da22a31b..6377df3410ab 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -846,12 +846,17 @@ <!-- Amount the button should shake when it's not long-pressed for long enough. --> <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen> - <dimen name="keyguard_affordance_horizontal_offset">32dp</dimen> + <dimen name="keyguard_affordance_horizontal_offset">16dp</dimen> <dimen name="keyguard_affordance_vertical_offset">32dp</dimen> <!-- Value should be at least sum of 'keyguard_affordance_width' + 'keyguard_affordance_horizontal_offset' --> <dimen name="keyguard_indication_area_padding">82dp</dimen> + <!-- The width/padding of the communal tutorial indicator on keyguard. --> + <dimen name="communal_tutorial_indicator_fixed_width">168dp</dimen> + <dimen name="communal_tutorial_indicator_padding">24dp</dimen> + <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen> + <!-- The width/height of the unlock icon view on keyguard. --> <dimen name="keyguard_lock_height">42dp</dimen> <dimen name="keyguard_lock_padding">20dp</dimen> @@ -1350,6 +1355,9 @@ <dimen name="screenrecord_options_padding_bottom">16dp</dimen> <dimen name="screenrecord_buttons_margin_top">20dp</dimen> + <!-- Connected display dialog --> + <dimen name="connected_display_dialog_logo_size">48dp</dimen> + <!-- Keyguard user switcher --> <dimen name="kg_user_switcher_text_size">16sp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 81101d828c86..05f4334bbe89 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -222,12 +222,14 @@ <item type="id" name="lock_icon" /> <item type="id" name="lock_icon_bg" /> <item type="id" name="burn_in_layer" /> + <item type="id" name="communal_tutorial_indicator" /> <!-- Privacy dialog --> <item type="id" name="privacy_dialog_close_app_button" /> <item type="id" name="privacy_dialog_manage_app_button" /> <!-- Communal mode --> + <item type="id" name="communal_hub" /> <item type="id" name="communal_widget_wrapper" /> <!-- Values assigned to the views in Biometrics Prompt --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a2637d5e55c3..321594f41479 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1043,6 +1043,9 @@ <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]--> <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string> + <!-- Indicator shown to start the communal tutorial. [CHAR LIMIT=100] --> + <string name="communal_tutorial_indicator_text">Click on the arrow button to start the communal tutorial</string> + <!-- Related to user switcher --><skip/> <!-- Accessibility label for the button that opens the user switcher. --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index 5d036fbe5e52..b44bf395930e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -71,7 +71,6 @@ public class FloatingRotationButton implements RotationButton { private AnimatedVectorDrawable mAnimatedDrawable; private boolean mIsShowing; - private boolean mCanShow = true; private int mDisplayRotation; private boolean mIsTaskbarVisible = false; @@ -150,7 +149,7 @@ public class FloatingRotationButton implements RotationButton { @Override public boolean show() { - if (!mCanShow || mIsShowing) { + if (mIsShowing) { return false; } @@ -222,14 +221,6 @@ public class FloatingRotationButton implements RotationButton { } @Override - public void setCanShowRotationButton(boolean canShow) { - mCanShow = canShow; - if (!mCanShow) { - hide(); - } - } - - @Override public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) { mIsTaskbarVisible = taskbarVisible; mIsTaskbarStashed = taskbarStashed; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java index 89f71ebf3dce..42dda0a4da4f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java @@ -36,7 +36,6 @@ public interface RotationButton { default boolean isVisible() { return false; } - default void setCanShowRotationButton(boolean canShow) {} default void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {} default void updateIcon(int lightIconColor, int darkIconColor) { } default void setOnClickListener(View.OnClickListener onClickListener) { } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 80040a384b9d..631423e4b7fc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -155,6 +155,26 @@ public class ActivityManagerWrapper { } } + + /** + * Requests for a new snapshot to be taken for the given task, stores it in the cache, and + * returns a {@link ThumbnailData} with the result. + */ + @NonNull + public ThumbnailData takeTaskThumbnail(int taskId) { + TaskSnapshot snapshot = null; + try { + snapshot = getService().takeTaskSnapshot(taskId, /* updateCache= */ true); + } catch (RemoteException e) { + Log.w(TAG, "Failed to take task snapshot", e); + } + if (snapshot != null) { + return new ThumbnailData(snapshot); + } else { + return new ThumbnailData(); + } + } + /** * Removes the outdated snapshot of home task. * diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 1e7222de91d9..88b9c020e9a5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -233,6 +233,11 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner runner.onAnimationCancelled(); finishRunnable.run(); } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean aborted) + throws RemoteException { + } }; } } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index eb7a7358cbf1..01a75d9e0f16 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -34,6 +34,7 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.customization.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.DOZING_MIGRATION_1 @@ -79,7 +80,7 @@ constructor( private val batteryController: BatteryController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val configurationController: ConfigurationController, - @Main private val resources: Resources, + @DisplaySpecific private val resources: Resources, private val context: Context, @Main private val mainExecutor: DelayableExecutor, @Background private val bgExecutor: Executor, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index f6a0563ebf94..9bddcd79ca49 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -22,10 +22,10 @@ import androidx.core.content.res.ResourcesCompat; import com.android.app.animation.Interpolators; import com.android.keyguard.dagger.KeyguardStatusViewScope; -import com.android.systemui.res.R; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.ClockController; +import com.android.systemui.res.R; import com.android.systemui.shared.clocks.DefaultClockController; import java.io.PrintWriter; @@ -452,6 +452,10 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); + // TODO: b/305022530 + if (mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) { + mClock.getEvents().onColorPaletteChanged(mContext.getResources()); + } if (changed) { post(() -> updateClockTargetRegions()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 50be97ec1af9..3585feb3442d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -44,8 +44,6 @@ import android.util.AttributeSet; import android.view.WindowInsets; import android.view.WindowInsetsAnimationControlListener; import android.view.WindowInsetsAnimationController; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.TextView; import androidx.annotation.NonNull; @@ -66,18 +64,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController; */ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { - private final int mDisappearYTranslation; - - private static final long IME_DISAPPEAR_DURATION_MS = 125; - - // A delay constant to be used in a workaround for the situation where InputMethodManagerService - // is not switched to the new user yet. - // TODO: Remove this by ensuring such a race condition never happens. - private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; - private Interpolator mLinearOutSlowInInterpolator; - private Interpolator mFastOutLinearInInterpolator; private DisappearAnimationListener mDisappearAnimationListener; @Nullable private MotionLayout mContainerMotionLayout; private boolean mAlreadyUsingSplitBouncer = false; @@ -93,12 +81,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { public KeyguardPasswordView(Context context, AttributeSet attrs) { super(context, attrs); - mDisappearYTranslation = getResources().getDimensionPixelSize( - R.dimen.disappear_y_translation); - mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.linear_out_slow_in); - mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.fast_out_linear_in); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 8717a532b43d..d2d051735643 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -31,6 +31,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -39,9 +40,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPinViewController extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { @@ -324,7 +325,11 @@ public class KeyguardSimPinViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash - msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + } else { + msg = rez.getString(R.string.kg_sim_pin_instructions); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 248b7afd7535..b52a36b8199e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -29,6 +29,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.WindowManager; import android.widget.ImageView; @@ -36,9 +37,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPukViewController extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { @@ -206,7 +207,11 @@ public class KeyguardSimPukViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; - msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + } else { + msg = rez.getString(R.string.kg_puk_enter_puk_hint); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index f9cc03eea288..d8486029a903 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -18,7 +18,6 @@ package com.android.keyguard; import static java.util.Collections.emptySet; -import android.animation.LayoutTransition; import android.content.Context; import android.graphics.Canvas; import android.os.Build; @@ -79,14 +78,6 @@ public class KeyguardStatusView extends GridLayout { mKeyguardSlice = findViewById(R.id.keyguard_slice_view); mMediaHostContainer = findViewById(R.id.status_view_media_container); - if (mMediaHostContainer != null) { - LayoutTransition mediaLayoutTransition = new LayoutTransition(); - ((ViewGroup) mMediaHostContainer).setLayoutTransition(mediaLayoutTransition); - mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); - mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); - mediaLayoutTransition.disableTransitionType(LayoutTransition.APPEARING); - mediaLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING); - } updateDark(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 67b705222977..79642bdae1ce 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -23,7 +23,6 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CL import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.Animator; -import android.animation.LayoutTransition; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.content.res.Configuration; @@ -50,14 +49,15 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; +import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.power.shared.model.ScreenPowerState; import com.android.systemui.plugins.ClockController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.power.shared.model.ScreenPowerState; +import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -175,27 +175,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV return; } - final LayoutTransition mediaLayoutTransition = - ((ViewGroup) mediaHostContainer).getLayoutTransition(); - if (mediaLayoutTransition == null) return; - - mediaLayoutTransition.enableTransitionType(LayoutTransition.CHANGING); + ViewHierarchyAnimator.Companion.animateNextUpdate(mediaHostContainer, + Interpolators.STANDARD, /* duration= */ 500L, + /* animateChildren= */ false); }); - - mediaHostContainer.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - final LayoutTransition mediaLayoutTransition = - ((ViewGroup) mediaHostContainer).getLayoutTransition(); - if (mediaLayoutTransition == null) return; - if (!mediaLayoutTransition.isTransitionTypeEnabled( - LayoutTransition.CHANGING)) { - return; - } - // Note: when this is called, the LayoutTransition is already been set up. - // Disables the LayoutTransition until it's explicitly enabled again. - mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGING); - } - ); } mDumpManager.registerDumpable(getInstanceName(), this); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3f5ec7d020da..b7bb35eb6783 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -189,6 +189,8 @@ import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; import com.android.systemui.util.settings.SecureSettings; +import dalvik.annotation.optimization.NeverCompile; + import com.google.android.collect.Lists; import java.io.PrintWriter; @@ -1248,6 +1250,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthFailed() { Assert.isMainThread(); String reason = @@ -1276,6 +1279,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAcquired(int acquireInfo) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1297,6 +1301,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated"); try { @@ -1325,6 +1330,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceHelp(int msgId, String helpString) { if (mFaceAcquiredInfoIgnoreList.contains(msgId)) { return; @@ -1342,6 +1348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceError(int msgId, final String originalErrMsg) { Assert.isMainThread(); String errString = originalErrMsg; @@ -4430,6 +4437,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } @SuppressLint("MissingPermission") + @NeverCompile @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("KeyguardUpdateMonitor state:"); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 7b596328ca13..247606771155 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -25,12 +25,14 @@ import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.plugins.WeatherData; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.util.TimeZone; /** * Callback for general information relevant to lock screen. */ +@WeaklyReferencedCallback public class KeyguardUpdateMonitorCallback { /** diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 0d3f726b011b..83da80f4123a 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -692,10 +692,10 @@ public class LockIconViewController implements Dumpable { mVelocityTracker.addMovement(event); // Compute pointer velocity in pixels per second. mVelocityTracker.computeCurrentVelocity(1000); - float velocity = UdfpsController.computePointerSpeed(mVelocityTracker, + float velocity = computePointerSpeed(mVelocityTracker, mActivePointerId); if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS - && UdfpsController.exceedsVelocityThreshold(velocity)) { + && exceedsVelocityThreshold(velocity)) { Log.v(TAG, "lock icon long-press rescheduled due to " + "high pointer velocity=" + velocity); mLongPressCancelRunnable.run(); @@ -713,6 +713,23 @@ public class LockIconViewController implements Dumpable { return true; } + /** + * Calculate the pointer speed given a velocity tracker and the pointer id. + * This assumes that the velocity tracker has already been passed all relevant motion events. + */ + private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { + final float vx = tracker.getXVelocity(pointerId); + final float vy = tracker.getYVelocity(pointerId); + return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); + } + + /** + * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. + */ + private static boolean exceedsVelocityThreshold(float velocity) { + return velocity > 750f; + } + private boolean actionableDownEventStartedOnView(MotionEvent event) { if (!isActionable()) { return false; diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt index c5817881718b..1f145d887381 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.res.Resources import android.view.Display import com.android.systemui.dagger.qualifiers.DisplaySpecific +import com.android.systemui.util.kotlin.getOrNull import dagger.BindsOptionalOf import dagger.Module import dagger.Provides @@ -23,11 +24,12 @@ abstract class KeyguardDisplayModule { companion object { @Provides @DisplaySpecific - fun getDisplayContext(context: Context, display: Optional<Display>): Context { - return if (display.isPresent) { - context.createDisplayContext(display.get()) - } else { + fun getDisplayContext(context: Context, optionalDisplay: Optional<Display>): Context { + val display = optionalDisplay.getOrNull() ?: return context + return if (context.displayId == display.displayId) { context + } else { + context.createDisplayContext(display) } } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 2dfb370bb382..fe19616cef1d 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -451,7 +451,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { } fun logSubInfo(subInfo: SubscriptionInfo?) { - logBuffer.log(TAG, VERBOSE, { str1 = "$subInfo" }, { "SubInfo:$str1" }) + logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" }) } fun logTimeFormatChanged(newTimeFormat: String?) { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 7739021bad33..1a34cc4fc3a9 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -15,123 +15,55 @@ package com.android.systemui; import android.annotation.Nullable; -import android.app.AlarmManager; -import android.app.INotificationManager; -import android.app.IWallpaperManager; -import android.hardware.SensorPrivacyManager; -import android.hardware.display.NightDisplayListener; import android.os.Handler; import android.os.Looper; import android.util.ArrayMap; -import android.util.DisplayMetrics; -import android.view.IWindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; -import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; -import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.DialogLaunchAnimator; -import com.android.systemui.appops.AppOpsController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.power.EnhancedEstimates; -import com.android.systemui.power.PowerUI; -import com.android.systemui.privacy.PrivacyItemController; -import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.settings.UserTracker; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.DevicePolicyManagerWrapper; -import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationListener; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.SmartReplyController; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.events.PrivacyDotViewController; -import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; -import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; -import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.SystemUIDialogManager; -import com.android.systemui.statusbar.policy.AccessibilityController; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.FlashlightController; -import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.LocationController; -import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RotationLockController; -import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.statusbar.policy.SensorPrivacyController; -import com.android.systemui.statusbar.policy.SmartReplyConstants; -import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.util.leak.LeakDetector; -import com.android.systemui.util.leak.LeakReporter; -import com.android.systemui.util.sensors.AsyncSensorManager; import dagger.Lazy; -import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -154,10 +86,6 @@ import javax.inject.Named; */ @SysUISingleton public class Dependency { - /** - * Key for getting a the main looper. - */ - private static final String MAIN_LOOPER_NAME = "main_looper"; /** * Key for getting a background Looper for background work. @@ -171,15 +99,6 @@ public class Dependency { * Generic handler on the main thread. */ private static final String MAIN_HANDLER_NAME = "main_handler"; - /** - * Generic executor on the main thread. - */ - private static final String MAIN_EXECUTOR_NAME = "main_executor"; - - /** - * Generic executor on a background thread. - */ - private static final String BACKGROUND_EXECUTOR_NAME = "background_executor"; /** * An email address to send memory leak reports to by default. @@ -197,10 +116,6 @@ public class Dependency { */ public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME); /** - * Key for getting a mainer Looper. - */ - public static final DependencyKey<Looper> MAIN_LOOPER = new DependencyKey<>(MAIN_LOOPER_NAME); - /** * Key for getting a Handler for receiving time tick broadcasts on. */ public static final DependencyKey<Handler> TIME_TICK_HANDLER = @@ -211,133 +126,43 @@ public class Dependency { public static final DependencyKey<Handler> MAIN_HANDLER = new DependencyKey<>(MAIN_HANDLER_NAME); - /** - * Generic executor on the main thread. - */ - public static final DependencyKey<Executor> MAIN_EXECUTOR = - new DependencyKey<>(MAIN_EXECUTOR_NAME); - /** - * Generic executor on a background thread. - */ - public static final DependencyKey<Executor> BACKGROUND_EXECUTOR = - new DependencyKey<>(BACKGROUND_EXECUTOR_NAME); - - /** - * An email address to send memory leak reports to by default. - */ - public static final DependencyKey<String> LEAK_REPORT_EMAIL = - new DependencyKey<>(LEAK_REPORT_EMAIL_NAME); - private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>(); private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>(); @Inject DumpManager mDumpManager; - @Inject Lazy<ActivityStarter> mActivityStarter; @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher; - @Inject Lazy<AsyncSensorManager> mAsyncSensorManager; @Inject Lazy<BluetoothController> mBluetoothController; - @Inject Lazy<LocationController> mLocationController; - @Inject Lazy<RotationLockController> mRotationLockController; - @Inject Lazy<ZenModeController> mZenModeController; - @Inject Lazy<HotspotController> mHotspotController; - @Inject Lazy<CastController> mCastController; @Inject Lazy<FlashlightController> mFlashlightController; - @Inject Lazy<UserSwitcherController> mUserSwitcherController; - @Inject Lazy<UserInfoController> mUserInfoController; - @Inject Lazy<KeyguardStateController> mKeyguardMonitor; @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor; - @Inject Lazy<NightDisplayListener> mNightDisplayListener; - @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController; - @Inject Lazy<ManagedProfileController> mManagedProfileController; - @Inject Lazy<NextAlarmController> mNextAlarmController; - @Inject Lazy<DataSaverController> mDataSaverController; - @Inject Lazy<AccessibilityController> mAccessibilityController; @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController; @Inject Lazy<PluginManager> mPluginManager; @Inject Lazy<AssistManager> mAssistManager; - @Inject Lazy<SecurityController> mSecurityController; - @Inject Lazy<LeakDetector> mLeakDetector; - @Inject Lazy<LeakReporter> mLeakReporter; - @Inject Lazy<GarbageMonitor> mGarbageMonitor; @Inject Lazy<TunerService> mTunerService; - @Inject Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; - @Inject Lazy<StatusBarWindowController> mTempStatusBarWindowController; @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher; - @Inject Lazy<StatusBarIconController> mStatusBarIconController; - @Inject Lazy<ScreenLifecycle> mScreenLifecycle; - @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle; @Inject Lazy<FragmentService> mFragmentService; - @Inject Lazy<ExtensionController> mExtensionController; - @Inject Lazy<PluginDependencyProvider> mPluginDependencyProvider; @Nullable - @Inject Lazy<LocalBluetoothManager> mLocalBluetoothManager; @Inject Lazy<VolumeDialogController> mVolumeDialogController; @Inject Lazy<MetricsLogger> mMetricsLogger; - @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper; - @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor; @Inject Lazy<TunablePaddingService> mTunablePaddingService; @Inject Lazy<UiOffloadThread> mUiOffloadThread; - @Inject Lazy<PowerUI.WarningsUI> mWarningsUI; @Inject Lazy<LightBarController> mLightBarController; - @Inject Lazy<IWindowManager> mIWindowManager; @Inject Lazy<OverviewProxyService> mOverviewProxyService; @Inject Lazy<NavigationModeController> mNavBarModeController; @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver; @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController; - @Inject Lazy<EnhancedEstimates> mEnhancedEstimates; - @Inject Lazy<VibratorHelper> mVibratorHelper; @Inject Lazy<IStatusBarService> mIStatusBarService; - @Inject Lazy<DisplayMetrics> mDisplayMetrics; - @Inject Lazy<LockscreenGestureLogger> mLockscreenGestureLogger; - @Inject Lazy<ShadeController> mShadeController; @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback; - @Inject Lazy<AppOpsController> mAppOpsController; @Inject Lazy<NavigationBarController> mNavigationBarController; - @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController; @Inject Lazy<StatusBarStateController> mStatusBarStateController; - @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager; @Inject Lazy<NotificationMediaManager> mNotificationMediaManager; - @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager; - @Inject Lazy<SmartReplyConstants> mSmartReplyConstants; - @Inject Lazy<NotificationListener> mNotificationListener; - @Inject Lazy<NotificationLogger> mNotificationLogger; - @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil; - @Inject Lazy<SmartReplyController> mSmartReplyController; - @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler; - @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; - @Inject Lazy<AutoHideController> mAutoHideController; - @Inject Lazy<PrivacyItemController> mPrivacyItemController; @Inject @Background Lazy<Looper> mBgLooper; - @Inject @Background Lazy<Handler> mBgHandler; - @Inject @Main Lazy<Looper> mMainLooper; @Inject @Main Lazy<Handler> mMainHandler; @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; - @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; - @Inject @Main Lazy<Executor> mMainExecutor; - @Inject @Background Lazy<Executor> mBackgroundExecutor; - @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper; - @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper; - @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper; - @Inject Lazy<SensorPrivacyController> mSensorPrivacyController; - @Inject Lazy<DockManager> mDockManager; - @Inject Lazy<INotificationManager> mINotificationManager; @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; - @Inject Lazy<AlarmManager> mAlarmManager; - @Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel; - @Inject Lazy<DozeParameters> mDozeParameters; - @Inject Lazy<IWallpaperManager> mWallpaperManager; @Inject Lazy<CommandQueue> mCommandQueue; - @Inject Lazy<RecordingController> mRecordingController; - @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory; - @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy; - @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager; - @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy; - @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy; - @Inject Lazy<EdgeBackGestureHandler.Factory> mEdgeBackGestureHandlerFactoryLazy; @Inject Lazy<UiEventLogger> mUiEventLogger; @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; - @Inject Lazy<InternetDialogFactory> mInternetDialogFactory; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy; @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController; @@ -360,183 +185,36 @@ public class Dependency { // on imports. mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get); mProviders.put(BG_LOOPER, mBgLooper::get); - mProviders.put(MAIN_LOOPER, mMainLooper::get); mProviders.put(MAIN_HANDLER, mMainHandler::get); - mProviders.put(MAIN_EXECUTOR, mMainExecutor::get); - mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get); - mProviders.put(ActivityStarter.class, mActivityStarter::get); mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get); - - mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get); - mProviders.put(BluetoothController.class, mBluetoothController::get); - mProviders.put(SensorPrivacyManager.class, mSensorPrivacyManager::get); - - mProviders.put(LocationController.class, mLocationController::get); - - mProviders.put(RotationLockController.class, mRotationLockController::get); - - mProviders.put(ZenModeController.class, mZenModeController::get); - - mProviders.put(HotspotController.class, mHotspotController::get); - - mProviders.put(CastController.class, mCastController::get); - mProviders.put(FlashlightController.class, mFlashlightController::get); - - mProviders.put(KeyguardStateController.class, mKeyguardMonitor::get); - mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get); - - mProviders.put(UserSwitcherController.class, mUserSwitcherController::get); - - mProviders.put(UserInfoController.class, mUserInfoController::get); - - mProviders.put(NightDisplayListener.class, mNightDisplayListener::get); - - mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get); - - mProviders.put(ManagedProfileController.class, mManagedProfileController::get); - - mProviders.put(NextAlarmController.class, mNextAlarmController::get); - - mProviders.put(DataSaverController.class, mDataSaverController::get); - - mProviders.put(AccessibilityController.class, mAccessibilityController::get); - mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get); - mProviders.put(PluginManager.class, mPluginManager::get); - mProviders.put(AssistManager.class, mAssistManager::get); - - mProviders.put(SecurityController.class, mSecurityController::get); - - mProviders.put(LeakDetector.class, mLeakDetector::get); - - mProviders.put(LEAK_REPORT_EMAIL, mLeakReportEmail::get); - - mProviders.put(LeakReporter.class, mLeakReporter::get); - - mProviders.put(GarbageMonitor.class, mGarbageMonitor::get); - mProviders.put(TunerService.class, mTunerService::get); - - mProviders.put(NotificationShadeWindowController.class, - mNotificationShadeWindowController::get); - - mProviders.put(StatusBarWindowController.class, mTempStatusBarWindowController::get); - mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get); - - mProviders.put(StatusBarIconController.class, mStatusBarIconController::get); - - mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get); - - mProviders.put(WakefulnessLifecycle.class, mWakefulnessLifecycle::get); - mProviders.put(FragmentService.class, mFragmentService::get); - - mProviders.put(ExtensionController.class, mExtensionController::get); - - mProviders.put(PluginDependencyProvider.class, mPluginDependencyProvider::get); - - mProviders.put(LocalBluetoothManager.class, mLocalBluetoothManager::get); - mProviders.put(VolumeDialogController.class, mVolumeDialogController::get); - mProviders.put(MetricsLogger.class, mMetricsLogger::get); - - mProviders.put(AccessibilityManagerWrapper.class, mAccessibilityManagerWrapper::get); - - mProviders.put(SysuiColorExtractor.class, mSysuiColorExtractor::get); - mProviders.put(TunablePaddingService.class, mTunablePaddingService::get); - mProviders.put(UiOffloadThread.class, mUiOffloadThread::get); - - mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get); - mProviders.put(LightBarController.class, mLightBarController::get); - - mProviders.put(IWindowManager.class, mIWindowManager::get); - mProviders.put(OverviewProxyService.class, mOverviewProxyService::get); - mProviders.put(NavigationModeController.class, mNavBarModeController::get); - mProviders.put(AccessibilityButtonModeObserver.class, mAccessibilityButtonModeObserver::get); mProviders.put(AccessibilityButtonTargetsObserver.class, mAccessibilityButtonListController::get); - - mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get); - - mProviders.put(VibratorHelper.class, mVibratorHelper::get); - mProviders.put(IStatusBarService.class, mIStatusBarService::get); - - mProviders.put(DisplayMetrics.class, mDisplayMetrics::get); - - mProviders.put(LockscreenGestureLogger.class, mLockscreenGestureLogger::get); - - mProviders.put(ShadeController.class, mShadeController::get); - mProviders.put(NotificationRemoteInputManager.Callback.class, mNotificationRemoteInputManagerCallback::get); - - mProviders.put(AppOpsController.class, mAppOpsController::get); - mProviders.put(NavigationBarController.class, mNavigationBarController::get); - - mProviders.put(AccessibilityFloatingMenuController.class, - mAccessibilityFloatingMenuController::get); - mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); - mProviders.put(NotificationLockscreenUserManager.class, - mNotificationLockscreenUserManager::get); mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get); - mProviders.put(NotificationRemoteInputManager.class, - mNotificationRemoteInputManager::get); - mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get); - mProviders.put(NotificationListener.class, mNotificationListener::get); - mProviders.put(NotificationLogger.class, mNotificationLogger::get); - mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get); - mProviders.put(SmartReplyController.class, mSmartReplyController::get); - mProviders.put(RemoteInputQuickSettingsDisabler.class, - mRemoteInputQuickSettingsDisabler::get); - mProviders.put(PrivacyItemController.class, mPrivacyItemController::get); - mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get); - mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get); - mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get); - mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get); - mProviders.put(DockManager.class, mDockManager::get); - mProviders.put(INotificationManager.class, mINotificationManager::get); mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get); - mProviders.put(AlarmManager.class, mAlarmManager::get); - mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get); - mProviders.put(DozeParameters.class, mDozeParameters::get); - mProviders.put(IWallpaperManager.class, mWallpaperManager::get); mProviders.put(CommandQueue.class, mCommandQueue::get); - mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get); - mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get); - - // TODO(b/118592525): to support multi-display , we start to add something which is - // per-display, while others may be global. I think it's time to add - // a new class maybe named DisplayDependency to solve per-display - // Dependency problem. - mProviders.put(AutoHideController.class, mAutoHideController::get); - - mProviders.put(RecordingController.class, mRecordingController::get); - - mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get); - - mProviders.put(SystemStatusAnimationScheduler.class, - mSystemStatusAnimationSchedulerLazy::get); - mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); - mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get); - mProviders.put(EdgeBackGestureHandler.Factory.class, - mEdgeBackGestureHandlerFactoryLazy::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java index 4541384aaa37..b573fadea15e 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java @@ -28,6 +28,7 @@ import com.android.systemui.res.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.settings.UserTracker; @@ -46,6 +47,7 @@ import dagger.assisted.AssistedInject; /** * Manages notification when a guest session is resumed. */ +@SysUISingleton public class GuestResumeSessionReceiver { @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 4af2c740ddc9..6faee8cd1c4f 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -83,7 +83,7 @@ import com.android.systemui.decor.RoundedCornerDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegateImpl; import com.android.systemui.decor.ScreenDecorCommand; import com.android.systemui.log.ScreenDecorationsLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; @@ -93,6 +93,8 @@ import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.settings.SecureSettings; +import dalvik.annotation.optimization.NeverCompile; + import kotlin.Pair; import java.io.PrintWriter; @@ -163,7 +165,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { ScreenDecorHwcLayer mScreenDecorHwcLayer; private WindowManager mWindowManager; private int mRotation; - private SettingObserver mColorInversionSetting; + private UserSettingObserver mColorInversionSetting; @Nullable private DelayableExecutor mExecutor; private Handler mHandler; @@ -684,7 +686,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { // Watch color inversion and invert the overlay as needed. if (mColorInversionSetting == null) { - mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler, + mColorInversionSetting = new UserSettingObserver(mSecureSettings, mHandler, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, mUserTracker.getUserId()) { @Override @@ -1088,6 +1090,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } } + @NeverCompile @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("ScreenDecorations state:"); diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java index 1ee06cc8d757..56273eb9a2cf 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -35,18 +35,19 @@ import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.assist.AssistLogger; import com.android.systemui.assist.AssistManager; import com.android.systemui.assist.AssistantSessionEvent; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.res.R; + +import dagger.Lazy; import java.util.Locale; import javax.inject.Inject; -import dagger.Lazy; - /** * Default UiController implementation. Shows white edge lights along the bottom of the phone, * expanding from the corners to meet in the center. @@ -80,7 +81,8 @@ public class DefaultUiController implements AssistManager.UiController { @Inject public DefaultUiController(Context context, AssistLogger assistLogger, WindowManager windowManager, MetricsLogger metricsLogger, - Lazy<AssistManager> assistManagerLazy) { + Lazy<AssistManager> assistManagerLazy, + NavigationBarController navigationBarController) { mAssistLogger = assistLogger; mRoot = new FrameLayout(context); mWindowManager = windowManager; @@ -103,6 +105,7 @@ public class DefaultUiController implements AssistManager.UiController { mInvocationLightsView = (InvocationLightsView) LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false); + mInvocationLightsView.setNavigationBarController(navigationBarController); mRoot.addView(mInvocationLightsView); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java index 4d89231f0b15..0cdb376e7a27 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java @@ -31,11 +31,10 @@ import android.view.ContextThemeWrapper; import android.view.View; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; -import com.android.systemui.res.R; -import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBar; +import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarTransitions; +import com.android.systemui.res.R; import java.util.ArrayList; @@ -64,6 +63,8 @@ public class InvocationLightsView extends View private final int mLightColor; @ColorInt private final int mDarkColor; + @Nullable + private NavigationBarController mNavigationBarController; // Allocate variable for screen location lookup to avoid memory alloc onDraw() private int[] mScreenLocation = new int[2]; @@ -279,12 +280,11 @@ public class InvocationLightsView extends View private void attemptRegisterNavBarListener() { if (!mRegistered) { - NavigationBarController controller = Dependency.get(NavigationBarController.class); - if (controller == null) { + if (mNavigationBarController == null) { return; } - NavigationBar navBar = controller.getDefaultNavigationBar(); + NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar(); if (navBar == null) { return; } @@ -296,12 +296,11 @@ public class InvocationLightsView extends View private void attemptUnregisterNavBarListener() { if (mRegistered) { - NavigationBarController controller = Dependency.get(NavigationBarController.class); - if (controller == null) { + if (mNavigationBarController == null) { return; } - NavigationBar navBar = controller.getDefaultNavigationBar(); + NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar(); if (navBar == null) { return; } @@ -310,4 +309,8 @@ public class InvocationLightsView extends View mRegistered = false; } } + + public void setNavigationBarController(NavigationBarController navigationBarController) { + mNavigationBarController = navigationBarController; + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt deleted file mode 100644 index ca4b8efa98a5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -/** - * Interface for controlling the on finger down & on finger up events. - */ -interface AlternateUdfpsTouchProvider { - - /** - * onPointerDown: - * - * This operation is used to notify the Fingerprint HAL that - * a fingerprint has been detected on the device's screen. - * - * See fingerprint/ISession#onPointerDown for more details. - */ - fun onPointerDown(pointerId: Long, x: Int, y: Int, minor: Float, major: Float) - - /** - * onPointerUp: - * - * This operation can be invoked when the HAL is performing any one of: ISession#authenticate, - * ISession#enroll, ISession#detectInteraction. This operation is used to indicate - * that a fingerprint that was previously down, is now up. - * - * See fingerprint/ISession#onPointerUp for more details. - */ - fun onPointerUp(pointerId: Long) - - /** - * onUiReady: - * - * This operation is used by the callee to notify the Fingerprint HAL that SystemUI is - * correctly configured for the fingerprint capture. - * - * See fingerprint/ISession#onUiReady for more details. - */ - fun onUiReady() -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index bdad41348c95..395f68c7c0e6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -37,9 +37,6 @@ public abstract class UdfpsAnimationView extends FrameLayout { private float mDialogSuggestedAlpha = 1f; private float mNotificationShadeExpansion = 0f; - // Used for Udfps ellipse detection when flag is true, set by AnimationViewController - boolean mUseExpandedOverlay = false; - // mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha private int mAlpha; boolean mPauseAuth; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 79d9c1ba70bc..c9e4cbe30a2c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -32,7 +32,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.SensorProperties; @@ -54,7 +53,6 @@ import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -84,7 +82,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter; @@ -102,7 +99,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; -import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.SystemClock; import kotlin.Unit; @@ -110,7 +106,6 @@ import kotlin.Unit; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; @@ -136,13 +131,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { private static final String TAG = "UdfpsController"; private static final long AOD_SEND_FINGER_UP_DELAY_MILLIS = 1000; - // Minimum required delay between consecutive touch logs in milliseconds. - private static final long MIN_TOUCH_LOG_INTERVAL = 50; private static final long MIN_UNCHANGED_INTERACTION_LOG_INTERVAL = 50; - // This algorithm checks whether the touch is within the sensor's bounding box. - private static final int BOUNDING_BOX_TOUCH_CONFIG_ID = 0; - private final Context mContext; private final Execution mExecution; private final FingerprintManager mFingerprintManager; @@ -175,8 +165,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Nullable private final TouchProcessor mTouchProcessor; @NonNull private final SessionTracker mSessionTracker; @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor; - @NonNull private final SecureSettings mSecureSettings; - @NonNull private final UdfpsUtils mUdfpsUtils; @NonNull private final InputManager mInputManager; @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; private final boolean mIgnoreRefreshRate; @@ -187,11 +175,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams(); // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this. @Nullable private Runnable mAuthControllerUpdateUdfpsLocation; - @Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider; @Nullable private UdfpsDisplayModeProvider mUdfpsDisplayMode; - // Tracks the velocity of a touch to help filter out the touches that move too fast. - @Nullable private VelocityTracker mVelocityTracker; // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. private int mActivePointerId = -1; // Whether a pointer has been pilfered for current gesture @@ -259,8 +244,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { final int touchConfigId = mContext.getResources().getInteger( com.android.internal.R.integer.config_selected_udfps_touch_detection); pw.println("mSensorProps=(" + mSensorProps + ")"); - pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled( - Flags.UDFPS_NEW_TOUCH_DETECTION)); pw.println("touchConfigId: " + touchConfigId); } @@ -272,7 +255,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay( new UdfpsControllerOverlay( mContext, - mFingerprintManager, mInflater, mWindowManager, mAccessibilityManager, @@ -286,7 +268,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardStateController, mUnlockedScreenOffAnimationController, mUdfpsDisplayMode, - mSecureSettings, requestId, reason, callback, @@ -299,7 +280,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, - mUdfpsUtils, mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels ))); @@ -376,13 +356,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { * Debug to run onUiReady */ public void debugOnUiReady(int sensorId) { - if (UdfpsController.this.mAlternateTouchProvider != null) { - UdfpsController.this.mAlternateTouchProvider.onUiReady(); - } else { - final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; - UdfpsController.this.mFingerprintManager.onUdfpsUiEvent( - FingerprintManager.UDFPS_UI_READY, requestId, sensorId); - } + final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; + UdfpsController.this.mFingerprintManager.onUdfpsUiEvent( + FingerprintManager.UDFPS_UI_READY, requestId, sensorId); } } @@ -423,23 +399,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mUdfpsDisplayMode = udfpsDisplayMode; } - /** - * Calculate the pointer speed given a velocity tracker and the pointer id. - * This assumes that the velocity tracker has already been passed all relevant motion events. - */ - public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { - final float vx = tracker.getXVelocity(pointerId); - final float vy = tracker.getYVelocity(pointerId); - return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); - } - - /** - * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. - */ - public static boolean exceedsVelocityThreshold(float velocity) { - return velocity > 750f; - } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -457,38 +416,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { } }; - /** - * Forwards touches to the udfps controller / view - */ - public boolean onTouch(MotionEvent event) { - if (mOverlay == null || mOverlay.isHiding()) { - return false; - } - // TODO(b/225068271): may not be correct but no way to get the id yet - return onTouch(mOverlay.getRequestId(), event, false); - } - - /** - * @param x coordinate - * @param y coordinate - * @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else, - * calculate from the display dimensions in portrait orientation - */ - private boolean isWithinSensorArea(UdfpsView udfpsView, float x, float y, - boolean relativeToUdfpsView) { - if (relativeToUdfpsView) { - // TODO: move isWithinSensorArea to UdfpsController. - return udfpsView.isWithinSensorArea(x, y); - } - - if (mOverlay == null || mOverlay.getAnimationViewController() == null) { - return false; - } - - return !mOverlay.getAnimationViewController().shouldPauseAuth() - && mOverlayParams.getSensorBounds().contains((int) x, (int) y); - } - private void tryDismissingKeyguard() { if (!mOnFingerDown) { playStartHaptic(); @@ -497,15 +424,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mAttemptedToDismissKeyguard = true; } - @VisibleForTesting - boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - return newOnTouch(requestId, event, fromUdfpsView); - } else { - return oldOnTouch(requestId, event, fromUdfpsView); - } - } - private int getBiometricSessionType() { if (mOverlay == null) { return -1; @@ -566,7 +484,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } - private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { + private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { if (!fromUdfpsView) { Log.e(TAG, "ignoring the touch injected from outside of UdfpsView"); return false; @@ -580,7 +498,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { + mOverlay.getRequestId()); return false; } - if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f && !mAlternateBouncerInteractor.isVisibleState()) || mPrimaryBouncerInteractor.isInTransit()) { @@ -654,8 +571,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { break; case UNCHANGED: - if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), - true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID + if (mActivePointerId == MotionEvent.INVALID_POINTER_ID && mAlternateBouncerInteractor.isVisibleState()) { // No pointer on sensor, forward to keyguard if alternateBouncer is visible mKeyguardViewManager.onTouch(event); @@ -667,7 +583,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { logBiometricTouch(processedTouch.getEvent(), data); // Always pilfer pointers that are within sensor area or when alternate bouncer is showing - if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true) + if (mActivePointerId != MotionEvent.INVALID_POINTER_ID || mAlternateBouncerInteractor.isVisibleState()) { shouldPilfer = true; } @@ -680,146 +596,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mPointerPilfered = true; } - return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds()); - } - - private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { - if (mOverlay == null) { - Log.w(TAG, "ignoring onTouch with null overlay"); - return false; - } - if (!mOverlay.matchesRequestId(requestId)) { - Log.w(TAG, "ignoring stale touch event: " + requestId + " current: " - + mOverlay.getRequestId()); - return false; - } - - final UdfpsView udfpsView = mOverlay.getOverlayView(); - boolean handled = false; - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_HOVER_ENTER: - Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN"); - // To simplify the lifecycle of the velocity tracker, make sure it's never null - // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } else { - // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new - // ACTION_DOWN, in that case we should just reuse the old instance. - mVelocityTracker.clear(); - } - - final boolean withinSensorArea = - isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView); - if (withinSensorArea) { - Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0); - Log.v(TAG, "onTouch | action down"); - // The pointer that causes ACTION_DOWN is always at index 0. - // We need to persist its ID to track it during ACTION_MOVE that could include - // data for many other pointers because of multi-touch support. - mActivePointerId = event.getPointerId(0); - mVelocityTracker.addMovement(event); - handled = true; - mAcquiredReceived = false; - } - if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) { - Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN"); - tryDismissingKeyguard(); - } - - Trace.endSection(); - break; - - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_HOVER_MOVE: - Trace.beginSection("UdfpsController.onTouch.ACTION_MOVE"); - final int idx = mActivePointerId == -1 - ? event.getPointerId(0) - : event.findPointerIndex(mActivePointerId); - if (idx == event.getActionIndex()) { - final boolean actionMoveWithinSensorArea = - isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx), - fromUdfpsView); - if ((fromUdfpsView || actionMoveWithinSensorArea) - && shouldTryToDismissKeyguard()) { - Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE"); - tryDismissingKeyguard(); - break; - } - // Map the touch to portrait mode if the device is in landscape mode. - final Point scaledTouch = mUdfpsUtils.getTouchInNativeCoordinates( - idx, event, mOverlayParams); - if (actionMoveWithinSensorArea) { - if (mVelocityTracker == null) { - // touches could be injected, so the velocity tracker may not have - // been initialized (via ACTION_DOWN). - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - // Compute pointer velocity in pixels per second. - mVelocityTracker.computeCurrentVelocity(1000); - // Compute pointer speed from X and Y velocities. - final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); - final float minor = event.getTouchMinor(idx); - final float major = event.getTouchMajor(idx); - final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v); - final String touchInfo = String.format( - "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", - minor, major, v, exceedsVelocityThreshold); - final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; - - if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) { - final float scale = mOverlayParams.getScaleFactor(); - float scaledMinor = minor / scale; - float scaledMajor = major / scale; - onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor, - scaledMajor); - - Log.v(TAG, "onTouch | finger down: " + touchInfo); - mTouchLogTime = mSystemClock.elapsedRealtime(); - handled = true; - } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { - Log.v(TAG, "onTouch | finger move: " + touchInfo); - mTouchLogTime = mSystemClock.elapsedRealtime(); - } - } else { - Log.v(TAG, "onTouch | finger outside"); - onFingerUp(requestId, udfpsView); - // Maybe announce for accessibility. - mFgExecutor.execute(() -> { - if (mOverlay == null) { - Log.e(TAG, "touch outside sensor area received" - + "but serverRequest is null"); - return; - } - mOverlay.onTouchOutsideOfSensorArea(scaledTouch); - }); - } - } - Trace.endSection(); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_HOVER_EXIT: - Trace.beginSection("UdfpsController.onTouch.ACTION_UP"); - mActivePointerId = -1; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - Log.v(TAG, "onTouch | finger up"); - mAttemptedToDismissKeyguard = false; - onFingerUp(requestId, udfpsView); - mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); - Trace.endSection(); - break; - - default: - // Do nothing. - } - return handled; + return mActivePointerId != MotionEvent.INVALID_POINTER_ID; } private boolean shouldTryToDismissKeyguard() { @@ -859,15 +636,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull SystemUIDialogManager dialogManager, @NonNull LatencyTracker latencyTracker, @NonNull ActivityLaunchAnimator activityLaunchAnimator, - @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider, @NonNull @BiometricsBackground Executor biometricsExecutor, @NonNull PrimaryBouncerInteractor primaryBouncerInteractor, @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor, @NonNull SessionTracker sessionTracker, @NonNull AlternateBouncerInteractor alternateBouncerInteractor, - @NonNull SecureSettings secureSettings, @NonNull InputManager inputManager, - @NonNull UdfpsUtils udfpsUtils, @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) { @@ -900,7 +674,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLatencyTracker = latencyTracker; mActivityLaunchAnimator = activityLaunchAnimator; - mAlternateTouchProvider = alternateTouchProvider.map(Provider::get).orElse(null); mSensorProps = new FingerprintSensorPropertiesInternal( -1 /* sensorId */, SensorProperties.STRENGTH_CONVENIENCE, @@ -912,13 +685,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { mBiometricExecutor = biometricsExecutor; mPrimaryBouncerInteractor = primaryBouncerInteractor; mAlternateBouncerInteractor = alternateBouncerInteractor; - mSecureSettings = secureSettings; - mUdfpsUtils = udfpsUtils; mInputManager = inputManager; mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate; - mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) - ? singlePointerTouchProcessor : null; + mTouchProcessor = singlePointerTouchProcessor; mSessionTracker = sessionTracker; mDumpManager.registerDumpable(TAG, this); @@ -1172,16 +942,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private void dispatchOnUiReady(long requestId) { - if (mAlternateTouchProvider != null) { - mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onUiReady(); - mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); - }); - } else { - mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId, - mSensorProps.sensorId); - mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); - } + mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId, + mSensorProps.sensorId); + mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); } private void onFingerDown( @@ -1241,24 +1004,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } mOnFingerDown = true; - if (mAlternateTouchProvider != null) { - mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major); - }); - mFgExecutor.execute(() -> { - if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { - mKeyguardUpdateMonitor.onUdfpsPointerDown((int) requestId); - } - }); - } else { - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, - minor, major, orientation, time, gestureStart, isAod); - } else { - mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x, - (int) y, minor, major); - } - } + mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, + minor, major, orientation, time, gestureStart, isAod); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); if (view != null && isOptical()) { @@ -1305,23 +1052,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mActivePointerId = -1; mAcquiredReceived = false; if (mOnFingerDown) { - if (mAlternateTouchProvider != null) { - mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onPointerUp(requestId); - }); - mFgExecutor.execute(() -> { - if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { - mKeyguardUpdateMonitor.onUdfpsPointerUp((int) requestId); - } - }); - } else { - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x, - y, minor, major, orientation, time, gestureStart, isAod); - } else { - mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); - } - } + mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x, + y, minor, major, orientation, time, gestureStart, isAod); for (Callback cb : mCallbacks) { cb.onFingerUp(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 34a0d8a46e52..7130bfb462e3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -20,7 +20,6 @@ import android.annotation.SuppressLint import android.annotation.UiThread import android.content.Context import android.graphics.PixelFormat -import android.graphics.Point import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD @@ -29,7 +28,6 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTING import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR import android.hardware.biometrics.BiometricOverlayConstants.ShowReason -import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.os.Build import android.os.RemoteException @@ -54,7 +52,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels @@ -65,7 +62,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.settings.SecureSettings import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Provider @@ -83,7 +79,6 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( private val context: Context, - fingerprintManager: FingerprintManager, private val inflater: LayoutInflater, private val windowManager: WindowManager, private val accessibilityManager: AccessibilityManager, @@ -97,7 +92,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, - private val secureSettings: SecureSettings, val requestId: Long, @ShowReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, @@ -107,7 +101,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, - private val udfpsUtils: UdfpsUtils, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, ) { @@ -134,10 +127,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY // Avoid announcing window title. accessibilityTitle = " " - - if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY - } + inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY } /** If the overlay is currently showing. */ @@ -206,7 +196,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( overlayTouchListener!! ) overlayTouchListener?.onTouchExplorationStateChanged(true) - useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) } } catch (e: RuntimeException) { Log.e(TAG, "showUdfpsOverlay | failed to add window", e) @@ -331,25 +320,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( return wasShowing } - /** - * This function computes the angle of touch relative to the sensor and maps - * the angle to a list of help messages which are announced if accessibility is enabled. - * - */ - fun onTouchOutsideOfSensorArea(scaledTouch: Point) { - val theStr = - udfpsUtils.onTouchOutsideOfSensorArea( - touchExplorationEnabled, - context, - scaledTouch.x, - scaledTouch.y, - overlayParams - ) - if (theStr != null) { - animationViewController?.doAnnounceForAccessibility(theStr) - } - } - /** Cancel this request. */ fun cancel() { try { @@ -367,10 +337,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ): WindowManager.LayoutParams { val paddingX = animation?.paddingX ?: 0 val paddingY = animation?.paddingY ?: 0 - if (!featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) && animation != null && - animation.listenForTouchesOutsideView()) { - flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - } val isEnrollment = when (requestReason) { REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true @@ -379,19 +345,15 @@ class UdfpsControllerOverlay @JvmOverloads constructor( // Use expanded overlay unless touchExploration enabled var rotatedBounds = - if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) { - Rect(overlayParams.sensorBounds) - } else { - Rect( - 0, - 0, - overlayParams.naturalDisplayWidth, - overlayParams.naturalDisplayHeight - ) - } - } else { + if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) { Rect(overlayParams.sensorBounds) + } else { + Rect( + 0, + 0, + overlayParams.naturalDisplayWidth, + overlayParams.naturalDisplayHeight + ) } val rot = overlayParams.rotation @@ -399,9 +361,9 @@ class UdfpsControllerOverlay @JvmOverloads constructor( if (!shouldRotate(animation)) { Log.v( TAG, "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) + - " animation=$animation" + - " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + - " isOccluded=${keyguardStateController.isOccluded}" + " animation=$animation" + + " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + + " isOccluded=${keyguardStateController.isOccluded}" ) } else { Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot)) @@ -412,14 +374,12 @@ class UdfpsControllerOverlay @JvmOverloads constructor( rot ) - if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - RotationUtils.rotateBounds( - sensorBounds, - overlayParams.naturalDisplayWidth, - overlayParams.naturalDisplayHeight, - rot - ) - } + RotationUtils.rotateBounds( + sensorBounds, + overlayParams.naturalDisplayWidth, + overlayParams.naturalDisplayHeight, + rot + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt index 8cc15dadffd2..afe37d496150 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt @@ -43,10 +43,6 @@ class UdfpsKeyguardView( return fingerprintDrawablePlaceHolder } - fun useExpandedOverlay(useExpandedOverlay: Boolean) { - mUseExpandedOverlay = useExpandedOverlay - } - fun isVisible(): Boolean { return visible } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 8ce98a92adbd..3d5be6fb9f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -19,7 +19,6 @@ package com.android.systemui.biometrics import android.animation.ValueAnimator import android.content.res.Configuration import android.util.MathUtils -import android.view.MotionEvent import android.view.View import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle @@ -27,16 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.res.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.StackStateAnimator @@ -80,8 +78,6 @@ open class UdfpsKeyguardViewControllerLegacy( ), UdfpsKeyguardViewControllerAdapter { private val uniqueIdentifier = this.toString() - private val useExpandedOverlay: Boolean = - featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) private var showingUdfpsBouncer = false private var udfpsRequested = false private var qsExpansion = 0f @@ -192,24 +188,6 @@ open class UdfpsKeyguardViewControllerLegacy( updateAlpha() updatePauseAuth() } - - /** - * Forward touches to the UdfpsController. This allows the touch to start from outside - * the sensor area and then slide their finger into the sensor area. - */ - override fun onTouch(event: MotionEvent) { - // Don't forward touches if the shade has already started expanding. - if (transitionToFullShadeProgress != 0f) { - return - } - - // Forwarding touches not needed with expanded overlay - if (useExpandedOverlay) { - return - } else { - udfpsController.onTouch(event) - } - } } private val occludingAppBiometricUI: OccludingAppBiometricUI = @@ -294,7 +272,6 @@ open class UdfpsKeyguardViewControllerLegacy( keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this activityLaunchAnimator.addListener(activityLaunchAnimatorListener) - view.mUseExpandedOverlay = useExpandedOverlay view.startIconAsyncInflate { val animationViewInternal: View = view.requireViewById(R.id.udfps_animation_view_internal) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java index 36a42f945235..95e3a76c11d8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java @@ -298,45 +298,34 @@ public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView { pw.println(" mUdfpsRequested=" + mUdfpsRequested); pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount); pw.println(" mAnimationType=" + mAnimationType); - pw.println(" mUseExpandedOverlay=" + mUseExpandedOverlay); } private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener = new AsyncLayoutInflater.OnInflateFinishedListener() { - @Override - public void onInflateFinished(View view, int resid, ViewGroup parent) { - mFullyInflated = true; - mAodFp = view.findViewById(R.id.udfps_aod_fp); - mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp); - mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg); - - updatePadding(); - updateColor(); - updateAlpha(); - - if (mUseExpandedOverlay) { - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.width = mSensorBounds.width(); - lp.height = mSensorBounds.height(); - RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds)); - lp.setMarginsRelative( - (int) relativeToView.left, - (int) relativeToView.top, - (int) relativeToView.right, - (int) relativeToView.bottom - ); - parent.addView(view, lp); - } else { - parent.addView(view); - } - - // requires call to invalidate to update the color - mLockScreenFp.addValueCallback( - new KeyPath("**"), LottieProperty.COLOR_FILTER, - frameInfo -> new PorterDuffColorFilter(mTextColorPrimary, - PorterDuff.Mode.SRC_ATOP) - ); - mOnFinishInflateRunnable.run(); - } - }; + @Override + public void onInflateFinished(View view, int resid, ViewGroup parent) { + mFullyInflated = true; + mAodFp = view.findViewById(R.id.udfps_aod_fp); + mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp); + mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg); + + updatePadding(); + updateColor(); + updateAlpha(); + + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + lp.width = mSensorBounds.width(); + lp.height = mSensorBounds.height(); + RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds)); + lp.setMarginsRelative((int) relativeToView.left, (int) relativeToView.top, + (int) relativeToView.right, (int) relativeToView.bottom); + parent.addView(view, lp); + + // requires call to invalidate to update the color + mLockScreenFp.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, + frameInfo -> new PorterDuffColorFilter(mTextColorPrimary, + PorterDuff.Mode.SRC_ATOP)); + mOnFinishInflateRunnable.run(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt index 6ce6172c9faa..76bcd6e2863b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt @@ -19,14 +19,12 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint -import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF import android.util.AttributeSet import android.util.Log import android.view.MotionEvent import android.widget.FrameLayout -import com.android.systemui.res.R import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.doze.DozeReceiver @@ -39,10 +37,6 @@ class UdfpsView( context: Context, attrs: AttributeSet? ) : FrameLayout(context, attrs), DozeReceiver { - - // Use expanded overlay when feature flag is true, set by UdfpsViewController - var useExpandedOverlay: Boolean = false - // sensorRect may be bigger than the sensor. True sensor dimensions are defined in // overlayParams.sensorBounds var sensorRect = Rect() @@ -53,14 +47,6 @@ class UdfpsView( textSize = 32f } - private val sensorTouchAreaCoefficient: Float = - context.theme.obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0, 0).use { a -> - require(a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) { - "UdfpsView must contain sensorTouchAreaCoefficient" - } - a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f) - } - /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */ var animationViewController: UdfpsAnimationViewController<*>? = null @@ -94,22 +80,8 @@ class UdfpsView( override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) - val paddingX = animationViewController?.paddingX ?: 0 - val paddingY = animationViewController?.paddingY ?: 0 - // Updates sensor rect in relation to the overlay view - if (useExpandedOverlay) { - animationViewController?.onSensorRectUpdated(RectF(sensorRect)) - } else { - sensorRect.set( - paddingX, - paddingY, - (overlayParams.sensorBounds.width() + paddingX), - (overlayParams.sensorBounds.height() + paddingY) - ) - - animationViewController?.onSensorRectUpdated(RectF(sensorRect)) - } + animationViewController?.onSensorRectUpdated(RectF(sensorRect)) } override fun onAttachedToWindow() { @@ -131,22 +103,6 @@ class UdfpsView( } } - fun isWithinSensorArea(x: Float, y: Float): Boolean { - // The X and Y coordinates of the sensor's center. - val translation = animationViewController?.touchTranslation ?: PointF(0f, 0f) - val cx = sensorRect.centerX() + translation.x - val cy = sensorRect.centerY() + translation.y - // Radii along the X and Y axes. - val rx = (sensorRect.right - sensorRect.left) / 2.0f - val ry = (sensorRect.bottom - sensorRect.top) / 2.0f - - return x > cx - rx * sensorTouchAreaCoefficient && - x < cx + rx * sensorTouchAreaCoefficient && - y > cy - ry * sensorTouchAreaCoefficient && - y < cy + ry * sensorTouchAreaCoefficient && - !(animationViewController?.shouldPauseAuth() ?: false) - } - fun configureDisplay(onDisplayConfigured: Runnable) { isDisplayConfigured = true animationViewController?.onDisplayConfiguring() diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index f6e02969e4c2..53b6879db3d7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -15,7 +15,10 @@ interface CommunalRepository { class CommunalRepositoryImpl @Inject constructor( - featureFlags: FeatureFlagsClassic, + private val featureFlags: FeatureFlagsClassic, ) : CommunalRepository { - override val isCommunalEnabled = featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) + override val isCommunalEnabled: Boolean + get() = + featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && + featureFlags.isEnabled(Flags.COMMUNAL_HUB) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt new file mode 100644 index 000000000000..9a9b0e29cbc4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt @@ -0,0 +1,144 @@ +/* + * 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.systemui.communal.data.repository + +import android.provider.Settings +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED +import android.provider.Settings.Secure.HubModeTutorialState +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.repository.UserRepository +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.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** + * Repository for the current state of hub mode tutorial. Valid states are defined in + * [HubModeTutorialState]. + */ +interface CommunalTutorialRepository { + /** Emits the tutorial state stored in Settings */ + val tutorialSettingState: StateFlow<Int> + + /** Update the tutorial state */ + suspend fun setTutorialState(@HubModeTutorialState state: Int) +} + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalTutorialRepositoryImpl +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + userRepository: UserRepository, + private val secureSettings: SecureSettings, + private val userTracker: UserTracker, + @CommunalLog logBuffer: LogBuffer, +) : CommunalTutorialRepository { + + companion object { + private const val TAG = "CommunalTutorialRepository" + } + + private data class SettingsState( + @HubModeTutorialState val hubModeTutorialState: Int? = null, + ) + + private val logger = Logger(logBuffer, TAG) + + private val settingsState: Flow<SettingsState> = + userRepository.selectedUserInfo + .flatMapLatest { observeSettings() } + .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed()) + + /** Emits the state of tutorial state in settings */ + override val tutorialSettingState: StateFlow<Int> = + settingsState + .map { it.hubModeTutorialState } + .filterNotNull() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = HUB_MODE_TUTORIAL_NOT_STARTED + ) + + private fun observeSettings(): Flow<SettingsState> = + secureSettings + .observerFlow( + userId = userTracker.userId, + names = + arrayOf( + Settings.Secure.HUB_MODE_TUTORIAL_STATE, + ) + ) + // Force an update + .onStart { emit(Unit) } + .map { readFromSettings() } + + private suspend fun readFromSettings(): SettingsState = + withContext(backgroundDispatcher) { + val userId = userTracker.userId + val hubModeTutorialState = + secureSettings.getIntForUser( + Settings.Secure.HUB_MODE_TUTORIAL_STATE, + HUB_MODE_TUTORIAL_NOT_STARTED, + userId, + ) + val settingsState = SettingsState(hubModeTutorialState) + logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) { + int1 = userId + str1 = settingsState.hubModeTutorialState.toString() + } + + settingsState + } + + override suspend fun setTutorialState(state: Int): Unit = + withContext(backgroundDispatcher) { + val userId = userTracker.userId + if (tutorialSettingState.value == state) { + return@withContext + } + logger.d({ "Update communal tutorial state to $int1 for user $int2" }) { + int1 = state + int2 = userId + } + secureSettings.putIntForUser( + Settings.Secure.HUB_MODE_TUTORIAL_STATE, + state, + userId, + ) + } +} diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt index f8b843bc032f..69b0a27c55a8 100644 --- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt @@ -1,22 +1,27 @@ /* - * Copyright (c) 2016, The Android Open Source Project + * 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES 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.security.keymaster; +package com.android.systemui.communal.data.repository -/* The cpp_header is relative to system/security/keystore/include - * Link against libkeystore_binder to make use of the native implementation of this Parcelable. - */ -parcelable KeyAttestationPackageInfo cpp_header "keystore/KeyAttestationPackageInfo.h"; +import dagger.Binds +import dagger.Module + +@Module +interface CommunalTutorialRepositoryModule { + @Binds + fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): CommunalTutorialRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 9fb8da3e76af..04bb6ae75e60 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -28,11 +28,13 @@ import kotlinx.coroutines.flow.Flow class CommunalInteractor @Inject constructor( - communalRepository: CommunalRepository, + private val communalRepository: CommunalRepository, widgetRepository: CommunalWidgetRepository, ) { + /** Whether communal features are enabled. */ - val isCommunalEnabled: Boolean = communalRepository.isCommunalEnabled + val isCommunalEnabled: Boolean + get() = communalRepository.isCommunalEnabled /** A flow of info about the widget to be displayed, or null if widget is unavailable. */ val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt new file mode 100644 index 000000000000..276df4eb68ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt @@ -0,0 +1,48 @@ +/* + * 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.systemui.communal.domain.interactor + +import android.provider.Settings +import com.android.systemui.communal.data.repository.CommunalTutorialRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged + +/** Encapsulates business-logic related to communal tutorial state. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalTutorialInteractor +@Inject +constructor( + communalTutorialRepository: CommunalTutorialRepository, + keyguardInteractor: KeyguardInteractor, +) { + /** An observable for whether the tutorial is available. */ + val isTutorialAvailable: Flow<Boolean> = + combine( + keyguardInteractor.isKeyguardVisible, + communalTutorialRepository.tutorialSettingState, + ) { isKeyguardVisible, tutorialSettingState -> + isKeyguardVisible && + tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt new file mode 100644 index 000000000000..dab6819e1028 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt @@ -0,0 +1,66 @@ +/* + * 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.systemui.communal.ui.binder + +import android.widget.TextView +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.launch + +/** View binder for communal tutorial indicator shown on keyguard. */ +object CommunalTutorialIndicatorViewBinder { + fun bind( + view: TextView, + viewModel: CommunalTutorialIndicatorViewModel, + ): DisposableHandle { + val disposableHandle = + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.showIndicator.collect { isVisible -> + updateView( + view = view, + isIndicatorVisible = isVisible, + ) + } + } + } + } + + return disposableHandle + } + + private fun updateView( + isIndicatorVisible: Boolean, + view: TextView, + ) { + if (!isIndicatorVisible) { + view.isGone = true + return + } + + if (!view.isVisible) { + view.isVisible = true + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt index 3ff1f09cc0f1..d8d1dc0c11ef 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.ui.view.layout.blueprints +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint @@ -28,10 +29,15 @@ import javax.inject.Inject class DefaultCommunalBlueprint @Inject constructor( + defaultCommunalHubSection: DefaultCommunalHubSection, defaultCommunalWidgetSection: DefaultCommunalWidgetSection, ) : KeyguardBlueprint { override val id: String = COMMUNAL - override val sections: Set<KeyguardSection> = setOf(defaultCommunalWidgetSection) + override val sections: Set<KeyguardSection> = + setOf( + defaultCommunalHubSection, + defaultCommunalWidgetSection, + ) companion object { const val COMMUNAL = "communal" diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt new file mode 100644 index 000000000000..027cc96350f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt @@ -0,0 +1,130 @@ +/* + * 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.systemui.communal.ui.view.layout.sections + +import android.content.res.Resources +import android.graphics.Typeface +import android.graphics.Typeface.NORMAL +import android.view.Gravity +import android.view.View +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.content.res.ResourcesCompat +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder +import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.view.layout.sections.removeView +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle + +class CommunalTutorialIndicatorSection +@Inject +constructor( + @Main private val resources: Resources, + private val communalTutorialIndicatorViewModel: CommunalTutorialIndicatorViewModel, + private val communalInteractor: CommunalInteractor, +) : KeyguardSection() { + private var communalTutorialIndicatorHandle: DisposableHandle? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + if (!communalInteractor.isCommunalEnabled) { + return + } + val padding = + constraintLayout.resources.getDimensionPixelSize( + R.dimen.communal_tutorial_indicator_padding + ) + val view = + TextView(constraintLayout.context).apply { + id = R.id.communal_tutorial_indicator + visibility = View.GONE + background = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_bg, + context.theme + ) + foreground = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_selected_border, + context.theme + ) + gravity = Gravity.CENTER_VERTICAL + typeface = Typeface.create("google-sans", NORMAL) + text = constraintLayout.context.getString(R.string.communal_tutorial_indicator_text) + setPadding(padding, padding, padding, padding) + } + constraintLayout.addView(view) + } + + override fun bindData(constraintLayout: ConstraintLayout) { + if (!communalInteractor.isCommunalEnabled) { + return + } + communalTutorialIndicatorHandle = + CommunalTutorialIndicatorViewBinder.bind( + constraintLayout.requireViewById(R.id.communal_tutorial_indicator), + communalTutorialIndicatorViewModel, + ) + } + + override fun applyConstraints(constraintSet: ConstraintSet) { + if (!communalInteractor.isCommunalEnabled) { + return + } + val tutorialIndicatorId = R.id.communal_tutorial_indicator + val width = resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_fixed_width) + val horizontalOffsetMargin = + resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_horizontal_offset) + + constraintSet.apply { + constrainWidth(tutorialIndicatorId, width) + constrainHeight(tutorialIndicatorId, WRAP_CONTENT) + connect( + tutorialIndicatorId, + ConstraintSet.RIGHT, + ConstraintSet.PARENT_ID, + ConstraintSet.RIGHT, + horizontalOffsetMargin + ) + connect( + tutorialIndicatorId, + ConstraintSet.TOP, + ConstraintSet.PARENT_ID, + ConstraintSet.TOP + ) + connect( + tutorialIndicatorId, + ConstraintSet.BOTTOM, + ConstraintSet.PARENT_ID, + ConstraintSet.BOTTOM + ) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + communalTutorialIndicatorHandle?.dispose() + constraintLayout.removeView(R.id.communal_tutorial_indicator) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt new file mode 100644 index 000000000000..932dbfb093ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt @@ -0,0 +1,57 @@ +package com.android.systemui.communal.ui.view.layout.sections + +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.view.layout.sections.removeView +import com.android.systemui.res.R +import javax.inject.Inject + +/** A keyguard section that hosts the communal hub. */ +class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() { + private val communalHubViewId = R.id.communal_hub + + override fun addViews(constraintLayout: ConstraintLayout) { + constraintLayout.addView( + ComposeFacade.createCommunalView(constraintLayout.context).apply { + id = communalHubViewId + }, + ) + } + + override fun bindData(constraintLayout: ConstraintLayout) {} + + override fun applyConstraints(constraintSet: ConstraintSet) { + constraintSet.apply { + connect( + communalHubViewId, + ConstraintSet.START, + ConstraintSet.PARENT_ID, + ConstraintSet.START, + ) + connect( + communalHubViewId, + ConstraintSet.TOP, + ConstraintSet.PARENT_ID, + ConstraintSet.TOP, + ) + connect( + communalHubViewId, + ConstraintSet.END, + ConstraintSet.PARENT_ID, + ConstraintSet.END, + ) + connect( + communalHubViewId, + ConstraintSet.BOTTOM, + ConstraintSet.PARENT_ID, + ConstraintSet.BOTTOM, + ) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(communalHubViewId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt new file mode 100644 index 000000000000..eaf95508cf12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt @@ -0,0 +1,31 @@ +/* + * 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.systemui.communal.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** View model for communal tutorial indicator on keyguard */ +class CommunalTutorialIndicatorViewModel +@Inject +constructor( + communalTutorialInteractor: CommunalTutorialInteractor, +) { + /** An observable for whether the tutorial indicator view should be visible. */ + val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable +} diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 1a6f7e13cf68..5c1539a7fcf3 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -72,4 +72,9 @@ interface BaseComposeFacade { windowInsets: StateFlow<WindowInsets?>, sceneByKey: Map<SceneKey, Scene>, ): View + + /** Create a [View] that represents the communal hub. */ + fun createCommunalView( + context: Context, + ): View } diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt index 8e3b5109339c..436b8cb851ab 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt @@ -22,7 +22,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.qs.SettingObserver +import com.android.systemui.qs.UserSettingObserver import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject @@ -64,7 +64,8 @@ constructor( .flatMapLatest { userInfo -> conflatedCallbackFlow { val observer = - object : SettingObserver(secureSettings, null, setting, userInfo.id) { + object : + UserSettingObserver(secureSettings, null, setting, userInfo.id) { override fun handleValueChanged( value: Int, observedChange: Boolean diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 04a9cae31382..d57f31f91df1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -67,6 +67,7 @@ import android.hardware.input.InputManager; import android.media.AudioManager; import android.media.IAudioService; import android.media.MediaRouter2Manager; +import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjectionManager; import android.media.session.MediaSessionManager; import android.net.ConnectivityManager; @@ -414,6 +415,13 @@ public class FrameworkServicesModule { } @Provides + @Singleton + static IMediaProjectionManager provideIMediaProjectionManager() { + return IMediaProjectionManager.Stub.asInterface( + ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)); + } + + @Provides static MediaRouter2Manager provideMediaRouter2Manager(Context context) { return MediaRouter2Manager.getInstance(context); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java index ca725c0e39ff..5c38264fbcf6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java @@ -64,6 +64,7 @@ public class GlobalModule { * @deprecated Deprecdated because {@link Display#getMetrics} is deprecated. */ @Provides + @Deprecated public DisplayMetrics provideDisplayMetrics(Context context) { DisplayMetrics displayMetrics = new DisplayMetrics(); context.getDisplay().getMetrics(displayMetrics); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 4b6ad6d9be03..7d4e1a1011db 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -33,7 +33,6 @@ import com.android.systemui.aconfig.AConfigModule; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; import com.android.systemui.authentication.AuthenticationModule; -import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.biometrics.FingerprintReEnrollNotification; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; @@ -56,6 +55,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; +import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; @@ -181,6 +181,7 @@ import javax.inject.Named; FalsingModule.class, FlagsModule.class, FooterActionsModule.class, + KeyEventRepositoryModule.class, KeyboardModule.class, KeyguardBlueprintModule.class, LetterboxModule.class, @@ -298,9 +299,6 @@ public abstract class SystemUIModule { abstract UdfpsDisplayModeProvider optionalUdfpsDisplayModeProvider(); @BindsOptionalOf - abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider(); - - @BindsOptionalOf abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider(); @BindsOptionalOf diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt index 7510cf6ce04c..d19efbdd8026 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt @@ -15,14 +15,12 @@ */ package com.android.systemui.display.ui.view -import android.app.Dialog import android.content.Context import android.os.Bundle -import android.view.Gravity import android.view.View -import android.view.WindowManager import android.widget.TextView import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog /** * Dialog used to decide what to do with a connected display. @@ -35,7 +33,7 @@ class MirroringConfirmationDialog( private val onStartMirroringClickListener: View.OnClickListener, private val onCancelMirroring: View.OnClickListener, theme: Int = R.style.Theme_SystemUI_Dialog, -) : Dialog(context, theme) { +) : SystemUIBottomSheetDialog(context, theme) { private lateinit var mirrorButton: TextView private lateinit var dismissButton: TextView @@ -43,13 +41,8 @@ class MirroringConfirmationDialog( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - window?.apply { - setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) - addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) - setGravity(Gravity.BOTTOM) - } setContentView(R.layout.connected_display_dialog) - setCanceledOnTouchOutside(true) + mirrorButton = requireViewById<TextView>(R.id.enable_display).apply { setOnClickListener(onStartMirroringClickListener) diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java index 6bbd40c4f892..694695017efd 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -31,7 +31,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.os.Bundle; -import android.os.UserHandle; import android.util.Log; import androidx.annotation.NonNull; @@ -56,15 +55,15 @@ import javax.inject.Named; /** * Concrete implementation of the a Flag manager that returns default values for debug builds - * + * <p> * Flags can be set (or unset) via the following adb command: - * + * <p> * adb shell cmd statusbar flag <id> <on|off|toggle|erase> - * + * <p> * Alternatively, you can change flags via a broadcast intent: - * + * <p> * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>] - * + * <p> * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command. */ @SysUISingleton @@ -319,8 +318,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { Log.w(TAG, "Failed to set flag " + name + " to " + value); return; } - mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data, - UserHandle.USER_CURRENT); + mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), data); } <T> void eraseFlag(Flag<T> flag) { @@ -354,8 +352,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */ private void eraseInternal(String name) { // We can't actually "erase" things from settings, but we can set them to empty! - mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "", - UserHandle.USER_CURRENT); + mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), ""); Log.i(TAG, "Erase name " + name); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index f6f24e0aecc0..11ac39ff867b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -473,6 +473,9 @@ object Flags { // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") + // TODO(b/304506662): Tracking Bug + val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true) + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") @@ -667,8 +670,6 @@ object Flags { @JvmField val NOTE_TASKS = releasedFlag("keycode_flag") // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.) - // TODO(b/259264861): Tracking Bug - @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection") // 2300 - stylus @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used") @@ -786,8 +787,7 @@ object Flags { // TODO(b/290213663): Tracking Bug @JvmField - val ONE_WAY_HAPTICS_API_MIGRATION = - unreleasedFlag("oneway_haptics_api_migration", teamfood = true) + val ONE_WAY_HAPTICS_API_MIGRATION = releasedFlag("oneway_haptics_api_migration") /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */ @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt index eaecda52a5a2..3fe68062f19a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt @@ -28,27 +28,23 @@ import com.android.systemui.Dependency * flag-disabled builds, but with a check that should crash eng builds or tests when the * expectation is violated. * - * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it, + * The constructors require that you provide a [FeatureFlags] instance. If you're using this in a + * View class, it's acceptable to ue the [forView] constructor methods, which do not require one, * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes - * inside views where injecting flag values after initialization can be error-prone. + * inside Views where injecting flag values after initialization can be error-prone. */ -class ViewRefactorFlag +class RefactorFlag private constructor( private val injectedFlags: FeatureFlags?, - private val flag: BooleanFlag, + private val flagName: Any, private val readFlagValue: (FeatureFlags) -> Boolean ) { - @JvmOverloads constructor( - flags: FeatureFlags? = null, + flags: FeatureFlags, flag: UnreleasedFlag ) : this(flags, flag, { it.isEnabled(flag) }) - @JvmOverloads - constructor( - flags: FeatureFlags? = null, - flag: ReleasedFlag - ) : this(flags, flag, { it.isEnabled(flag) }) + constructor(flags: FeatureFlags, flag: ReleasedFlag) : this(flags, flag, { it.isEnabled(flag) }) /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */ val isEnabled by lazy { @@ -69,7 +65,8 @@ private constructor( * } * ```` */ - fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." } + fun assertDisabled() = + check(!isEnabled) { "Code path not supported when $flagName is enabled." } /** * Called to ensure code is only run when the flag is enabled. This protects users from the @@ -87,13 +84,25 @@ private constructor( */ fun expectEnabled(): Boolean { if (!isEnabled) { - val message = "Code path not supported when $flag is disabled." + val message = "Code path not supported when $flagName is disabled." Log.wtf(TAG, message, Exception(message)) } return isEnabled } - private companion object { - private const val TAG = "ViewRefactorFlag" + companion object { + private const val TAG = "RefactorFlag" + + /** Construct a [RefactorFlag] within View construction where injection is impossible. */ + @JvmStatic + @JvmOverloads + fun forView(flag: UnreleasedFlag, flags: FeatureFlags? = null) = + RefactorFlag(flags, flag) { it.isEnabled(flag) } + + /** Construct a [RefactorFlag] within View construction where injection is impossible. */ + @JvmStatic + @JvmOverloads + fun forView(flag: ReleasedFlag, flags: FeatureFlags? = null) = + RefactorFlag(flags, flag) { it.isEnabled(flag) } } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index ac402303bf69..c6c1f79c7113 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -749,7 +749,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @VisibleForTesting boolean shouldDisplayBugReport(@Nullable UserInfo user) { return user != null && user.isAdmin() - && mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, + && mSecureSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, user.id) != 0; } @@ -1091,7 +1091,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public boolean showBeforeProvisioning() { - return Build.isDebuggable() && mGlobalSettings.getIntForUser( + return Build.isDebuggable() && mSecureSettings.getIntForUser( Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0 && getCurrentUser().isAdmin(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt new file mode 100644 index 000000000000..5bc5d0b290ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt @@ -0,0 +1,56 @@ +/* + * 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.systemui.keyevent.data.repository + +import android.view.KeyEvent +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.CommandQueue +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** Defines interface for classes that encapsulate application state for key event presses. */ +interface KeyEventRepository { + /** Observable for whether the power button key is pressed/down or not. */ + val isPowerButtonDown: Flow<Boolean> +} + +@SysUISingleton +class KeyEventRepositoryImpl +@Inject +constructor( + val commandQueue: CommandQueue, +) : KeyEventRepository { + override val isPowerButtonDown: Flow<Boolean> = conflatedCallbackFlow { + val callback = + object : CommandQueue.Callbacks { + override fun handleSystemKey(event: KeyEvent) { + if (event.keyCode == KeyEvent.KEYCODE_POWER) { + trySendWithFailureLogging(event.isDown, TAG, "updated isPowerButtonDown") + } + } + } + commandQueue.addCallback(callback) + awaitClose { commandQueue.removeCallback(callback) } + } + + companion object { + private const val TAG = "KeyEventRepositoryImpl" + } +} diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt index 9f6ff58ed5ce..afba5dbd84c1 100644 --- a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl +++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2016, The Android Open Source Project + * 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,12 @@ * limitations under the License. */ -package android.security.keymaster; +package com.android.systemui.keyevent.data.repository -/* The cpp_header is relative to system/security/keystore/include - * Link against libkeystore_binder to make use of the native implementation of this Parcelable. - */ -parcelable KeyAttestationApplicationId cpp_header "keystore/KeyAttestationApplicationId.h"; +import dagger.Binds +import dagger.Module + +@Module +interface KeyEventRepositoryModule { + @Binds fun keyEventRepository(impl: KeyEventRepositoryImpl): KeyEventRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt index 3f2f67dbba37..9949fa589cd5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt @@ -13,55 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.systemui.keyevent.domain.interactor -import android.view.KeyEvent -import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor +import com.android.systemui.keyevent.data.repository.KeyEventRepository import javax.inject.Inject /** - * Sends key events to the appropriate interactors and then acts upon key events that haven't - * already been handled but should be handled by SystemUI. + * Business logic for all key event state. This includes all key events, regardless of whether + * they've been handled or not by a consumer. + * + * For key events that SysUI wants to properly handle, see [SysUIKeyEventHandler]. */ @SysUISingleton class KeyEventInteractor @Inject constructor( - private val backActionInteractor: BackActionInteractor, - private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor, + repository: KeyEventRepository, ) { - fun dispatchKeyEvent(event: KeyEvent): Boolean { - if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) { - return true - } - - when (event.keyCode) { - KeyEvent.KEYCODE_BACK -> { - if (event.handleAction()) { - backActionInteractor.onBackRequested() - } - return true - } - } - return false - } - - fun interceptMediaKey(event: KeyEvent): Boolean { - return keyguardKeyEventInteractor.interceptMediaKey(event) - } - - fun dispatchKeyEventPreIme(event: KeyEvent): Boolean { - return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event) - } - - companion object { - // Most actions shouldn't be handled on the down event and instead handled on subsequent - // key events like ACTION_UP. - fun KeyEvent.handleAction(): Boolean { - return action != KeyEvent.ACTION_DOWN - } - } + val isPowerButtonDown = repository.isPowerButtonDown } diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt new file mode 100644 index 000000000000..1febc79b8241 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt @@ -0,0 +1,69 @@ +/* + * 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.systemui.keyevent.domain.interactor + +import android.view.KeyEvent +import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor +import javax.inject.Inject + +/** + * Sends key events to the appropriate interactors and then acts upon key events that haven't + * already been handled but should be handled by SystemUI. + * + * To observe any key event states, see [KeyEventInteractor]. + */ +@SysUISingleton +class SysUIKeyEventHandler +@Inject +constructor( + private val backActionInteractor: BackActionInteractor, + private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor, +) { + fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) { + return true + } + + when (event.keyCode) { + KeyEvent.KEYCODE_BACK -> { + if (event.handleAction()) { + backActionInteractor.onBackRequested() + } + return true + } + } + return false + } + + fun interceptMediaKey(event: KeyEvent): Boolean { + return keyguardKeyEventInteractor.interceptMediaKey(event) + } + + fun dispatchKeyEventPreIme(event: KeyEvent): Boolean { + return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event) + } + + companion object { + // Most actions shouldn't be handled on the down event and instead handled on subsequent + // key events like ACTION_UP. + fun KeyEvent.handleAction(): Boolean { + return action != KeyEvent.ACTION_DOWN + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index a1f0e7723597..b45613e0e182 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -269,6 +269,11 @@ public class KeyguardService extends Service { } } + @Override + public void onTransitionConsumed(IBinder transition, boolean aborted) + throws RemoteException { + } + private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t, @NonNull RemoteAnimationTarget[] targets) { for (RemoteAnimationTarget target : targets) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index fde92b85cac3..0bac40bcbcc1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -451,7 +451,8 @@ class KeyguardUnlockAnimationController @Inject constructor( if (!keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations) { try { - launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */) + launcherUnlockController?.setUnlockAmount(1f, + biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */) } catch (e: DeadObjectException) { Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " + "onKeyguardGoingAwayChanged(). Catching exception as this should mean " + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 41bde91d9a01..081edd152538 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -39,6 +39,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.communal.data.repository.CommunalRepositoryModule; +import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule; import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -96,6 +97,7 @@ import kotlinx.coroutines.CoroutineDispatcher; KeyguardUserSwitcherComponent.class}, includes = { CommunalRepositoryModule.class, + CommunalTutorialRepositoryModule.class, CommunalWidgetRepositoryModule.class, FalsingModule.class, KeyguardDataQuickAffordanceModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 75aa4b60f7b6..ca882e539a1a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -29,9 +29,7 @@ import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @SysUISingleton @@ -64,29 +62,14 @@ constructor( private fun listenForDreamingToOccluded() { scope.launch { - keyguardInteractor.isDreaming - // Add a slight delay, as dreaming and occluded events will arrive with a small gap - // in time. This prevents a transition to OCCLUSION happening prematurely. - .onEach { delay(50) } - .sample( - combine( - keyguardInteractor.isKeyguardOccluded, - transitionInteractor.startedKeyguardTransitionStep, - ::Pair, - ), - ::toTriple - ) - .collect { (isDreaming, isOccluded, lastStartedTransition) -> + combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair) + .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple) + .collect { (isOccluded, isDreaming, lastStartedTransition) -> if ( isOccluded && !isDreaming && - (lastStartedTransition.to == KeyguardState.DREAMING || - lastStartedTransition.to == KeyguardState.LOCKSCREEN) + lastStartedTransition.to == KeyguardState.DREAMING ) { - // At the moment, checking for LOCKSCREEN state above provides a corrective - // action. There's no great signal to determine when the dream is ending - // and a transition to OCCLUDED is beginning directly. For now, the solution - // is DREAMING->LOCKSCREEN->OCCLUDED startTransitionTo(KeyguardState.OCCLUDED) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index ffa1a4959878..660bd84006d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -318,16 +318,9 @@ constructor( private fun listenForLockscreenToOccluded() { scope.launch { keyguardInteractor.isKeyguardOccluded - .sample( - combine( - transitionInteractor.startedKeyguardState, - keyguardInteractor.isDreaming, - ::Pair - ), - ::toTriple - ) - .collect { (isOccluded, keyguardState, isDreaming) -> - if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + .sample(transitionInteractor.startedKeyguardState, ::Pair) + .collect { (isOccluded, keyguardState) -> + if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) { startTransitionTo(KeyguardState.OCCLUDED) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt index ac012f840d1f..122c4c44cf9f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt @@ -21,7 +21,7 @@ import android.media.AudioManager import android.view.KeyEvent import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction +import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler.Companion.handleAction import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractor @@ -29,17 +29,14 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi /** Handles key events arriving when the keyguard is showing or device is dozing. */ -@ExperimentalCoroutinesApi @SysUISingleton class KeyguardKeyEventInteractor @Inject constructor( private val context: Context, private val statusBarStateController: StatusBarStateController, - private val keyguardInteractor: KeyguardInteractor, private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, private val shadeController: ShadeController, private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper, @@ -58,11 +55,7 @@ constructor( if (event.handleAction()) { when (event.keyCode) { KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent() - KeyEvent.KEYCODE_SPACE, - KeyEvent.KEYCODE_ENTER -> - if (isDeviceAwake()) { - return collapseShadeLockedOrShowPrimaryBouncer() - } + KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent() } } return false @@ -98,24 +91,16 @@ constructor( (statusBarStateController.state != StatusBarState.SHADE) && statusBarKeyguardViewManager.shouldDismissOnMenuPressed() if (shouldUnlockOnMenuPressed) { - return collapseShadeLockedOrShowPrimaryBouncer() + shadeController.animateCollapseShadeForced() + return true } return false } - private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean { - when (statusBarStateController.state) { - StatusBarState.SHADE -> return false - StatusBarState.SHADE_LOCKED -> { - shadeController.animateCollapseShadeForced() - return true - } - StatusBarState.KEYGUARD -> { - if (!statusBarKeyguardViewManager.primaryBouncerIsShowing()) { - statusBarKeyguardViewManager.showPrimaryBouncer(true) - return true - } - } + private fun dispatchSpaceEvent(): Boolean { + if (isDeviceAwake() && statusBarStateController.state != StatusBarState.SHADE) { + shadeController.animateCollapseShadeForced() + return true } return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index abc30efec716..c5a8375f5576 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -47,6 +47,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.util.doOnEnd import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -402,6 +403,9 @@ object KeyguardBottomAreaViewBinder { KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds shakeAnimator.interpolator = CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles) + shakeAnimator.doOnEnd { + view.translationX = 0f + } shakeAnimator.start() vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index f0d118cbe20f..99025acef70d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -41,6 +41,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewMod import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.util.doOnEnd import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -240,6 +241,9 @@ object KeyguardQuickAffordanceViewBinder { KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds shakeAnimator.interpolator = CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles) + shakeAnimator.doOnEnd { + view.translationX = 0f + } shakeAnimator.start() vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt index a0e0da452036..475d26f1db54 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt @@ -23,7 +23,6 @@ import android.widget.FrameLayout import androidx.asynclayoutinflater.view.AsyncLayoutInflater import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.biometrics.UdfpsKeyguardView import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel @@ -32,6 +31,7 @@ import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -52,8 +52,6 @@ object UdfpsKeyguardViewBinder { fingerprintViewModel: FingerprintViewModel, backgroundViewModel: BackgroundViewModel, ) { - view.useExpandedOverlay(viewModel.useExpandedOverlay()) - val layoutInflaterFinishListener = AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent -> UdfpsKeyguardInternalViewBinder.bind( @@ -63,25 +61,21 @@ object UdfpsKeyguardViewBinder { fingerprintViewModel, backgroundViewModel, ) - if (viewModel.useExpandedOverlay()) { - val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams - lp.width = viewModel.sensorBounds.width() - lp.height = viewModel.sensorBounds.height() - val relativeToView = - getBoundsRelativeToView( - inflatedInternalView, - RectF(viewModel.sensorBounds), - ) - lp.setMarginsRelative( - relativeToView.left.toInt(), - relativeToView.top.toInt(), - relativeToView.right.toInt(), - relativeToView.bottom.toInt(), + val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams + lp.width = viewModel.sensorBounds.width() + lp.height = viewModel.sensorBounds.height() + val relativeToView = + getBoundsRelativeToView( + inflatedInternalView, + RectF(viewModel.sensorBounds), ) - parent!!.addView(inflatedInternalView, lp) - } else { - parent!!.addView(inflatedInternalView) - } + lp.setMarginsRelative( + relativeToView.left.toInt(), + relativeToView.top.toInt(), + relativeToView.right.toInt(), + relativeToView.bottom.toInt(), + ) + parent!!.addView(inflatedInternalView, lp) } val inflater = AsyncLayoutInflater(view.context) inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener) 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 c6d8ec7789f2..4a2954dc6559 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 @@ -29,12 +29,12 @@ import android.os.Handler import android.os.IBinder import android.view.Display import android.view.Display.DEFAULT_DISPLAY +import android.view.DisplayInfo import android.view.LayoutInflater import android.view.SurfaceControlViewHost import android.view.View import android.view.ViewGroup import android.view.WindowManager -import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible @@ -129,7 +129,7 @@ constructor( bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS) private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY) - private val display: Display = displayManager.getDisplay(displayId) + private val display: Display? = displayManager.getDisplay(displayId) private var host: SurfaceControlViewHost @@ -179,7 +179,7 @@ constructor( fun render() { mainHandler.post { - val previewContext = context.createDisplayContext(display) + val previewContext = display?.let { context.createDisplayContext(it) } ?: context val rootView = FrameLayout(previewContext) @@ -189,16 +189,18 @@ constructor( setUpBottomArea(rootView) } - val windowContext = context.createWindowContext(display, TYPE_KEYGUARD, null) - val windowManagerOfDisplay = windowContext.getSystemService(WindowManager::class.java) + var displayInfo: DisplayInfo? = null + display?.let { + displayInfo = DisplayInfo() + it.getDisplayInfo(displayInfo) + } rootView.measure( View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.width() - ?: windowManager.currentWindowMetrics.bounds.width(), + displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(), View.MeasureSpec.EXACTLY ), View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.height() + displayInfo?.logicalHeight ?: windowManager.currentWindowMetrics.bounds.height(), View.MeasureSpec.EXACTLY ), diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index e8df1a6fdaab..d8e43966990e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints +import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection @@ -53,6 +54,7 @@ constructor( splitShadeGuidelines: SplitShadeGuidelines, aodNotificationIconsSection: AodNotificationIconsSection, aodBurnInSection: AodBurnInSection, + communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, ) : KeyguardBlueprint { override val id: String = DEFAULT @@ -69,6 +71,7 @@ constructor( splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, + communalTutorialIndicatorSection, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt index 929f27f4fea3..dca151db8b58 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt @@ -17,20 +17,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.graphics.Rect -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi -class UdfpsKeyguardViewModel -@Inject -constructor( - private val featureFlags: FeatureFlags, -) { +class UdfpsKeyguardViewModel @Inject constructor() { var sensorBounds: Rect = Rect() - - fun useExpandedOverlay(): Boolean { - return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index 3d4fca1b8945..1b3b47350197 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -51,11 +51,12 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener Uri uri; boolean looping; AudioAttributes attributes; + float volume; long requestTime; public String toString() { return "{ code=" + code + " looping=" + looping + " attributes=" + attributes - + " uri=" + uri + " }"; + + " volume=" + volume + " uri=" + uri + " }"; } } @@ -101,6 +102,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener player.setAudioAttributes(mCmd.attributes); player.setDataSource(mCmd.context, mCmd.uri); player.setLooping(mCmd.looping); + player.setVolume(mCmd.volume); player.setOnCompletionListener(NotificationPlayer.this); player.setOnErrorListener(NotificationPlayer.this); player.prepare(); @@ -401,10 +403,11 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener * (see {@link MediaPlayer#setLooping(boolean)}) * @param stream the AudioStream to use. * (see {@link MediaPlayer#setAudioStreamType(int)}) + * @param volume the volume for the audio with values in range [0.0, 1.0] * @deprecated use {@link #play(Context, Uri, boolean, AudioAttributes)} instead. */ @Deprecated - public void play(Context context, Uri uri, boolean looping, int stream) { + public void play(Context context, Uri uri, boolean looping, int stream, float volume) { if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); } PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play"); Command cmd = new Command(); @@ -414,6 +417,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener cmd.uri = uri; cmd.looping = looping; cmd.attributes = new AudioAttributes.Builder().setInternalLegacyStreamType(stream).build(); + cmd.volume = volume; synchronized (mCmdQueue) { enqueueLocked(cmd); mState = PLAY; @@ -432,8 +436,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener * (see {@link MediaPlayer#setLooping(boolean)}) * @param attributes the AudioAttributes to use. * (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)}) + * @param volume the volume for the audio with values in range [0.0, 1.0] */ - public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) { + public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes, + float volume) { if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); } Command cmd = new Command(); cmd.requestTime = SystemClock.uptimeMillis(); @@ -442,6 +448,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener cmd.uri = uri; cmd.looping = looping; cmd.attributes = attributes; + cmd.volume = volume; synchronized (mCmdQueue) { enqueueLocked(cmd); mState = PLAY; diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 80be76661098..7a488365c740 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -285,7 +285,8 @@ public class RingtonePlayer implements CoreStartable { } @Override - public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) { + public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa, + float volume) { if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")"); if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Async playback only available from system UID."); @@ -293,7 +294,7 @@ public class RingtonePlayer implements CoreStartable { if (UserHandle.ALL.equals(user)) { user = UserHandle.SYSTEM; } - mAsyncPlayer.play(getContextForUser(user), uri, looping, aa); + mAsyncPlayer.play(getContextForUser(user), uri, looping, aa, volume); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt index a1291a4d6b0f..724241d8d41f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.controls.pipeline import android.content.Context import android.os.SystemProperties import android.util.Log +import com.android.internal.annotations.KeepForWeakReference import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main @@ -82,6 +83,8 @@ constructor( private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA private var reactivatedKey: String? = null + // Ensure the field (and associated reference) isn't removed during optimization. + @KeepForWeakReference private val userTrackerCallback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 0f3e0acd007d..2a32ddf03137 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -60,7 +60,6 @@ import com.android.internal.annotations.Keep import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -83,6 +82,7 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager @@ -90,6 +90,7 @@ import com.android.systemui.tuner.TunerService import com.android.systemui.util.Assert import com.android.systemui.util.Utils import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.ThreadFactory import com.android.systemui.util.time.SystemClock import com.android.systemui.util.traceSection import java.io.IOException @@ -245,7 +246,7 @@ class MediaDataManager( @Inject constructor( context: Context, - @Background backgroundExecutor: Executor, + threadFactory: ThreadFactory, @Main uiExecutor: Executor, @Main foregroundExecutor: DelayableExecutor, mediaControllerFactory: MediaControllerFactory, @@ -267,7 +268,9 @@ class MediaDataManager( keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, - backgroundExecutor, + // Loading bitmap for UMO background can take longer time, so it cannot run on the default + // background thread. Use a custom thread for media. + threadFactory.buildExecutorOnNewThread(TAG), uiExecutor, foregroundExecutor, mediaControllerFactory, @@ -1429,8 +1432,6 @@ class MediaDataManager( } private fun onSessionDestroyed(key: String) { - if (!mediaFlags.isRetainingPlayersEnabled()) return - if (DEBUG) Log.d(TAG, "session destroyed for $key") val entry = mediaEntries.remove(key) ?: return // Clear token since the session is no longer valid @@ -1474,7 +1475,7 @@ class MediaDataManager( if (DEBUG) Log.d(TAG, "Removing still-active player $key") notifyMediaDataRemoved(key) logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) - } else { + } else if (mediaFlags.isRetainingPlayersEnabled() || isAbleToResume(removed)) { // Convert to resume if (DEBUG) { Log.d( @@ -1484,6 +1485,11 @@ class MediaDataManager( ) } convertToResumePlayer(key, removed) + } else { + // Retaining players flag is off and app doesn't support resume: remove player. + if (DEBUG) Log.d(TAG, "Removing player $key") + notifyMediaDataRemoved(key) + logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt index 1fe93ed5503d..1db31ae4e050 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.Context import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager +import android.media.RoutingSessionInfo import android.media.session.MediaController import android.text.TextUtils import android.util.Log @@ -31,17 +32,20 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import java.util.concurrent.Executor @@ -64,7 +68,8 @@ constructor( private val localBluetoothManager: LocalBluetoothManager?, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, - dumpManager: DumpManager + dumpManager: DumpManager, + private val featureFlags: FeatureFlagsClassic, ) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() @@ -215,6 +220,7 @@ constructor( println(" volumeControlId=$volumeControlId cached= $playbackVolumeControlId") println(" routingSession=$routingSession") println(" selectedRoutes=$selectedRoutes") + println(" currentConnectedDevice=${localMediaManager.currentConnectedDevice}") } } @@ -348,16 +354,16 @@ constructor( } val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice - val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } + val routingSession = + controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device - val enabled = device != null && (controller == null || route != null) - val name = - if (controller == null || route != null) { - route?.name?.toString() ?: device?.name - } else { - null - } + val enabled = device != null && (controller == null || routingSession != null) + + val name = getDeviceName(device, routingSession) + if (DEBUG) { + Log.d(TAG, "new device name $name") + } current = MediaDeviceData( enabled, @@ -369,6 +375,57 @@ constructor( } } + /** Return a display name for the current device / route, or null if not possible */ + private fun getDeviceName( + device: MediaDevice?, + routingSession: RoutingSessionInfo?, + ): String? { + val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } + + if (DEBUG) { + Log.d( + TAG, + "device is $device, controller $controller," + + " routingSession ${routingSession?.name}" + + " or ${selectedRoutes?.firstOrNull()?.name}" + ) + } + + if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) { + if (controller == null || routingSession != null) { + return routingSession?.name?.toString() ?: device?.name + } + return null + } + + if (controller == null) { + // In resume state, we don't have a controller - just use the device name + return device?.name + } + + if (routingSession == null) { + // This happens when casting from apps that do not support MediaRouter2 + // The output switcher can't show anything useful here, so set to null + return null + } + + // If this is a user route (app / cast provided), use the provided name + if (!routingSession.isSystemSession) { + return routingSession.name?.toString() ?: device?.name + } + + selectedRoutes?.firstOrNull()?.let { + if (device is PhoneMediaDevice) { + // Get the (localized) name for this phone device + return PhoneMediaDevice.getSystemRouteNameFromType(context, it) + } else { + // If it's another type of device (in practice, Bluetooth), use the route name + return it.name.toString() + } + } + return null + } + private fun isLeAudioBroadcastEnabled(): Boolean { if (localBluetoothManager != null) { val profileManager = localBluetoothManager.profileManager diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt new file mode 100644 index 000000000000..8634b0911391 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -0,0 +1,83 @@ +/* + * 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.systemui.mediaprojection + +import android.media.projection.IMediaProjectionManager +import android.os.Process +import android.os.RemoteException +import android.util.Log +import com.android.internal.util.FrameworkStatsLog +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * Helper class for requesting that the server emit logs describing the MediaProjection setup + * experience. + */ +@SysUISingleton +class MediaProjectionMetricsLogger +@Inject +constructor(private val service: IMediaProjectionManager) { + /** + * Request to log that the permission was requested. + * + * @param sessionCreationSource The entry point requesting permission to capture. + */ + fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) { + // TODO check that state & SessionCreationSource matches expected values + notifyToServer(state, sessionCreationSource) + } + + /** + * Request to log that the permission request moved to the given state. + * + * Should not be used for the initialization state, since that + */ + fun notifyPermissionProgress(state: Int) { + // TODO validate state is valid + notifyToServer( + state, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN) + } + + /** + * Notifies system server that we are handling a particular state during the consent flow. + * + * Only used for emitting atoms. + * + * @param state The state that SystemUI is handling during the consent flow. Must be a valid + * state defined in the MediaProjectionState enum. + * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. + * Indicates the entry point for requesting the permission. Must be a valid state defined in + * the SessionCreationSource enum. + */ + private fun notifyToServer(state: Int, sessionCreationSource: Int) { + Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") + try { + service.notifyPermissionRequestStateChange( + Process.myUid(), state, sessionCreationSource) + } catch (e: RemoteException) { + Log.e( + TAG, + "Error notifying server of permission flow state $state from source $sessionCreationSource", + e) + } + } + + companion object { + const val TAG = "MediaProjectionMetricsLogger" + } +} 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 72aea040ba05..2217509167ef 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -33,6 +33,8 @@ import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnail import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider +import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProvider +import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProviderImpl import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity @@ -106,6 +108,8 @@ interface MediaProjectionAppSelectorModule { impl: TaskPreviewSizeProvider ): DefaultLifecycleObserver + @Binds fun windowMetricsProvider(impl: WindowMetricsProviderImpl): WindowMetricsProvider + companion object { @Provides @MediaProjectionAppSelector diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index e61650fbb163..fced117a8132 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -20,10 +20,15 @@ import android.content.ComponentName import android.os.UserHandle import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @MediaProjectionAppSelectorScope @@ -36,7 +41,8 @@ constructor( @HostUserHandle private val hostUserHandle: UserHandle, @MediaProjectionAppSelector private val scope: CoroutineScope, @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName, - @MediaProjectionAppSelector private val callerPackageName: String? + @MediaProjectionAppSelector private val callerPackageName: String?, + private val thumbnailLoader: RecentTaskThumbnailLoader, ) { fun init() { @@ -46,6 +52,11 @@ constructor( val tasks = recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks() + // Thumbnails are not fresh for the foreground task(s). They are only refreshed at + // launch, going to home, or going to overview. + // For this reason, we need to refresh them here. + refreshForegroundTaskThumbnails(tasks) + view.bind(tasks) } } @@ -54,6 +65,16 @@ constructor( scope.cancel() } + private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) { + coroutineScope { + val thumbnails: List<Deferred<ThumbnailData?>> = + tasks + .filter { it.isForegroundTask } + .map { async { thumbnailLoader.captureThumbnail(it.taskId) } } + thumbnails.forEach { thumbnail -> thumbnail.await() } + } + } + /** Removes all recent tasks that should be blocked according to the policy */ private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = filter { devicePolicyResolver.isScreenCaptureAllowed( diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index 41e22860d0ad..a9e6c53b3bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -25,5 +25,6 @@ data class RecentTask( @UserIdInt val userId: Int, val topActivityComponent: ComponentName?, val baseIntentComponent: ComponentName?, - @ColorInt val colorBackground: Int? + @ColorInt val colorBackground: Int?, + val isForegroundTask: Boolean, ) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 01398cf81314..aa4c4e55c718 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -48,9 +48,14 @@ constructor( override suspend fun loadRecentTasks(): List<RecentTask> = withContext(coroutineDispatcher) { - val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() - - rawRecentTasks + val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() + // Note: the returned task list is from the most-recent to least-recent order. + // The last foreground task is at index 1, because at index 0 will be our app selector. + val foregroundGroup = groupedTasks.elementAtOrNull(1) + val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId + val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId + val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2) + groupedTasks .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) } .map { RecentTask( @@ -58,7 +63,8 @@ constructor( it.userId, it.topActivity, it.baseIntent?.component, - it.taskDescription?.backgroundColor + it.taskDescription?.backgroundColor, + isForegroundTask = it.taskId in foregroundTaskIds ) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt index 47faaed10302..ccf272cbd3c2 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt @@ -25,6 +25,8 @@ import kotlinx.coroutines.withContext interface RecentTaskThumbnailLoader { suspend fun loadThumbnail(taskId: Int): ThumbnailData? + + suspend fun captureThumbnail(taskId: Int): ThumbnailData? } class ActivityTaskManagerThumbnailLoader @@ -36,8 +38,13 @@ constructor( override suspend fun loadThumbnail(taskId: Int): ThumbnailData? = withContext(coroutineDispatcher) { - val thumbnailData = - activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false) - if (thumbnailData.thumbnail == null) null else thumbnailData + activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false).takeIf { + it.thumbnail != null + } + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? = + withContext(coroutineDispatcher) { + activityManager.takeTaskThumbnail(taskId).takeIf { it.thumbnail != null } } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt index 864d35af41b4..c829471f53f3 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt @@ -19,8 +19,6 @@ package com.android.systemui.mediaprojection.appselector.view import android.content.Context import android.content.res.Configuration import android.graphics.Rect -import android.view.WindowInsets.Type -import android.view.WindowManager import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope @@ -36,7 +34,7 @@ class TaskPreviewSizeProvider @Inject constructor( private val context: Context, - private val windowManager: WindowManager, + private val windowMetricsProvider: WindowMetricsProvider, private val configurationController: ConfigurationController, ) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener, DefaultLifecycleObserver { @@ -62,17 +60,14 @@ constructor( } private fun calculateSize(): Rect { - val windowMetrics = windowManager.maximumWindowMetrics - val maximumWindowHeight = windowMetrics.bounds.height() - val width = windowMetrics.bounds.width() + val maxWindowBounds = windowMetricsProvider.maximumWindowBounds + val maximumWindowHeight = maxWindowBounds.height() + val width = maxWindowBounds.width() var height = maximumWindowHeight val isLargeScreen = isLargeScreen(context) if (isLargeScreen) { - val taskbarSize = - windowManager.currentWindowMetrics.windowInsets - .getInsets(Type.tappableElement()) - .bottom + val taskbarSize = windowMetricsProvider.currentWindowInsets.bottom height -= taskbarSize } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt new file mode 100644 index 000000000000..193292032868 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt @@ -0,0 +1,30 @@ +package com.android.systemui.mediaprojection.appselector.view + +import android.graphics.Insets +import android.graphics.Rect +import android.view.WindowInsets +import android.view.WindowManager +import javax.inject.Inject + +/** Provides values related to window metrics. */ +interface WindowMetricsProvider { + + val maximumWindowBounds: Rect + + val currentWindowInsets: Insets +} + +class WindowMetricsProviderImpl +@Inject +constructor( + private val windowManager: WindowManager, +) : WindowMetricsProvider { + override val maximumWindowBounds: Rect + get() = windowManager.maximumWindowMetrics.bounds + + override val currentWindowInsets: Insets + get() = + windowManager.currentWindowMetrics.windowInsets.getInsets( + WindowInsets.Type.tappableElement() + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt index 2f10ad3e6486..b9bafd4926fa 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt @@ -89,15 +89,15 @@ class MediaProjectionPermissionDialog( } return listOf( ScreenShareOption( - mode = ENTIRE_SCREEN, - spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, - warningText = entireScreenWarningText - ), - ScreenShareOption( mode = SINGLE_APP, spinnerText = R.string.screen_share_permission_dialog_option_single_app, warningText = singleAppWarningText, spinnerDisabledText = singleAppDisabledText, + ), + ScreenShareOption( + mode = ENTIRE_SCREEN, + spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, + warningText = entireScreenWarningText ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt index 37e8d9f26ee3..9bd57832c4df 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt @@ -23,8 +23,8 @@ import kotlin.annotation.Retention @IntDef(ENTIRE_SCREEN, SINGLE_APP) annotation class ScreenShareMode -const val ENTIRE_SCREEN = 0 -const val SINGLE_APP = 1 +const val SINGLE_APP = 0 +const val ENTIRE_SCREEN = 1 class ScreenShareOption( @ScreenShareMode val mode: Int, diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 07846b56d784..3cdcb2c4f550 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java @@ -24,6 +24,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.system.QuickStepContract; +import dalvik.annotation.optimization.NeverCompile; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -108,6 +110,7 @@ public class SysUiState implements Dumpable { } } + @NeverCompile @Override public void dump(PrintWriter pw, String[] args) { pw.println("SysUiState state:"); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 564e984fbce2..2928cceb35aa 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -65,6 +65,8 @@ import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; +import dalvik.annotation.optimization.NeverCompile; + import java.io.PrintWriter; import java.util.Optional; @@ -476,6 +478,7 @@ public class NavigationBarControllerImpl implements return mNavigationBars.get(mDisplayTracker.getDefaultDisplayId()); } + @NeverCompile @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mIsLargeScreen=" + mIsLargeScreen); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 4d6d95a05b1b..bc4f7f2513ce 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -584,7 +584,6 @@ public class NavigationBarView extends FrameLayout { if (!visible) { mTransitionListener.onBackAltCleared(); } - mRotationButtonController.getRotationButton().setCanShowRotationButton(!visible); } void setDisabledFlags(int disabledFlags, SysUiState sysUiState) { diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt index c567d56fd424..10a2b3ce7b85 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt @@ -31,10 +31,10 @@ import androidx.lifecycle.Lifecycle.State.CREATED import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.people.PeopleSpaceTileView import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.res.R import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -101,10 +101,10 @@ object PeopleViewBinder { view, priorityTiles, recentTiles, - viewModel::onTileClicked, + viewModel.onTileClicked, ) } else { - setNoConversationsContent(view, viewModel::onUserJourneyCancelled) + setNoConversationsContent(view, viewModel.onUserJourneyCancelled) } } } diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt index ed7c21b787ca..b847e9523b71 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt @@ -23,35 +23,32 @@ import android.content.Intent import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.people.PeopleSpaceUtils import com.android.systemui.people.PeopleTileViewHelper import com.android.systemui.people.data.model.PeopleTileModel import com.android.systemui.people.data.repository.PeopleTileRepository import com.android.systemui.people.data.repository.PeopleWidgetRepository +import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +private const val TAG = "PeopleViewModel" + /** * Models UI state for the people space, allowing the user to select which conversation should be * associated to a new or existing Conversation widget. */ class PeopleViewModel( - @Application private val context: Context, - private val tileRepository: PeopleTileRepository, - private val widgetRepository: PeopleWidgetRepository, -) : ViewModel() { /** * The list of the priority tiles/conversations. * * Important: Even though this is a Flow, the underlying API used to populate this Flow is not * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ - private val _priorityTiles = MutableStateFlow(priorityTiles()) - val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow() + val priorityTiles: StateFlow<List<PeopleTileViewModel>>, /** * The list of the priority tiles/conversations. @@ -59,104 +56,133 @@ class PeopleViewModel( * Important: Even though this is a Flow, the underlying API used to populate this Flow is not * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ - private val _recentTiles = MutableStateFlow(recentTiles()) - val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow() + val recentTiles: StateFlow<List<PeopleTileViewModel>>, /** The ID of the widget currently being edited/added. */ - private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) - val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow() + val appWidgetId: StateFlow<Int>, /** The result of this user journey. */ - private val _result = MutableStateFlow<Result?>(null) - val result: StateFlow<Result?> = _result.asStateFlow() + val result: StateFlow<Result?>, /** Refresh the [priorityTiles] and [recentTiles]. */ - fun onTileRefreshRequested() { - _priorityTiles.value = priorityTiles() - _recentTiles.value = recentTiles() - } + val onTileRefreshRequested: () -> Unit, /** Called when the [appWidgetId] should be changed to [widgetId]. */ - fun onWidgetIdChanged(widgetId: Int) { - _appWidgetId.value = widgetId - } + val onWidgetIdChanged: (widgetId: Int) -> Unit, /** Clear [result], setting it to null. */ - fun clearResult() { - _result.value = null - } + val clearResult: () -> Unit, /** Called when a tile is clicked. */ - fun onTileClicked(tile: PeopleTileViewModel) { - val widgetId = _appWidgetId.value - if (PeopleSpaceUtils.DEBUG) { - Log.d( - TAG, - "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId" - ) + val onTileClicked: (tile: PeopleTileViewModel) -> Unit, + + /** Called when this user journey is cancelled. */ + val onUserJourneyCancelled: () -> Unit, +) : ViewModel() { + /** The Factory that should be used to create a [PeopleViewModel]. */ + class Factory + @Inject + constructor( + @Application private val context: Context, + private val tileRepository: PeopleTileRepository, + private val widgetRepository: PeopleWidgetRepository, + ) : ViewModelProvider.Factory { + override fun <T : ViewModel> create(modelClass: Class<T>): T { + check(modelClass == PeopleViewModel::class.java) + return PeopleViewModel(context, tileRepository, widgetRepository) as T } - widgetRepository.setWidgetTile(widgetId, tile.key) - _result.value = - Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) }) } - /** Called when this user journey is cancelled. */ - fun onUserJourneyCancelled() { - _result.value = Result.Cancelled + sealed class Result { + class Success(val data: Intent) : Result() + + object Cancelled : Result() } +} - private fun priorityTiles(): List<PeopleTileViewModel> { +private fun PeopleViewModel( + @Application context: Context, + tileRepository: PeopleTileRepository, + widgetRepository: PeopleWidgetRepository, +): PeopleViewModel { + fun priorityTiles(): List<PeopleTileViewModel> { return try { - tileRepository.priorityTiles().map { it.toViewModel() } + tileRepository.priorityTiles().map { it.toViewModel(context) } } catch (e: Exception) { Log.e(TAG, "Couldn't retrieve priority conversations", e) emptyList() } } - private fun recentTiles(): List<PeopleTileViewModel> { + fun recentTiles(): List<PeopleTileViewModel> { return try { - tileRepository.recentTiles().map { it.toViewModel() } + tileRepository.recentTiles().map { it.toViewModel(context) } } catch (e: Exception) { Log.e(TAG, "Couldn't retrieve recent conversations", e) emptyList() } } - private fun PeopleTileModel.toViewModel(): PeopleTileViewModel { - val icon = - PeopleTileViewHelper.getPersonIconBitmap( - context, - this, - PeopleTileViewHelper.getSizeInDp( - context, - R.dimen.avatar_size_for_medium, - context.resources.displayMetrics.density, - ) - ) - return PeopleTileViewModel(key, icon, username) + val priorityTiles = MutableStateFlow(priorityTiles()) + val recentTiles = MutableStateFlow(recentTiles()) + val appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) + val result = MutableStateFlow<PeopleViewModel.Result?>(null) + + fun onTileRefreshRequested() { + priorityTiles.value = priorityTiles() + recentTiles.value = recentTiles() } - /** The Factory that should be used to create a [PeopleViewModel]. */ - class Factory - @Inject - constructor( - @Application private val context: Context, - private val tileRepository: PeopleTileRepository, - private val widgetRepository: PeopleWidgetRepository, - ) : ViewModelProvider.Factory { - override fun <T : ViewModel> create(modelClass: Class<T>): T { - check(modelClass == PeopleViewModel::class.java) - return PeopleViewModel(context, tileRepository, widgetRepository) as T - } + fun onWidgetIdChanged(widgetId: Int) { + appWidgetId.value = widgetId } - sealed class Result { - class Success(val data: Intent) : Result() - object Cancelled : Result() + fun clearResult() { + result.value = null } - companion object { - private const val TAG = "PeopleViewModel" + fun onTileClicked(tile: PeopleTileViewModel) { + val widgetId = appWidgetId.value + if (PeopleSpaceUtils.DEBUG) { + Log.d( + TAG, + "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId" + ) + } + widgetRepository.setWidgetTile(widgetId, tile.key) + result.value = + PeopleViewModel.Result.Success( + Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) } + ) } + + fun onUserJourneyCancelled() { + result.value = PeopleViewModel.Result.Cancelled + } + + return PeopleViewModel( + priorityTiles = priorityTiles.asStateFlow(), + recentTiles = recentTiles.asStateFlow(), + appWidgetId = appWidgetId.asStateFlow(), + result = result.asStateFlow(), + onTileRefreshRequested = ::onTileRefreshRequested, + onWidgetIdChanged = ::onWidgetIdChanged, + clearResult = ::clearResult, + onTileClicked = ::onTileClicked, + onUserJourneyCancelled = ::onUserJourneyCancelled, + ) +} + +fun PeopleTileModel.toViewModel(@Application context: Context): PeopleTileViewModel { + val icon = + PeopleTileViewHelper.getPersonIconBitmap( + context, + this, + PeopleTileViewHelper.getSizeInDp( + context, + R.dimen.avatar_size_for_medium, + context.resources.displayMetrics.density, + ) + ) + return PeopleTileViewModel(key, icon, username) } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt index d949a2a0afe5..67d390d4f10d 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.asIndenting +import com.android.systemui.util.annotations.WeaklyReferencedCallback import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.withIncreasedIndent import java.io.PrintWriter @@ -144,6 +145,7 @@ class PrivacyConfig @Inject constructor( ipw.flush() } + @WeaklyReferencedCallback interface Callback { fun onFlagMicCameraChanged(flag: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index 202254bb323f..4aad6a069cbb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -72,6 +72,8 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.util.Utils; +import dalvik.annotation.optimization.NeverCompile; + import java.io.PrintWriter; import java.util.Arrays; import java.util.function.Consumer; @@ -934,6 +936,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl return mListeningAndVisibilityLifecycleOwner; } + @NeverCompile @Override public void dump(PrintWriter pw, String[] args) { IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ " "); diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java index 7794fa071f45..eb11568614b7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java @@ -20,15 +20,14 @@ import android.database.ContentObserver; import android.os.Handler; import com.android.systemui.statusbar.policy.Listenable; -import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SettingsProxy; import com.android.systemui.util.settings.SystemSettings; /** - * Helper for managing secure, global, and system settings through use of {@link SettingsProxy}, - * which is the common superclass of {@link SecureSettings}, {@link GlobalSettings}, and - * {@link SystemSettings}. + * Helper for managing global settings through use of {@link SettingsProxy}. This should + * <em>not</em> be used for {@link SecureSettings} or {@link SystemSettings} since those must be + * user-aware (instead, use {@link UserSettingObserver}). */ public abstract class SettingObserver extends ContentObserver implements Listenable { private final SettingsProxy mSettingsProxy; @@ -36,23 +35,20 @@ public abstract class SettingObserver extends ContentObserver implements Listena private final int mDefaultValue; private boolean mListening; - private int mUserId; private int mObservedValue; protected abstract void handleValueChanged(int value, boolean observedChange); - public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName, - int userId) { - this(settingsProxy, handler, settingName, userId, 0); + public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) { + this(settingsProxy, handler, settingName, 0); } public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName, - int userId, int defaultValue) { + int defaultValue) { super(handler); mSettingsProxy = settingsProxy; mSettingName = settingName; mObservedValue = mDefaultValue = defaultValue; - mUserId = userId; } public int getValue() { @@ -65,11 +61,11 @@ public abstract class SettingObserver extends ContentObserver implements Listena * @param value The new value for the setting. */ public void setValue(int value) { - mSettingsProxy.putIntForUser(mSettingName, value, mUserId); + mSettingsProxy.putInt(mSettingName, value); } private int getValueFromProvider() { - return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId); + return mSettingsProxy.getInt(mSettingName, mDefaultValue); } @Override @@ -78,8 +74,8 @@ public abstract class SettingObserver extends ContentObserver implements Listena mListening = listening; if (listening) { mObservedValue = getValueFromProvider(); - mSettingsProxy.registerContentObserverForUser( - mSettingsProxy.getUriFor(mSettingName), false, this, mUserId); + mSettingsProxy.registerContentObserver( + mSettingsProxy.getUriFor(mSettingName), false, this); } else { mSettingsProxy.unregisterContentObserver(this); mObservedValue = mDefaultValue; @@ -94,21 +90,6 @@ public abstract class SettingObserver extends ContentObserver implements Listena handleValueChanged(value, changed); } - /** - * Set user handle for which to observe the setting. - */ - public void setUserId(int userId) { - mUserId = userId; - if (mListening) { - setListening(false); - setListening(true); - } - } - - public int getCurrentUser() { - return mUserId; - } - public String getKey() { return mSettingName; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index 11759f7ce06b..777faeacadd8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -19,7 +19,7 @@ package com.android.systemui.qs import android.content.Context import android.util.AttributeSet import com.android.systemui.flags.Flags -import com.android.systemui.flags.ViewRefactorFlag +import com.android.systemui.flags.RefactorFlag import com.android.systemui.res.R open class SideLabelTileLayout( @@ -27,8 +27,8 @@ open class SideLabelTileLayout( attrs: AttributeSet? ) : TileLayout(context, attrs) { - private final val isSmallLandscapeLockscreenEnabled = - ViewRefactorFlag(flag = Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled + private val isSmallLandscapeLockscreenEnabled = + RefactorFlag.forView(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled override fun updateResources(): Boolean { return super.updateResources().also { diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 9ba16454aea6..9d4eba5aafc2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -15,13 +15,13 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.systemui.FontSizeUtils; -import com.android.systemui.flags.ViewRefactorFlag; -import com.android.systemui.res.R; import com.android.systemui.flags.Flags; +import com.android.systemui.flags.RefactorFlag; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import com.android.systemui.qs.tileimpl.HeightOverrideable; import com.android.systemui.qs.tileimpl.QSTileViewImplKt; +import com.android.systemui.res.R; import java.util.ArrayList; @@ -55,7 +55,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { protected int mLastTileBottom; protected TextView mTempTextView; private final Boolean mIsSmallLandscapeLockscreenEnabled = - new ViewRefactorFlag(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled(); + RefactorFlag.forView(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled(); public TileLayout(Context context) { this(context, null); diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java new file mode 100644 index 000000000000..539c2d6e64da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java @@ -0,0 +1,117 @@ +/* + * 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.systemui.qs; + +import android.database.ContentObserver; +import android.os.Handler; + +import com.android.systemui.statusbar.policy.Listenable; +import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.util.settings.SystemSettings; +import com.android.systemui.util.settings.UserSettingsProxy; + +/** + * Helper for managing secure and system settings through use of {@link UserSettingsProxy}, + * which is the common superclass of {@link SecureSettings} and {@link SystemSettings}. + */ +public abstract class UserSettingObserver extends ContentObserver implements Listenable { + private final UserSettingsProxy mSettingsProxy; + private final String mSettingName; + private final int mDefaultValue; + + private boolean mListening; + private int mUserId; + private int mObservedValue; + + protected abstract void handleValueChanged(int value, boolean observedChange); + + public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName, + int userId) { + this(settingsProxy, handler, settingName, userId, 0); + } + + public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName, + int userId, int defaultValue) { + super(handler); + mSettingsProxy = settingsProxy; + mSettingName = settingName; + mObservedValue = mDefaultValue = defaultValue; + mUserId = userId; + } + + public int getValue() { + return mListening ? mObservedValue : getValueFromProvider(); + } + + /** + * Set the value of the observed setting. + * + * @param value The new value for the setting. + */ + public void setValue(int value) { + mSettingsProxy.putIntForUser(mSettingName, value, mUserId); + } + + private int getValueFromProvider() { + return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId); + } + + @Override + public void setListening(boolean listening) { + if (listening == mListening) return; + mListening = listening; + if (listening) { + mObservedValue = getValueFromProvider(); + mSettingsProxy.registerContentObserverForUser( + mSettingsProxy.getUriFor(mSettingName), false, this, mUserId); + } else { + mSettingsProxy.unregisterContentObserver(this); + mObservedValue = mDefaultValue; + } + } + + @Override + public void onChange(boolean selfChange) { + final int value = getValueFromProvider(); + final boolean changed = value != mObservedValue; + mObservedValue = value; + handleValueChanged(value, changed); + } + + /** + * Set user handle for which to observe the setting. + */ + public void setUserId(int userId) { + mUserId = userId; + if (mListening) { + setListening(false); + setListening(true); + } + } + + public int getCurrentUser() { + return mUserId; + } + + public String getKey() { + return mSettingName; + } + + public boolean isListening() { + return mListening; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 2469a98140e3..78f2da53cd43 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -17,6 +17,7 @@ package com.android.systemui.qs.external; import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; +import android.app.ActivityManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -35,6 +36,7 @@ import android.os.UserHandle; import android.service.quicksettings.IQSService; import android.service.quicksettings.IQSTileService; import android.service.quicksettings.TileService; +import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; @@ -81,7 +83,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements // Bind retry control. private static final int MAX_BIND_RETRIES = 5; - private static final int DEFAULT_BIND_RETRY_DELAY = 1000; + private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS; + private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS; // Shared prefs that hold tile lifecycle info. private static final String TILES = "tiles_prefs"; @@ -94,6 +97,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final IBinder mToken = new Binder(); private final PackageManagerAdapter mPackageManagerAdapter; private final BroadcastDispatcher mBroadcastDispatcher; + private final ActivityManager mActivityManager; private Set<Integer> mQueuedMessages = new ArraySet<>(); @Nullable @@ -102,7 +106,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements private IBinder mClickBinder; private int mBindTryCount; - private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; + private long mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; + private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false); private AtomicBoolean mBound = new AtomicBoolean(false); private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); @@ -115,7 +120,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @AssistedInject TileLifecycleManager(@Main Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, - @Assisted Intent intent, @Assisted UserHandle user, + @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager, @Background DelayableExecutor executor) { mContext = context; mHandler = handler; @@ -126,6 +131,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements mExecutor = executor; mPackageManagerAdapter = packageManagerAdapter; mBroadcastDispatcher = broadcastDispatcher; + mActivityManager = activityManager; if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); } @@ -152,10 +158,6 @@ public class TileLifecycleManager extends BroadcastReceiver implements } } - public void setBindRetryDelay(int delayMs) { - mBindRetryDelay = delayMs; - } - public boolean isActiveTile() { try { ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), @@ -250,19 +252,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements private boolean bindServices() { String packageName = mIntent.getComponent().getPackageName(); + int flags = Context.BIND_AUTO_CREATE + | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE + | Context.BIND_WAIVE_PRIORITY; if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, mUser)) { - return mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE - | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE - | Context.BIND_WAIVE_PRIORITY, - mUser); + return mContext.bindServiceAsUser(mIntent, this, flags, mUser); } return mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE - | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE - | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS - | Context.BIND_WAIVE_PRIORITY, + flags | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, mUser); } @@ -352,8 +350,32 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (!mBound.get()) return; if (DEBUG) Log.d(TAG, "handleDeath"); if (checkComponentState()) { - mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay); + if (isDeathRebindScheduled.compareAndSet(false, true)) { + mExecutor.executeDelayed(() -> { + setBindService(true); + isDeathRebindScheduled.set(false); + }, getRebindDelay()); + } + } + } + + /** + * @return the delay to automatically rebind after a service died. It provides a longer delay if + * the device is a low memory state because the service is likely to get killed again by the + * system. In this case we want to rebind later and not to cause a loop of a frequent rebinds. + */ + private long getRebindDelay() { + final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); + mActivityManager.getMemoryInfo(info); + + final long delay; + if (info.lowMemory) { + delay = LOW_MEMORY_BIND_RETRY_DELAY; + } else { + delay = mBindRetryDelay; } + Log.i(TAG, "Rebinding with a delay=" + delay); + return delay; } private boolean checkComponentState() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 941a9d6dc952..3ee4a1be40b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -75,13 +75,12 @@ public class TileServiceManager { private boolean mStarted = false; TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, - BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, - CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { + UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory, + CustomTileAddedRepository customTileAddedRepository) { this(tileServices, handler, userTracker, customTileAddedRepository, - new TileLifecycleManager(handler, tileServices.getContext(), tileServices, - new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, + tileLifecycleManagerFactory.create( new Intent(TileService.ACTION_QS_TILE).setComponent(component), - userTracker.getUserHandle(), executor)); + userTracker.getUserHandle())); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index acee8e9ad2eb..c3744df5faeb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -81,6 +81,7 @@ public class TileServices extends IQSService.Stub { private final UserTracker mUserTracker; private final StatusBarIconController mStatusBarIconController; private final PanelInteractor mPanelInteractor; + private final TileLifecycleManager.Factory mTileLifecycleManagerFactory; private final CustomTileAddedRepository mCustomTileAddedRepository; private final DelayableExecutor mBackgroundExecutor; @@ -96,6 +97,7 @@ public class TileServices extends IQSService.Stub { CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, + TileLifecycleManager.Factory tileLifecycleManagerFactory, CustomTileAddedRepository customTileAddedRepository, @Background DelayableExecutor backgroundExecutor) { mHost = host; @@ -109,6 +111,7 @@ public class TileServices extends IQSService.Stub { mStatusBarIconController = statusBarIconController; mCommandQueue.addCallback(mRequestListeningCallback); mPanelInteractor = panelInteractor; + mTileLifecycleManagerFactory = tileLifecycleManagerFactory; mCustomTileAddedRepository = customTileAddedRepository; mBackgroundExecutor = backgroundExecutor; } @@ -137,8 +140,8 @@ public class TileServices extends IQSService.Stub { protected TileServiceManager onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher) { - return new TileServiceManager(this, mHandlerProvider.get(), component, - broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor); + return new TileServiceManager(this, mHandlerProvider.get(), component, mUserTracker, + mTileLifecycleManagerFactory, mCustomTileAddedRepository); } public void freeService(CustomTileInterface tile, TileServiceManager service) { @@ -323,7 +326,7 @@ public class TileServices extends IQSService.Stub { if (info.applicationInfo.isSystemApp()) { final StatusBarIcon statusIcon = icon != null ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, - contentDescription) + contentDescription) : null; final String slot = getStatusBarIconSlotName(componentName); mMainHandler.post(new Runnable() { @@ -356,11 +359,11 @@ public class TileServices extends IQSService.Stub { synchronized (mServices) { mTokenMap.forEach((iBinder, customTile) -> sb.append(iBinder.toString()) - .append(":") - .append(customTile.getComponent().flattenToShortString()) - .append(":") - .append(customTile.getUser()) - .append(",")); + .append(":") + .append(customTile.getComponent().flattenToShortString()) + .append(":") + .append(customTile.getUser()) + .append(",")); } sb.append("]"); return sb.toString(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 64fa33ce8118..eff3e76f43be 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -33,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor +import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.res.R import com.android.systemui.util.icuMessageFormat import javax.inject.Inject @@ -47,17 +48,35 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +private const val TAG = "FooterActionsViewModel" + /** A ViewModel for the footer actions. */ class FooterActionsViewModel( - @Application appContext: Context, - private val footerActionsInteractor: FooterActionsInteractor, - private val falsingManager: FalsingManager, - private val globalActionsDialogLite: GlobalActionsDialogLite, - showPowerButton: Boolean, -) { - /** The context themed with the Quick Settings colors. */ - private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings) + /** The model for the security button. */ + val security: Flow<FooterActionsSecurityButtonViewModel?>, + + /** The model for the foreground services button. */ + val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?>, + /** The model for the user switcher button. */ + val userSwitcher: Flow<FooterActionsButtonViewModel?>, + + /** The model for the settings button. */ + val settings: FooterActionsButtonViewModel, + + /** The model for the power button. */ + val power: FooterActionsButtonViewModel?, + + /** + * Observe the device monitoring dialog requests and show the dialog accordingly. This function + * will suspend indefinitely and will need to be cancelled to stop observing. + * + * Important: [quickSettingsContext] must be the [Context] associated to the + * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this + * function must be cancelled when that fragment is destroyed. + */ + val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit, +) { /** * Whether the UI rendering this ViewModel should be visible. Note that even when this is false, * the UI should still participate to the layout it is included in (i.e. in the View world it @@ -74,107 +93,6 @@ class FooterActionsViewModel( private val _backgroundAlpha = MutableStateFlow(1f) val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow() - /** The model for the security button. */ - val security: Flow<FooterActionsSecurityButtonViewModel?> = - footerActionsInteractor.securityButtonConfig - .map { config -> - val (icon, text, isClickable) = config ?: return@map null - FooterActionsSecurityButtonViewModel( - icon, - text, - if (isClickable) this::onSecurityButtonClicked else null, - ) - } - .distinctUntilChanged() - - /** The model for the foreground services button. */ - val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> = - combine( - footerActionsInteractor.foregroundServicesCount, - footerActionsInteractor.hasNewForegroundServices, - security, - ) { foregroundServicesCount, hasNewChanges, securityModel -> - if (foregroundServicesCount <= 0) { - return@combine null - } - - val text = - icuMessageFormat( - context.resources, - R.string.fgs_manager_footer_label, - foregroundServicesCount, - ) - FooterActionsForegroundServicesButtonViewModel( - foregroundServicesCount, - text = text, - displayText = securityModel == null, - hasNewChanges = hasNewChanges, - this::onForegroundServiceButtonClicked, - ) - } - .distinctUntilChanged() - - /** The model for the user switcher button. */ - val userSwitcher: Flow<FooterActionsButtonViewModel?> = - footerActionsInteractor.userSwitcherStatus - .map { userSwitcherStatus -> - when (userSwitcherStatus) { - UserSwitcherStatusModel.Disabled -> null - is UserSwitcherStatusModel.Enabled -> { - if (userSwitcherStatus.currentUserImage == null) { - Log.e( - TAG, - "Skipped the addition of user switcher button because " + - "currentUserImage is missing", - ) - return@map null - } - - userSwitcherButton(userSwitcherStatus) - } - } - } - .distinctUntilChanged() - - /** The model for the settings button. */ - val settings: FooterActionsButtonViewModel = - FooterActionsButtonViewModel( - id = R.id.settings_button_container, - Icon.Resource( - R.drawable.ic_settings, - ContentDescription.Resource(R.string.accessibility_quick_settings_settings) - ), - iconTint = - Utils.getColorAttrDefaultColor( - context, - R.attr.onShadeInactiveVariant, - ), - backgroundColor = R.attr.shadeInactive, - this::onSettingsButtonClicked, - ) - - /** The model for the power button. */ - val power: FooterActionsButtonViewModel? = - if (showPowerButton) { - FooterActionsButtonViewModel( - id = R.id.pm_lite, - Icon.Resource( - android.R.drawable.ic_lock_power_off, - ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) - ), - iconTint = - Utils.getColorAttrDefaultColor( - context, - R.attr.onShadeActive, - ), - backgroundColor = R.attr.shadeActive, - this::onPowerButtonClicked, - ) - } else { - null - } - - /** Called when the visibility of the UI rendering this model should be changed. */ fun onVisibilityChangeRequested(visible: Boolean) { _isVisible.value = visible } @@ -195,14 +113,52 @@ class FooterActionsViewModel( } } - /** - * Observe the device monitoring dialog requests and show the dialog accordingly. This function - * will suspend indefinitely and will need to be cancelled to stop observing. - * - * Important: [quickSettingsContext] must be the [Context] associated to the - * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this - * function must be cancelled when that fragment is destroyed. - */ + @SysUISingleton + class Factory + @Inject + constructor( + @Application private val context: Context, + private val falsingManager: FalsingManager, + private val footerActionsInteractor: FooterActionsInteractor, + private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, + @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean, + ) { + /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */ + fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { + val globalActionsDialogLite = globalActionsDialogLiteProvider.get() + if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) { + // This should usually not happen, but let's make sure we already destroy + // globalActionsDialogLite. + globalActionsDialogLite.destroy() + } else { + // Destroy globalActionsDialogLite when the lifecycle is destroyed. + lifecycleOwner.lifecycle.addObserver( + object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + globalActionsDialogLite.destroy() + } + } + ) + } + + return FooterActionsViewModel( + context, + footerActionsInteractor, + falsingManager, + globalActionsDialogLite, + showPowerButton, + ) + } + } +} + +fun FooterActionsViewModel( + @Application appContext: Context, + footerActionsInteractor: FooterActionsInteractor, + falsingManager: FalsingManager, + globalActionsDialogLite: GlobalActionsDialogLite, + showPowerButton: Boolean, +): FooterActionsViewModel { suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) { footerActionsInteractor.deviceMonitoringDialogRequests.collect { footerActionsInteractor.showDeviceMonitoringDialog( @@ -212,7 +168,7 @@ class FooterActionsViewModel( } } - private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) { + fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -220,7 +176,7 @@ class FooterActionsViewModel( footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable) } - private fun onForegroundServiceButtonClicked(expandable: Expandable) { + fun onForegroundServiceButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -228,7 +184,7 @@ class FooterActionsViewModel( footerActionsInteractor.showForegroundServicesDialog(expandable) } - private fun onUserSwitcherClicked(expandable: Expandable) { + fun onUserSwitcherClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -236,7 +192,7 @@ class FooterActionsViewModel( footerActionsInteractor.showUserSwitcher(expandable) } - private fun onSettingsButtonClicked(expandable: Expandable) { + fun onSettingsButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -244,7 +200,7 @@ class FooterActionsViewModel( footerActionsInteractor.showSettings(expandable) } - private fun onPowerButtonClicked(expandable: Expandable) { + fun onPowerButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -252,71 +208,179 @@ class FooterActionsViewModel( footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable) } - private fun userSwitcherButton( - status: UserSwitcherStatusModel.Enabled - ): FooterActionsButtonViewModel { - val icon = status.currentUserImage!! - - return FooterActionsButtonViewModel( - id = R.id.multi_user_switch, - icon = - Icon.Loaded( - icon, - ContentDescription.Loaded( - userSwitcherContentDescription(status.currentUserName) - ), - ), - iconTint = null, - backgroundColor = R.attr.shadeInactive, - onClick = this::onUserSwitcherClicked, - ) - } + val qsThemedContext = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings) - private fun userSwitcherContentDescription(currentUser: String?): String? { - return currentUser?.let { user -> - context.getString(R.string.accessibility_quick_settings_user, user) - } - } + val security = + footerActionsInteractor.securityButtonConfig + .map { config -> + config?.let { securityButtonViewModel(it, ::onSecurityButtonClicked) } + } + .distinctUntilChanged() - @SysUISingleton - class Factory - @Inject - constructor( - @Application private val context: Context, - private val falsingManager: FalsingManager, - private val footerActionsInteractor: FooterActionsInteractor, - private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, - @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean, - ) { - /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */ - fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { - val globalActionsDialogLite = globalActionsDialogLiteProvider.get() - if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) { - // This should usually not happen, but let's make sure we already destroy - // globalActionsDialogLite. - globalActionsDialogLite.destroy() - } else { - // Destroy globalActionsDialogLite when the lifecycle is destroyed. - lifecycleOwner.lifecycle.addObserver( - object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { - globalActionsDialogLite.destroy() + val foregroundServices = + combine( + footerActionsInteractor.foregroundServicesCount, + footerActionsInteractor.hasNewForegroundServices, + security, + ) { foregroundServicesCount, hasNewChanges, securityModel -> + if (foregroundServicesCount <= 0) { + return@combine null + } + + foregroundServicesButtonViewModel( + qsThemedContext, + foregroundServicesCount, + securityModel, + hasNewChanges, + ::onForegroundServiceButtonClicked, + ) + } + .distinctUntilChanged() + + val userSwitcher = + footerActionsInteractor.userSwitcherStatus + .map { userSwitcherStatus -> + when (userSwitcherStatus) { + UserSwitcherStatusModel.Disabled -> null + is UserSwitcherStatusModel.Enabled -> { + if (userSwitcherStatus.currentUserImage == null) { + Log.e( + TAG, + "Skipped the addition of user switcher button because " + + "currentUserImage is missing", + ) + return@map null } + + userSwitcherButtonViewModel( + qsThemedContext, + userSwitcherStatus, + ::onUserSwitcherClicked + ) } - ) + } } + .distinctUntilChanged() - return FooterActionsViewModel( - context, - footerActionsInteractor, - falsingManager, - globalActionsDialogLite, - showPowerButton, - ) + val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked) + val power = + if (showPowerButton) { + powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked) + } else { + null } - } - companion object { - private const val TAG = "FooterActionsViewModel" + return FooterActionsViewModel( + security = security, + foregroundServices = foregroundServices, + userSwitcher = userSwitcher, + settings = settings, + power = power, + observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests, + ) +} + +fun securityButtonViewModel( + config: SecurityButtonConfig, + onSecurityButtonClicked: (Context, Expandable) -> Unit, +): FooterActionsSecurityButtonViewModel { + val (icon, text, isClickable) = config + return FooterActionsSecurityButtonViewModel( + icon, + text, + if (isClickable) onSecurityButtonClicked else null, + ) +} + +fun foregroundServicesButtonViewModel( + qsThemedContext: Context, + foregroundServicesCount: Int, + securityModel: FooterActionsSecurityButtonViewModel?, + hasNewChanges: Boolean, + onForegroundServiceButtonClicked: (Expandable) -> Unit, +): FooterActionsForegroundServicesButtonViewModel { + val text = + icuMessageFormat( + qsThemedContext.resources, + R.string.fgs_manager_footer_label, + foregroundServicesCount, + ) + + return FooterActionsForegroundServicesButtonViewModel( + foregroundServicesCount, + text = text, + displayText = securityModel == null, + hasNewChanges = hasNewChanges, + onForegroundServiceButtonClicked, + ) +} + +fun userSwitcherButtonViewModel( + qsThemedContext: Context, + status: UserSwitcherStatusModel.Enabled, + onUserSwitcherClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + val icon = status.currentUserImage!! + return FooterActionsButtonViewModel( + id = R.id.multi_user_switch, + icon = + Icon.Loaded( + icon, + ContentDescription.Loaded( + userSwitcherContentDescription(qsThemedContext, status.currentUserName) + ), + ), + iconTint = null, + backgroundColor = R.attr.shadeInactive, + onClick = onUserSwitcherClicked, + ) +} + +private fun userSwitcherContentDescription( + qsThemedContext: Context, + currentUser: String? +): String? { + return currentUser?.let { user -> + qsThemedContext.getString(R.string.accessibility_quick_settings_user, user) } } + +fun settingsButtonViewModel( + qsThemedContext: Context, + onSettingsButtonClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + return FooterActionsButtonViewModel( + id = R.id.settings_button_container, + Icon.Resource( + R.drawable.ic_settings, + ContentDescription.Resource(R.string.accessibility_quick_settings_settings) + ), + iconTint = + Utils.getColorAttrDefaultColor( + qsThemedContext, + R.attr.onShadeInactiveVariant, + ), + backgroundColor = R.attr.shadeInactive, + onSettingsButtonClicked, + ) +} + +fun powerButtonViewModel( + qsThemedContext: Context, + onPowerButtonClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + return FooterActionsButtonViewModel( + id = R.id.pm_lite, + Icon.Resource( + android.R.drawable.ic_lock_power_off, + ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) + ), + iconTint = + Utils.getColorAttrDefaultColor( + qsThemedContext, + R.attr.onShadeActive, + ), + backgroundColor = R.attr.shadeActive, + onPowerButtonClicked, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 5a9c0a1893f8..f8e01590d368 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -23,7 +23,10 @@ import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration import android.content.res.Resources.ID_NULL +import android.graphics.Color +import android.graphics.PorterDuff import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.os.Trace import android.service.quicksettings.Tile @@ -44,7 +47,6 @@ import android.widget.TextView import androidx.annotation.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils -import com.android.systemui.res.R import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.plugins.qs.QSIconView @@ -53,6 +55,7 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH +import com.android.systemui.res.R import java.util.Objects private const val TAG = "QSTileViewImpl" @@ -67,6 +70,7 @@ open class QSTileViewImpl @JvmOverloads constructor( private const val LABEL_NAME = "label" private const val SECONDARY_LABEL_NAME = "secondaryLabel" private const val CHEVRON_NAME = "chevron" + private const val OVERLAY_NAME = "overlay" const val UNAVAILABLE_ALPHA = 0.3f @VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_" @@ -97,6 +101,13 @@ open class QSTileViewImpl @JvmOverloads constructor( private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive) private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled) + private val overlayColorActive = Utils.applyAlpha( + /* alpha= */ 0.11f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)) + private val overlayColorInactive = Utils.applyAlpha( + /* alpha= */ 0.08f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)) + private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive) private val colorLabelUnavailable = @@ -123,8 +134,13 @@ open class QSTileViewImpl @JvmOverloads constructor( protected var showRippleEffect = true private lateinit var ripple: RippleDrawable - private lateinit var colorBackgroundDrawable: Drawable - private var paintColor: Int = 0 + private lateinit var backgroundDrawable: LayerDrawable + private lateinit var backgroundBaseDrawable: Drawable + private lateinit var backgroundOverlayDrawable: Drawable + + private var backgroundColor: Int = 0 + private var backgroundOverlayColor: Int = 0 + private val singleAnimator: ValueAnimator = ValueAnimator().apply { setDuration(QS_ANIM_LENGTH) addUpdateListener { animation -> @@ -134,7 +150,8 @@ open class QSTileViewImpl @JvmOverloads constructor( animation.getAnimatedValue(BACKGROUND_NAME) as Int, animation.getAnimatedValue(LABEL_NAME) as Int, animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int, - animation.getAnimatedValue(CHEVRON_NAME) as Int + animation.getAnimatedValue(CHEVRON_NAME) as Int, + animation.getAnimatedValue(OVERLAY_NAME) as Int, ) } } @@ -263,7 +280,12 @@ open class QSTileViewImpl @JvmOverloads constructor( fun createTileBackground(): Drawable { ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable - colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background) + backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable + backgroundBaseDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base) + backgroundOverlayDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay) + backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC) return ripple } @@ -343,10 +365,10 @@ open class QSTileViewImpl @JvmOverloads constructor( ripple.also { // In case that the colorBackgroundDrawable was used as the background, make sure // it has the correct callback instead of null - colorBackgroundDrawable.callback = it + backgroundDrawable.callback = it } } else { - colorBackgroundDrawable + backgroundDrawable } } @@ -512,7 +534,7 @@ open class QSTileViewImpl @JvmOverloads constructor( singleAnimator.setValues( colorValuesHolder( BACKGROUND_NAME, - paintColor, + backgroundColor, getBackgroundColorForState(state.state, state.disabledByPolicy) ), colorValuesHolder( @@ -529,6 +551,11 @@ open class QSTileViewImpl @JvmOverloads constructor( CHEVRON_NAME, chevronView.imageTintList?.defaultColor ?: 0, getChevronColorForState(state.state, state.disabledByPolicy) + ), + colorValuesHolder( + OVERLAY_NAME, + backgroundOverlayColor, + getOverlayColorForState(state.state) ) ) singleAnimator.start() @@ -537,7 +564,8 @@ open class QSTileViewImpl @JvmOverloads constructor( getBackgroundColorForState(state.state, state.disabledByPolicy), getLabelColorForState(state.state, state.disabledByPolicy), getSecondaryLabelColorForState(state.state, state.disabledByPolicy), - getChevronColorForState(state.state, state.disabledByPolicy) + getChevronColorForState(state.state, state.disabledByPolicy), + getOverlayColorForState(state.state) ) } } @@ -555,17 +583,19 @@ open class QSTileViewImpl @JvmOverloads constructor( backgroundColor: Int, labelColor: Int, secondaryLabelColor: Int, - chevronColor: Int + chevronColor: Int, + overlayColor: Int, ) { setColor(backgroundColor) setLabelColor(labelColor) setSecondaryLabelColor(secondaryLabelColor) setChevronColor(chevronColor) + setOverlayColor(overlayColor) } private fun setColor(color: Int) { - colorBackgroundDrawable.mutate().setTint(color) - paintColor = color + backgroundBaseDrawable.mutate().setTint(color) + backgroundColor = color } private fun setLabelColor(color: Int) { @@ -580,6 +610,11 @@ open class QSTileViewImpl @JvmOverloads constructor( chevronView.imageTintList = ColorStateList.valueOf(color) } + private fun setOverlayColor(overlayColor: Int) { + backgroundOverlayDrawable.setTint(overlayColor) + backgroundOverlayColor = overlayColor + } + private fun loadSideViewDrawableIfNecessary(state: QSTile.State) { if (state.sideViewCustomDrawable != null) { customDrawableView.setImageDrawable(state.sideViewCustomDrawable) @@ -654,9 +689,17 @@ open class QSTileViewImpl @JvmOverloads constructor( private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int = getSecondaryLabelColorForState(state, disabledByPolicy) + private fun getOverlayColorForState(state: Int): Int { + return when (state) { + Tile.STATE_ACTIVE -> overlayColorActive + Tile.STATE_INACTIVE -> overlayColorInactive + else -> Color.TRANSPARENT + } + } + @VisibleForTesting internal fun getCurrentColors(): List<Int> = listOf( - paintColor, + backgroundColor, label.currentTextColor, secondaryLabel.currentTextColor, chevronView.imageTintList?.defaultColor ?: 0 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index fb71cefe8e0a..17251c37473f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -36,7 +36,6 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -49,6 +48,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.GlobalSettings; @@ -56,8 +56,6 @@ import dagger.Lazy; import javax.inject.Inject; - - /** Quick settings tile: Airplane mode **/ public class AirplaneModeTile extends QSTileImpl<BooleanState> { @@ -90,8 +88,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { mBroadcastDispatcher = broadcastDispatcher; mLazyConnectivityManager = lazyConnectivityManager; - mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON, - userTracker.getUserId()) { + mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) { @Override protected void handleValueChanged(int value, boolean observedChange) { // mHandler is the background handler so calling this is OK diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index 18d472b9c755..426aa553f082 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -29,7 +29,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -38,9 +37,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.settings.SecureSettings; @@ -53,7 +53,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements private final BatteryController mBatteryController; @VisibleForTesting - protected final SettingObserver mSetting; + protected final UserSettingObserver mSetting; private int mLevel; private boolean mPowerSave; @@ -79,7 +79,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements mBatteryController = batteryController; mBatteryController.observe(getLifecycle(), this); int currentUser = host.getUserContext().getUserId(); - mSetting = new SettingObserver( + mSetting = new UserSettingObserver( secureSettings, mHandler, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 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 d862f563c8f9..18d2f306c247 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -39,7 +39,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -53,6 +52,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; import java.util.List; @@ -198,6 +198,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } state.expandedAccessibilityClassName = Switch.class.getName(); + state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG); } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java index ee57b2b62d49..c8adbfcf5487 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java @@ -28,8 +28,6 @@ import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; -import com.android.systemui.res.R.drawable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -38,9 +36,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -51,8 +50,8 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { public static final String TILE_SPEC = "color_correction"; - private final Icon mIcon = ResourceIcon.get(drawable.ic_qs_color_correction); - private final SettingObserver mSetting; + private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_color_correction); + private final UserSettingObserver mSetting; @Inject public ColorCorrectionTile( @@ -71,7 +70,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mSetting = new SettingObserver(secureSettings, mHandler, + mSetting = new UserSettingObserver(secureSettings, mHandler, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index 993ada6b86aa..c34a5842c1e3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -29,8 +29,6 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; -import com.android.systemui.res.R.drawable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -39,9 +37,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -51,7 +50,7 @@ import javax.inject.Inject; public class ColorInversionTile extends QSTileImpl<BooleanState> { public static final String TILE_SPEC = "inversion"; - private final SettingObserver mSetting; + private final UserSettingObserver mSetting; @Inject public ColorInversionTile( @@ -70,7 +69,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mSetting = new SettingObserver(secureSettings, mHandler, + mSetting = new UserSettingObserver(secureSettings, mHandler, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { @@ -126,8 +125,8 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_inversion_label); state.icon = ResourceIcon.get(state.value - ? drawable.qs_invert_colors_icon_on - : drawable.qs_invert_colors_icon_off); + ? R.drawable.qs_invert_colors_icon_on + : R.drawable.qs_invert_colors_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; } 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 0617b30eb8fa..f6518d1e8023 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -45,7 +45,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Prefs; -import com.android.systemui.res.R; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; @@ -56,10 +55,11 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.settings.SecureSettings; @@ -81,7 +81,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final ZenModeController mController; private final SharedPreferences mSharedPreferences; - private final SettingObserver mSettingZenDuration; + private final UserSettingObserver mSettingZenDuration; private final DialogLaunchAnimator mDialogLaunchAnimator; private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger; @@ -109,7 +109,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mSharedPreferences = sharedPreferences; mController.observe(getLifecycle(), mZenCallback); mDialogLaunchAnimator = dialogLaunchAnimator; - mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler, + mSettingZenDuration = new UserSettingObserver(secureSettings, mUiHandler, Settings.Secure.ZEN_DURATION, getHost().getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java index a08b3fca15aa..4f0a63b667f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -38,7 +38,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -49,9 +48,10 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -69,8 +69,8 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked); private final IDreamManager mDreamManager; private final BroadcastDispatcher mBroadcastDispatcher; - private final SettingObserver mEnabledSettingObserver; - private final SettingObserver mDreamSettingObserver; + private final UserSettingObserver mEnabledSettingObserver; + private final UserSettingObserver mDreamSettingObserver; private final UserTracker mUserTracker; private final boolean mDreamSupported; private final boolean mDreamOnlyEnabledForDockUser; @@ -111,14 +111,14 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { statusBarStateController, activityStarter, qsLogger); mDreamManager = dreamManager; mBroadcastDispatcher = broadcastDispatcher; - mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler, + mEnabledSettingObserver = new UserSettingObserver(secureSettings, mHandler, Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { refreshState(); } }; - mDreamSettingObserver = new SettingObserver(secureSettings, mHandler, + mDreamSettingObserver = new UserSettingObserver(secureSettings, mHandler, Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java index 78af97668fc0..b08e6a5580a2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java @@ -28,7 +28,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -37,9 +36,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.onehanded.OneHanded; @@ -53,7 +53,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { private final Icon mIcon = ResourceIcon.get( com.android.internal.R.drawable.ic_qs_one_handed_mode); - private final SettingObserver mSetting; + private final UserSettingObserver mSetting; @Inject public OneHandedModeTile( @@ -70,7 +70,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { SecureSettings secureSettings) { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mSetting = new SettingObserver(secureSettings, mHandler, + mSetting = new UserSettingObserver(secureSettings, mHandler, Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 5a9500494de0..f1d8f9f25286 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -36,7 +36,6 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -45,9 +44,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; @@ -67,7 +67,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements private final RotationLockController mController; private final SensorPrivacyManager mPrivacyManager; private final BatteryController mBatteryController; - private final SettingObserver mSetting; + private final UserSettingObserver mSetting; private final boolean mAllowRotationResolver; @Inject @@ -93,7 +93,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements mPrivacyManager = privacyManager; mBatteryController = batteryController; int currentUser = host.getUserContext().getUserId(); - mSetting = new SettingObserver( + mSetting = new UserSettingObserver( secureSettings, mHandler, Secure.CAMERA_AUTOROTATE, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 8ae2dc227998..80af76de23df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -102,9 +102,10 @@ constructor( showSeeAll: Boolean, showPairNewDevice: Boolean ) { - seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE - pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE - deviceItemAdapter.refreshDeviceItemList(deviceItem) + deviceItemAdapter.refreshDeviceItemList(deviceItem) { + seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE + pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE + } } internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) { @@ -173,8 +174,8 @@ constructor( internal fun getItem(position: Int) = asyncListDiffer.currentList[position] - internal fun refreshDeviceItemList(updated: List<DeviceItem>) { - asyncListDiffer.submitList(updated) + internal fun refreshDeviceItemList(updated: List<DeviceItem>, callback: () -> Unit) { + asyncListDiffer.submitList(updated, callback) } internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index 97e178371f2d..8e274932d4d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -113,7 +113,6 @@ constructor( .launchIn(this) deviceItemInteractor.deviceItemUpdate - .filterNotNull() .onEach { dialog!!.onDeviceItemUpdated( it.take(MAX_DEVICE_ITEM_ENTRY), diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt index 14d24f952c8d..e196c6c27c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt @@ -34,10 +34,10 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext @@ -55,10 +55,10 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, ) { - private val mutableDeviceItemUpdate: MutableStateFlow<List<DeviceItem>?> = - MutableStateFlow(null) + private val mutableDeviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = + MutableSharedFlow(extraBufferCapacity = 1) internal val deviceItemUpdate - get() = mutableDeviceItemUpdate.asStateFlow() + get() = mutableDeviceItemUpdate.asSharedFlow() internal val deviceItemUpdateRequest: SharedFlow<Unit> = conflatedCallbackFlow { @@ -119,16 +119,15 @@ constructor( internal suspend fun updateDeviceItems(context: Context) { withContext(backgroundDispatcher) { - val mostRecentlyConnectedDevices = bluetoothAdapter?.mostRecentlyConnectedDevices - - mutableDeviceItemUpdate.value = + mutableDeviceItemUpdate.tryEmit( bluetoothTileDialogRepository.cachedDevices .mapNotNull { cachedDevice -> deviceItemFactoryList .firstOrNull { it.isFilterMatched(cachedDevice, audioManager) } ?.create(context, cachedDevice) } - .sort(displayPriority, mostRecentlyConnectedDevices) + .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices) + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt index f704894e56e2..3927873f8ba8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt @@ -42,13 +42,6 @@ sealed class ObservableTransitionState { * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, + val isUserInputDriven: Boolean, ) : ObservableTransitionState() } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index a1d5d98ba9e9..ade93b1a913e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -186,14 +186,14 @@ class ScreenRecordPermissionDialog( private fun createOptionList(): List<ScreenShareOption> { return listOf( ScreenShareOption( - ENTIRE_SCREEN, - R.string.screen_share_permission_dialog_option_entire_screen, - R.string.screenrecord_permission_dialog_warning_entire_screen - ), - ScreenShareOption( SINGLE_APP, R.string.screen_share_permission_dialog_option_single_app, R.string.screenrecord_permission_dialog_warning_single_app + ), + ScreenShareOption( + ENTIRE_SCREEN, + R.string.screen_share_permission_dialog_option_entire_screen, + R.string.screenrecord_permission_dialog_warning_entire_screen ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 9325e18abce7..00d480a76355 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -19,6 +19,7 @@ package com.android.systemui.screenshot; import android.app.Activity; import android.app.ActivityOptions; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; @@ -44,10 +45,10 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.view.OneShotPreDrawListener; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; import com.android.systemui.screenshot.CropView.CropBoundary; import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; import com.android.systemui.settings.UserTracker; @@ -421,13 +422,15 @@ public class LongScreenshotActivity extends Activity { Log.e(TAG, "failed to export", e); return; } + Uri exported = ContentProvider.getUriWithoutUserId(result.uri); + Log.e(TAG, action + " uri=" + exported); switch (action) { case EDIT: - doEdit(result.uri); + doEdit(exported); break; case SHARE: - doShare(result.uri); + doShare(exported); break; case SAVE: // Nothing more to do diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4b3bd0b44bc9..127a57e26a30 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -303,6 +303,13 @@ public class ScreenshotController { private String mPackageName = ""; private BroadcastReceiver mCopyBroadcastReceiver; + // When false, the screenshot is taken without showing the ui. Note that this only applies to + // external displays, as on the default one the UI should **always** be shown. + // This is needed in case of screenshot during display mirroring, as adding another window to + // the external display makes mirroring stop. + // When there is a way to distinguish between displays that are mirroring or extending, this + // can be removed and we can directly show the ui only in the extended case. + private final Boolean mShowUIOnExternalDisplay; /** Tracks config changes that require re-creating UI */ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_ORIENTATION @@ -335,7 +342,8 @@ public class ScreenshotController { AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, Provider<ScreenshotSoundController> screenshotSoundController, - @Assisted int displayId + @Assisted int displayId, + @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -401,6 +409,7 @@ public class ScreenshotController { mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( ClipboardOverlayController.COPY_OVERLAY_ACTION), ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); + mShowUIOnExternalDisplay = showUIOnExternalDisplay; } void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, @@ -448,6 +457,23 @@ public class ScreenshotController { prepareViewForNewScreenshot(screenshot, oldPackageName); + if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { + mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), + new AssistContentRequester.Callback() { + @Override + public void onAssistContentAvailable(AssistContent assistContent) { + screenshot.setContextUrl(assistContent.getWebUri()); + } + }); + } + + if (!shouldShowUi()) { + saveScreenshotInWorkerThread( + screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, + (ignored) -> {}); + return; + } + saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); @@ -482,22 +508,16 @@ public class ScreenshotController { screenshot.getUserHandle())); mScreenshotView.setScreenshot(screenshot); - if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), - new AssistContentRequester.Callback() { - @Override - public void onAssistContentAvailable(AssistContent assistContent) { - screenshot.setContextUrl(assistContent.getWebUri()); - } - }); - } - // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } + private boolean shouldShowUi() { + return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay; + } + void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { withWindowAttached(() -> { if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { @@ -1199,7 +1219,13 @@ public class ScreenshotController { /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory public interface Factory { - /** Creates an instance of the controller for that specific displayId. */ - ScreenshotController create(int displayId); + /** + * Creates an instance of the controller for that specific displayId. + * + * @param displayId: display to capture + * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external + * display. + */ + ScreenshotController create(int displayId, boolean showUIOnExternalDisplay); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 03c3f7a8ddf3..abe40ff11ead 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -135,7 +135,9 @@ constructor( } private fun getScreenshotController(id: Int): ScreenshotController { - return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) } + return screenshotControllers.computeIfAbsent(id) { + screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false) + } } /** For java compatibility only. see [executeScreenshots] */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 0be2265bf8fa..75d52cbe2e36 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -132,7 +132,8 @@ public class TakeScreenshotService extends Service { if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { mScreenshot = null; } else { - mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY); + mScreenshot = screenshotControllerFactory.create( + Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false); } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index bd592c9daff6..cf1fbe3685e3 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -16,6 +16,8 @@ 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 @@ -64,6 +66,7 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { /** * Callback for notifying of changes. */ + @WeaklyReferencedCallback interface Callback { /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 2ef83dd42868..51972c2d025c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -230,6 +230,8 @@ import com.android.systemui.util.Utils; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.animation.FlingAnimationUtils; +import dalvik.annotation.optimization.NeverCompile; + import kotlin.Unit; import java.io.PrintWriter; @@ -412,9 +414,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private int mDisplayLeftInset = 0; // in pixels @VisibleForTesting - KeyguardClockPositionAlgorithm - mClockPositionAlgorithm = - new KeyguardClockPositionAlgorithm(); + KeyguardClockPositionAlgorithm mClockPositionAlgorithm; private final KeyguardClockPositionAlgorithm.Result mClockPositionResult = new KeyguardClockPositionAlgorithm.Result(); @@ -777,7 +777,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump KeyguardViewConfigurator keyguardViewConfigurator, KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, SplitShadeStateController splitShadeStateController, - PowerInteractor powerInteractor) { + PowerInteractor powerInteractor, + KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) { keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardFadingAwayChanged() { @@ -805,6 +806,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; + mClockPositionAlgorithm = keyguardClockPositionAlgorithm; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -3379,6 +3381,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mBlockingExpansionForCurrentTouch = isTracking(); } + @NeverCompile @Override public void dump(PrintWriter pw, String[] args) { pw.println(TAG + ":"); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 5414b3f30aa5..d05dfe2c11c1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -48,7 +48,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor; +import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -106,7 +106,7 @@ public class NotificationShadeWindowViewController implements Dumpable { private final NotificationInsetsController mNotificationInsetsController; private final boolean mIsTrackpadCommonEnabled; private final FeatureFlags mFeatureFlags; - private final KeyEventInteractor mKeyEventInteractor; + private final SysUIKeyEventHandler mSysUIKeyEventHandler; private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; private final AlternateBouncerInteractor mAlternateBouncerInteractor; private GestureDetector mPulsingWakeupGestureHandler; @@ -185,7 +185,7 @@ public class NotificationShadeWindowViewController implements Dumpable { SystemClock clock, BouncerMessageInteractor bouncerMessageInteractor, BouncerLogger bouncerLogger, - KeyEventInteractor keyEventInteractor, + SysUIKeyEventHandler sysUIKeyEventHandler, PrimaryBouncerInteractor primaryBouncerInteractor, AlternateBouncerInteractor alternateBouncerInteractor) { mLockscreenShadeTransitionController = transitionController; @@ -214,7 +214,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mNotificationInsetsController = notificationInsetsController; mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON); mFeatureFlags = featureFlags; - mKeyEventInteractor = keyEventInteractor; + mSysUIKeyEventHandler = sysUIKeyEventHandler; mPrimaryBouncerInteractor = primaryBouncerInteractor; mAlternateBouncerInteractor = alternateBouncerInteractor; @@ -529,17 +529,17 @@ public class NotificationShadeWindowViewController implements Dumpable { @Override public boolean interceptMediaKey(KeyEvent event) { - return mKeyEventInteractor.interceptMediaKey(event); + return mSysUIKeyEventHandler.interceptMediaKey(event); } @Override public boolean dispatchKeyEventPreIme(KeyEvent event) { - return mKeyEventInteractor.dispatchKeyEventPreIme(event); + return mSysUIKeyEventHandler.dispatchKeyEventPreIme(event); } @Override public boolean dispatchKeyEvent(KeyEvent event) { - return mKeyEventInteractor.dispatchKeyEvent(event); + return mSysUIKeyEventHandler.dispatchKeyEvent(event); } }); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 9b74ac4afed4..3bbb2cf83a83 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -105,6 +105,8 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.LargeScreenUtils; import com.android.systemui.util.kotlin.JavaAdapter; +import dalvik.annotation.optimization.NeverCompile; + import dagger.Lazy; import java.io.PrintWriter; @@ -2015,6 +2017,7 @@ public class QuickSettingsController implements Dumpable { (int) ((y - getInitialTouchY()) / displayDensity), (int) (vel / displayDensity)); } + @NeverCompile @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println(TAG + ":"); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 0554c5855d61..1a28ddc9719b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -72,10 +72,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { listener.onShadeExpansionFullyChanged(qsExpanded) } - fun removeFullExpansionListener(listener: ShadeFullExpansionListener) { - fullExpansionListeners.remove(listener) - } - fun addQsExpansionListener(listener: ShadeQsExpansionListener) { qsExpansionListeners.add(listener) listener.onQsExpansionChanged(qsExpanded) @@ -99,11 +95,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { stateListeners.add(listener) } - /** Removes a state listener. */ - fun removeStateListener(listener: ShadeStateListener) { - stateListeners.remove(listener) - } - override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) { shadeStateEventsListeners.addIfAbsent(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 6117f9f80e6c..99370e963c9f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -34,7 +34,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import javax.inject.Provider @@ -71,7 +71,7 @@ constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, userSetupRepository: UserSetupRepository, - userInteractor: UserInteractor, + userRepository: UserRepository, sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, repository: ShadeRepository, ) { @@ -156,12 +156,13 @@ constructor( * * TODO(b/300258424) remove all but the first sentence of this comment */ - val isAnyExpanded: Flow<Boolean> = + val isAnyExpanded: StateFlow<Boolean> = if (sceneContainerFlags.isEnabled()) { - anyExpansion.map { it > 0f }.distinctUntilChanged() - } else { - repository.legacyExpandedOrAwaitingInputTransfer - } + anyExpansion.map { it > 0f }.distinctUntilChanged() + } else { + repository.legacyExpandedOrAwaitingInputTransfer + } + .stateIn(scope, SharingStarted.Eagerly, false) /** * Whether the user is expanding or collapsing the shade with user input. This will be true even @@ -227,7 +228,7 @@ constructor( isDeviceProvisioned && // Disallow QS during setup if it's a simple user switcher. (The user intends to // use the lock screen user switcher, QS is not needed.) - (isUserSetup || !userInteractor.isSimpleUserSwitcher) && + (isUserSetup || !userRepository.isSimpleUserSwitcher()) && isShadeEnabled && disableFlags.isQuickSettingsEnabled() && !isDozing @@ -261,7 +262,7 @@ constructor( when (state) { is ObservableTransitionState.Idle -> false is ObservableTransitionState.Transition -> - state.isInitiatedByUserInput && + state.isUserInputDriven && (state.toScene == sceneKey || state.fromScene == sceneKey) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 2147510bbde5..f32f1c2dcd25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -16,9 +16,16 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.os.UserHandle.USER_NULL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.Notification; @@ -30,8 +37,10 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -41,14 +50,18 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.recents.OverviewProxyService; @@ -63,7 +76,9 @@ import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -84,6 +99,11 @@ public class NotificationLockscreenUserManagerImpl implements private final SecureSettings mSecureSettings; private final Object mLock = new Object(); + private static final Uri SHOW_LOCKSCREEN = + Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS); + private static final Uri SHOW_PRIVATE_LOCKSCREEN = + Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy; private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy; private final DevicePolicyManager mDevicePolicyManager; @@ -91,6 +111,23 @@ public class NotificationLockscreenUserManagerImpl implements private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray(); private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); + + // The variables between mUsersDpcAllowingNotifications and + // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread + // and read on the main thread. Because the pipeline needs these values, adding locks would + // introduce too much jank. This means that some pipeline runs could get incorrect values, that + // would be fixed on the next pipeline run. We think this will be rare since a pipeline run + // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the + // pipeline frequently enough that it should be corrected by the next time it matters for the + // user. + private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray(); + private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray(); + private boolean mKeyguardAllowingNotifications = true; + private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications + = new SparseBooleanArray(); + private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications + = new SparseBooleanArray(); + private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray(); private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray(); private final UserManager mUserManager; @@ -99,24 +136,39 @@ public class NotificationLockscreenUserManagerImpl implements private final BroadcastDispatcher mBroadcastDispatcher; private final NotificationClickNotifier mClickNotifier; private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy; + private final FeatureFlagsClassic mFeatureFlags; private boolean mShowLockscreenNotifications; private LockPatternUtils mLockPatternUtils; protected KeyguardManager mKeyguardManager; private int mState = StatusBarState.SHADE; private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners = new ListenerSet<>(); + private final Collection<Uri> mLockScreenUris = new ArrayList<>(); + protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && - isCurrentProfile(getSendingUserId())) { - mUsersAllowingPrivateNotifications.clear(); - updateLockscreenNotificationSetting(); - // TODO(b/231976036): Consolidate pipeline invalidations related to this event - // notifyNotificationStateChanged(); + if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + boolean changed = updateDpcSettings(getSendingUserId()); + if (mCurrentUserId == getSendingUserId()) { + changed |= updateLockscreenNotificationSetting(); + } + if (changed) { + notifyNotificationStateChanged(); + } + } else { + if (isCurrentProfile(getSendingUserId())) { + mUsersAllowingPrivateNotifications.clear(); + updateLockscreenNotificationSetting(); + // TODO(b/231976036): Consolidate pipeline invalidations related to this + // event + // notifyNotificationStateChanged(); + } + } } } }; @@ -136,6 +188,14 @@ public class NotificationLockscreenUserManagerImpl implements updateCurrentProfilesCache(); break; case Intent.ACTION_USER_ADDED: + updateCurrentProfilesCache(); + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); + mBackgroundHandler.post(() -> { + initValuesForUser(userId); + }); + } + break; case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: updateCurrentProfilesCache(); @@ -193,6 +253,8 @@ public class NotificationLockscreenUserManagerImpl implements protected final Context mContext; private final Handler mMainHandler; + private final Handler mBackgroundHandler; + private final Executor mBackgroundExecutor; protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>(); @@ -214,13 +276,18 @@ public class NotificationLockscreenUserManagerImpl implements KeyguardManager keyguardManager, StatusBarStateController statusBarStateController, @Main Handler mainHandler, + @Background Handler backgroundHandler, + @Background Executor backgroundExecutor, DeviceProvisionedController deviceProvisionedController, KeyguardStateController keyguardStateController, SecureSettings secureSettings, DumpManager dumpManager, - LockPatternUtils lockPatternUtils) { + LockPatternUtils lockPatternUtils, + FeatureFlagsClassic featureFlags) { mContext = context; mMainHandler = mainHandler; + mBackgroundHandler = backgroundHandler; + mBackgroundExecutor = backgroundExecutor; mDevicePolicyManager = devicePolicyManager; mUserManager = userManager; mUserTracker = userTracker; @@ -236,6 +303,10 @@ public class NotificationLockscreenUserManagerImpl implements mDeviceProvisionedController = deviceProvisionedController; mSecureSettings = secureSettings; mKeyguardStateController = keyguardStateController; + mFeatureFlags = featureFlags; + + mLockScreenUris.add(SHOW_LOCKSCREEN); + mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN); dumpManager.registerDumpable(this); } @@ -243,16 +314,53 @@ public class NotificationLockscreenUserManagerImpl implements public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; - mLockscreenSettingsObserver = new ContentObserver(mMainHandler) { + mLockscreenSettingsObserver = new ContentObserver( + mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) + ? mBackgroundHandler + : mMainHandler) { + @Override - public void onChange(boolean selfChange) { - // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or - // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... - mUsersAllowingPrivateNotifications.clear(); - mUsersAllowingNotifications.clear(); - // ... and refresh all the notifications - updateLockscreenNotificationSetting(); - notifyNotificationStateChanged(); + public void onChange(boolean selfChange, Collection<Uri> uris, int flags) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + @SuppressLint("MissingPermission") + List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; i--) { + onChange(selfChange, uris, flags,users.get(i).getUserHandle()); + } + } else { + // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or + // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... + mUsersAllowingPrivateNotifications.clear(); + mUsersAllowingNotifications.clear(); + // ... and refresh all the notifications + updateLockscreenNotificationSetting(); + notifyNotificationStateChanged(); + } + } + + // Note: even though this is an override, this method is not called by the OS + // since we're not in system_server. We are using it internally for cases when + // we have a single user id available (e.g. from USER_ADDED). + @Override + public void onChange(boolean selfChange, Collection<Uri> uris, + int flags, UserHandle user) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + boolean changed = false; + for (Uri uri: uris) { + if (SHOW_LOCKSCREEN.equals(uri)) { + changed |= updateUserShowSettings(user.getIdentifier()); + } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) { + changed |= updateUserShowPrivateSettings(user.getIdentifier()); + } + } + + if (mCurrentUserId == user.getIdentifier()) { + changed |= updateLockscreenNotificationSetting(); + } + if (changed) { + notifyNotificationStateChanged(); + } + } } }; @@ -268,23 +376,26 @@ public class NotificationLockscreenUserManagerImpl implements }; mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, + SHOW_LOCKSCREEN, false, mLockscreenSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + SHOW_PRIVATE_LOCKSCREEN, true, mLockscreenSettingsObserver, UserHandle.USER_ALL); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, - mSettingsObserver); + if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, + mSettingsObserver); + } mBroadcastDispatcher.registerReceiver(mAllUsersReceiver, new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), - null /* handler */, UserHandle.ALL); + mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) + ? mBackgroundExecutor : null, UserHandle.ALL); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); @@ -305,7 +416,23 @@ public class NotificationLockscreenUserManagerImpl implements mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late updateCurrentProfilesCache(); - mSettingsObserver.onChange(false); // set up + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // Set up + mBackgroundHandler.post(() -> { + @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; i--) { + initValuesForUser(users.get(i).id); + } + }); + } else { + mSettingsObserver.onChange(false); // set up + } + } + + private void initValuesForUser(@UserIdInt int userId) { + mLockscreenSettingsObserver.onChange( + false, mLockScreenUris, 0, UserHandle.of(userId)); + updateDpcSettings(userId); } public boolean shouldShowLockscreenNotifications() { @@ -322,17 +449,75 @@ public class NotificationLockscreenUserManagerImpl implements mShowLockscreenNotifications = show; } - protected void updateLockscreenNotificationSetting() { - final boolean show = mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - 1, - mCurrentUserId) != 0; - final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( - null /* admin */, mCurrentUserId); - final boolean allowedByDpm = (dpmFlags - & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + protected boolean updateLockscreenNotificationSetting() { + boolean show; + boolean allowedByDpm; + + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + show = mUsersUsersAllowingNotifications.get(mCurrentUserId) + && mKeyguardAllowingNotifications; + // If DPC never notified us about a user, that means they have no policy for the user, + // and they allow the behavior + allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true); + } else { + show = mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 1, + mCurrentUserId) != 0; + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( + null /* admin */, mCurrentUserId); + allowedByDpm = (dpmFlags + & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + } + final boolean oldValue = mShowLockscreenNotifications; setShowLockscreenNotifications(show && allowedByDpm); + + return oldValue != mShowLockscreenNotifications; + } + + @WorkerThread + protected boolean updateDpcSettings(int userId) { + boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId); + boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId); + final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( + null /* admin */, userId); + final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; + mUsersDpcAllowingNotifications.put(userId, allowedLockscreen); + mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate); + return (originalAllowLockscreen != allowedLockscreen) + || (originalAllowPrivate != allowedPrivate); + } + + @WorkerThread + private boolean updateUserShowSettings(int userId) { + boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId); + boolean newAllowLockscreen = mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, + 1, + userId) != 0; + mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen); + boolean keyguardChanged = updateGlobalKeyguardSettings(); + return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged; + } + + @WorkerThread + private boolean updateUserShowPrivateSettings(int userId) { + boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId); + boolean newValue = mSecureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + 0, + userId) != 0; + mUsersUsersAllowingPrivateNotifications.put(userId, newValue); + return (newValue != originalValue); + } + + @WorkerThread + private boolean updateGlobalKeyguardSettings() { + final boolean oldValue = mKeyguardAllowingNotifications; + mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed(); + return oldValue != mKeyguardAllowingNotifications; } /** @@ -340,21 +525,41 @@ public class NotificationLockscreenUserManagerImpl implements * when the lockscreen is in "public" (secure & locked) mode? */ public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { - if (userHandle == UserHandle.USER_ALL) { - return true; - } + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + if (userHandle == UserHandle.USER_ALL) { + userHandle = mCurrentUserId; + } + if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable()); + updateUserShowPrivateSettings(userHandle); + } + if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable()); + updateDpcSettings(userHandle); + } + return mUsersUsersAllowingPrivateNotifications.get(userHandle) + && mUsersDpcAllowingPrivateNotifications.get(userHandle); + } else { + if (userHandle == UserHandle.USER_ALL) { + return true; + } - if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { - final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); - final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, - DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - final boolean allowed = allowedByUser && allowedByDpm; - mUsersAllowingPrivateNotifications.append(userHandle, allowed); - return allowed; - } + if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { + final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); + final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, + KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + final boolean allowed = allowedByUser && allowedByDpm; + mUsersAllowingPrivateNotifications.append(userHandle, allowed); + return allowed; + } - return mUsersAllowingPrivateNotifications.get(userHandle); + return mUsersAllowingPrivateNotifications.get(userHandle); + } } /** @@ -406,21 +611,44 @@ public class NotificationLockscreenUserManagerImpl implements * "public" (secure & locked) mode? */ public boolean userAllowsNotificationsInPublic(int userHandle) { - if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { - return true; - } + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // Unlike 'show private', settings does not show a copy of this setting for each + // profile, so it inherits from the parent user. + if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) { + userHandle = mCurrentUserId; + } + if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable()); + updateUserShowSettings(userHandle); + } + if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) { + // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe + // default value before moving to 'released' + Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable()); + updateDpcSettings(userHandle); + } + return mUsersUsersAllowingNotifications.get(userHandle) + && mUsersDpcAllowingNotifications.get(userHandle) + && mKeyguardAllowingNotifications; + } else { + if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { + return true; + } - if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { - final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); - final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, - DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); - final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; - mUsersAllowingNotifications.append(userHandle, allowed); - return allowed; + if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { + final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( + LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); + final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, + KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); + final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; + mUsersAllowingNotifications.append(userHandle, allowed); + return allowed; + } + return mUsersAllowingNotifications.get(userHandle); } - return mUsersAllowingNotifications.get(userHandle); } /** @return true if the entry needs redaction when on the lockscreen. */ @@ -451,9 +679,15 @@ public class NotificationLockscreenUserManagerImpl implements return true; } NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); - return entry != null - && entry.getRanking().getLockscreenVisibilityOverride() - == Notification.VISIBILITY_PRIVATE; + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + return entry != null + && entry.getRanking().getChannel().getLockscreenVisibility() + == Notification.VISIBILITY_PRIVATE; + } else { + return entry != null + && entry.getRanking().getLockscreenVisibilityOverride() + == Notification.VISIBILITY_PRIVATE; + } } private void updateCurrentProfilesCache() { @@ -491,20 +725,6 @@ public class NotificationLockscreenUserManagerImpl implements } /** - * If any managed/work profiles are in public mode. - */ - public boolean isAnyManagedProfilePublicMode() { - synchronized (mLock) { - for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) { - if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) { - return true; - } - } - } - return false; - } - - /** * Returns the current user id. This can change if the user is switched. */ public int getCurrentUserId() { @@ -581,8 +801,16 @@ public class NotificationLockscreenUserManagerImpl implements } private void notifyNotificationStateChanged() { - for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { - listener.onNotificationStateChanged(); + if (!Looper.getMainLooper().isCurrentThread()) { + mMainHandler.post(() -> { + for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { + listener.onNotificationStateChanged(); + } + }); + } else { + for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { + listener.onNotificationStateChanged(); + } } } @@ -620,5 +848,15 @@ public class NotificationLockscreenUserManagerImpl implements pw.println(mUsersInLockdownLatestResult); pw.print(" mShouldHideNotifsLatestResult="); pw.println(mShouldHideNotifsLatestResult); + pw.print(" mUsersDpcAllowingNotifications="); + pw.println(mUsersDpcAllowingNotifications); + pw.print(" mUsersUsersAllowingNotifications="); + pw.println(mUsersUsersAllowingNotifications); + pw.print(" mKeyguardAllowingNotifications="); + pw.println(mKeyguardAllowingNotifications); + pw.print(" mUsersDpcAllowingPrivateNotifications="); + pw.println(mUsersDpcAllowingPrivateNotifications); + pw.print(" mUsersUsersAllowingPrivateNotifications="); + pw.println(mUsersUsersAllowingPrivateNotifications); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 09ad55e005d4..f8c049e86cb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -37,11 +37,11 @@ import androidx.annotation.NonNull; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; -import com.android.systemui.res.R; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.flags.Flags; -import com.android.systemui.flags.ViewRefactorFlag; +import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; @@ -96,10 +96,10 @@ public class NotificationShelf extends ActivatableNotificationView implements St private float mCornerAnimationDistance; private NotificationShelfController mController; private float mActualWidth = -1; - private final ViewRefactorFlag mSensitiveRevealAnim = - new ViewRefactorFlag(Flags.SENSITIVE_REVEAL_ANIM); - private final ViewRefactorFlag mShelfRefactor = - new ViewRefactorFlag(Flags.NOTIFICATION_SHELF_REFACTOR); + private final RefactorFlag mSensitiveRevealAnim = + RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM); + private final RefactorFlag mShelfRefactor = + RefactorFlag.forView(Flags.NOTIFICATION_SHELF_REFACTOR); private boolean mCanModifyColorOfNotifications; private boolean mCanInteract; private NotificationStackScrollLayout mHostLayout; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 37a4ef168423..ef45d2b75ba0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -43,15 +44,17 @@ import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.util.Compile; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Comparator; @@ -95,6 +98,7 @@ public class StatusBarStateControllerImpl implements private final ArrayList<RankedListener> mListeners = new ArrayList<>(); private final UiEventLogger mUiEventLogger; private final InteractionJankMonitor mInteractionJankMonitor; + private final Lazy<ShadeInteractor> mShadeInteractorLazy; private int mState; private int mLastState; private int mUpcomingState; @@ -158,15 +162,13 @@ public class StatusBarStateControllerImpl implements UiEventLogger uiEventLogger, DumpManager dumpManager, InteractionJankMonitor interactionJankMonitor, - ShadeExpansionStateManager shadeExpansionStateManager - ) { + Lazy<ShadeInteractor> shadeInteractorLazy) { mUiEventLogger = uiEventLogger; mInteractionJankMonitor = interactionJankMonitor; + mShadeInteractorLazy = shadeInteractorLazy; for (int i = 0; i < HISTORY_SIZE; i++) { mHistoricalRecords[i] = new HistoricalState(); } - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); - dumpManager.registerDumpable(this); } @@ -336,6 +338,8 @@ public class StatusBarStateControllerImpl implements && (view != null && view.isAttachedToWindow())) { mView = view; mClockSwitchView = view.findViewById(R.id.keyguard_clock_container); + collectFlow(mView, mShadeInteractorLazy.get().isAnyExpanded(), + this::onShadeOrQsExpanded); } mDozeAmountTarget = dozeAmount; if (animated) { @@ -345,7 +349,7 @@ public class StatusBarStateControllerImpl implements } } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (mIsExpanded != isExpanded) { mIsExpanded = isExpanded; String tag = getClass().getSimpleName() + "#setIsExpanded"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 9db61c6b3ba2..fc84973c46bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -83,6 +83,8 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.CarrierConfigTracker; +import dalvik.annotation.optimization.NeverCompile; + import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -1154,6 +1156,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } /** */ + @NeverCompile public void dump(PrintWriter pw, String[] args) { pw.println("NetworkController state:"); pw.println(" mUserSetup=" + mUserSetup); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt index 328a74100e48..3e9c6fbb2ec4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt @@ -3,10 +3,10 @@ package com.android.systemui.statusbar.notification import android.util.FloatProperty import android.view.View import androidx.annotation.FloatRange -import com.android.systemui.res.R import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.flags.ViewRefactorFlag +import com.android.systemui.flags.RefactorFlag +import com.android.systemui.res.R import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import kotlin.math.abs @@ -323,7 +323,7 @@ constructor( internal var maxRadius = maxRadius private set - internal val newHeadsUpAnim = ViewRefactorFlag(featureFlags, Flags.IMPROVED_HUN_ANIMATIONS) + internal val newHeadsUpAnim = RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS, featureFlags) /** Animatable for top roundness */ private val topAnimatable = topAnimatable(roundable) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9d56a8ede1cc..362786ec4b58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -115,6 +115,7 @@ public interface NotifCollectionListener { * * @deprecated Use {@link #onRankingApplied()} instead. */ + @Deprecated default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 26db5f2bc095..e74b3fcdf050 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -11,10 +11,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.systemui.statusbar.notification.row; +package com.android.systemui.statusbar.notification.footer.ui.view; import static android.graphics.PorterDuff.Mode.SRC_ATOP; @@ -35,6 +35,8 @@ import androidx.annotation.NonNull; import com.android.settingslib.Utils; import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.row.FooterViewButton; +import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.util.DumpUtilsKt; @@ -93,6 +95,7 @@ public class FooterView extends StackScrollerDecorView { updateColors(); } + /** Show a message instead of the footer buttons. */ public void setFooterLabelVisible(boolean isVisible) { if (isVisible) { mManageButton.setVisibility(View.GONE); @@ -105,14 +108,22 @@ public class FooterView extends StackScrollerDecorView { } } + /** Set onClickListener for the manage/history button. */ public void setManageButtonClickListener(OnClickListener listener) { mManageButton.setOnClickListener(listener); } + /** Set onClickListener for the clear all (end) button. */ public void setClearAllButtonClickListener(OnClickListener listener) { mClearAllButton.setOnClickListener(listener); } + /** + * Whether the touch is outside the Clear all button. + * + * TODO(b/293167744): This is an artifact from the time when we could press underneath the + * shade to dismiss it. Check if it's safe to remove. + */ public boolean isOnEmptySpace(float touchX, float touchY) { return touchX < mContent.getX() || touchX > mContent.getX() + mContent.getWidth() @@ -120,6 +131,7 @@ public class FooterView extends StackScrollerDecorView { || touchY > mContent.getY() + mContent.getHeight(); } + /** Show "History" instead of "Manage" on the start button. */ public void showHistory(boolean showHistory) { if (mShowHistory == showHistory) { return; @@ -141,6 +153,7 @@ public class FooterView extends StackScrollerDecorView { .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null); } + /** Whether the start button shows "History" (true) or "Manage" (false). */ public boolean isHistoryShown() { return mShowHistory; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt index 805a4dba111f..50efbb5458cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt @@ -34,7 +34,7 @@ import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.flags.ViewRefactorFlag +import com.android.systemui.flags.RefactorFlag import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationListener @@ -99,7 +99,7 @@ constructor( private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context) private val updateStatusBarIcons = Runnable { updateStatusBarIcons() } - private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR) + private val shelfRefactor = RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR) private val tintAreas = ArrayList<Rect>() private var iconSize = 0 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index b2c32cd42d1d..db7f46eb28f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -1,6 +1,5 @@ package com.android.systemui.statusbar.notification.interruption -import android.app.Notification import android.app.Notification.VISIBILITY_SECRET import android.content.Context import android.database.ContentObserver @@ -14,6 +13,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -78,7 +79,8 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, private val userTracker: UserTracker, private val secureSettings: SecureSettings, - private val globalSettings: GlobalSettings + private val globalSettings: GlobalSettings, + private val featureFlags: FeatureFlagsClassic ) : CoreStartable, KeyguardNotificationVisibilityProvider { private val showSilentNotifsUri = secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) @@ -201,7 +203,7 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( // device isn't public, no need to check public-related settings, so allow !lockscreenUserManager.isLockscreenPublicMode(user) -> false // entry is meant to be secret on the lockscreen, disallow - entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true + isRankingVisibilitySecret(entry) -> true // disallow if user disallows notifications in public else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user) } @@ -215,6 +217,17 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( } } + private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean { + return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting + // info, and NotificationLockscreenUserManagerImpl is already listening for updates + // to those + entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET + } else { + entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET + } + } + override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run { println("isLockedOrLocking=$isLockedOrLocking") withIncreasedIndent { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index 6ec9dbe003a2..b0155f13dbec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -180,4 +180,9 @@ public interface NotificationInterruptStateProvider { * Add a component that can suppress visual interruptions. */ void addSuppressor(NotificationInterruptSuppressor suppressor); + + /** + * Remove a component that can suppress visual interruptions. + */ + void removeSuppressor(NotificationInterruptSuppressor suppressor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 3819843aa7b2..778a0a90cd85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -175,6 +175,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } @Override + public void removeSuppressor(NotificationInterruptSuppressor suppressor) { + mSuppressors.remove(suppressor); + } + + @Override public boolean shouldBubbleUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt index ebdeded6e329..d7f0baf4f002 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -58,6 +58,10 @@ class NotificationInterruptStateProviderWrapper( wrapped.addSuppressor(suppressor) } + override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) { + wrapped.removeSuppressor(suppressor) + } + override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision = wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt index 454ba02b2d73..920bbe96b33b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -60,6 +60,13 @@ interface VisualInterruptionDecisionProvider { fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) /** + * Removes a [component][suppressor] that can suppress visual interruptions. + * + * @param[suppressor] the suppressor to remove + */ + fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) + + /** * Decides whether a [notification][entry] should display as heads-up or not, but does not log * that decision. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt index 197ae1a77198..dc9028d94312 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt @@ -25,6 +25,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row import com.android.systemui.statusbar.notification.collection.NotifPipeline +import dalvik.annotation.optimization.NeverCompile import java.io.PrintWriter import javax.inject.Inject @@ -39,6 +40,7 @@ constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipelin Log.i("NotificationMemory", "Registered dumpable.") } + @NeverCompile override fun dump(pw: PrintWriter, args: Array<out String>) { val memoryUse = NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 847d94861401..661768da8479 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -351,12 +351,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, - boolean isHeadsUpAnimation, Runnable onFinishedRunnable, + boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; - startAppearAnimation(false /* isAppearing */, translationDirection, - delay, duration, onFinishedRunnable, animationListener); + if (mDrawingAppearAnimation) { + startAppearAnimation(false /* isAppearing */, translationDirection, + delay, duration, onStartedRunnable, onFinishedRunnable, animationListener); + } else { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + if (onFinishedRunnable != null) { + onFinishedRunnable.run(); + } + } return 0; } @@ -365,12 +374,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView Runnable onFinishRunnable) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAppear; - startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, - duration, null, null); + if (mDrawingAppearAnimation) { + startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, + duration, null, null, null); + } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, - long duration, final Runnable onFinishedRunnable, + long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { mAnimationTranslationY = translationDirection * getActualHeight(); cancelAppearAnimation(); @@ -434,6 +445,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } mRunWithoutInterruptions = true; Configuration.Builder builder = Configuration.Builder .withView(getCujType(isAppearing), ActivatableNotificationView.this); 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 061132ff4f19..9340b85a743d 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 @@ -27,7 +27,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.Notification; -import android.app.NotificationChannel; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -40,8 +39,6 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Trace; -import android.service.notification.StatusBarNotification; -import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.IndentingPrintWriter; @@ -73,15 +70,15 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.internal.widget.CallLayout; -import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.flags.ViewRefactorFlag; +import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; 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.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarIconView; @@ -274,8 +271,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private OnExpandClickListener mOnExpandClickListener; private View.OnClickListener mOnFeedbackClickListener; private Path mExpandingClipPath; - private final ViewRefactorFlag mInlineReplyAnimation = - new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); + private final RefactorFlag mInlineReplyAnimation = + RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); // Listener will be called when receiving a long click event. // Use #setLongPressPosition to optionally assign positional data with the long press. @@ -2930,6 +2927,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { @@ -2937,10 +2935,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (anim != null) { anim.addListener(new AnimatorListenerAdapter() { @Override + public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + } + @Override public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - onFinishedRunnable, animationListener); + null, onFinishedRunnable, animationListener); } }); anim.start(); @@ -2948,7 +2952,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 259923170477..2a3e69b7f4d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -28,9 +28,9 @@ import android.util.IndentingPrintWriter; import android.view.View; import android.view.ViewOutlineProvider; -import com.android.systemui.res.R; import com.android.systemui.flags.Flags; -import com.android.systemui.flags.ViewRefactorFlag; +import com.android.systemui.flags.RefactorFlag; +import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.RoundableState; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.util.DumpUtilsKt; @@ -49,8 +49,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { private float mOutlineAlpha = -1f; private boolean mAlwaysRoundBothCorners; private Path mTmpPath = new Path(); - protected final ViewRefactorFlag mImprovedHunAnimation = - new ViewRefactorFlag(Flags.IMPROVED_HUN_ANIMATIONS); + protected final RefactorFlag mImprovedHunAnimation = + RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS); /** * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index f2f55a87ba3f..6edab4d26d59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -69,6 +69,9 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro private boolean mClipToActualHeight = true; private boolean mChangingPosition = false; private ViewGroup mTransientContainer; + + // Needs to be added as transient view when removed from parent, because it's in animation + private boolean mInRemovalAnimation; private boolean mInShelf; private boolean mTransformingInShelf; protected float mContentTransformationAmount; @@ -381,6 +384,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro */ public abstract long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); @@ -604,6 +608,25 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro } /** + * Add the view to a transient container. + */ + public void addToTransientContainer(ViewGroup container, int index) { + container.addTransientView(this, index); + setTransientContainer(container); + } + + /** + * @return If the view is in a process of removal animation. + */ + public boolean inRemovalAnimation() { + return mInRemovalAnimation; + } + + public void setInRemovalAnimation(boolean inRemovalAnimation) { + mInRemovalAnimation = inRemovalAnimation; + } + + /** * @return true if the group's expansion state is changing, false otherwise. */ public boolean isGroupExpansionChanging() { @@ -837,6 +860,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro pw.println(); } if (DUMP_VERBOSE) { + pw.println("mInRemovalAnimation: " + mInRemovalAnimation); pw.println("mClipTopAmount: " + mClipTopAmount); pw.println("mClipBottomAmount " + mClipBottomAmount); pw.println("mClipToActualHeight: " + mClipToActualHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 0c686be0406d..aabf2954f23c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -167,7 +167,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { } @VisibleForTesting - boolean isSecondaryVisible() { + public boolean isSecondaryVisible() { return mIsSecondaryVisible; } @@ -179,7 +179,8 @@ public abstract class StackScrollerDecorView extends ExpandableView { return mIsVisible; } - void setDuration(int duration) { + @VisibleForTesting + public void setDuration(int duration) { mDuration = duration; } @@ -236,9 +237,13 @@ public abstract class StackScrollerDecorView extends ExpandableView { @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run()); return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index 5d46f52dba87..bae5baaf91ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -25,9 +25,7 @@ import android.util.AttributeSet import com.android.systemui.res.R import com.android.systemui.statusbar.notification.row.ExpandableView -/** - * Root view to insert Lock screen media controls into the notification stack. - */ +/** Root view to insert Lock screen media controls into the notification stack. */ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) { override var clipHeight = 0 @@ -46,8 +44,8 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } private fun updateResources() { - cornerRadius = context.resources - .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() + cornerRadius = + context.resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() } public override fun updateClipping() { @@ -70,18 +68,23 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } override fun performRemoveAnimation( - duration: Long, - delay: Long, - translationDirection: Float, - isHeadsUpAnimation: Boolean, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter? + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onStartedRunnable: Runnable?, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter? ): Long { return 0 } - override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean, - onEnd: Runnable?) { + override fun performAddAnimation( + delay: Long, + duration: Long, + isHeadsUpAppear: Boolean, + onEnd: Runnable? + ) { // No animation, it doesn't need it, this would be local } -}
\ No newline at end of file +} 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 78d75582817b..79f8f22fd753 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 @@ -20,6 +20,7 @@ import static android.os.Trace.TRACE_TAG_APP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; +import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.DumpUtilsKt.println; @@ -86,12 +87,12 @@ import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.ExpandHelper; -import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.flags.ViewRefactorFlag; +import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.res.R; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.TouchLogger; import com.android.systemui.statusbar.CommandQueue; @@ -106,12 +107,12 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; +import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; -import com.android.systemui.statusbar.notification.row.FooterView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; @@ -200,8 +201,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private Set<Integer> mDebugTextUsedYPositions; private final boolean mDebugRemoveAnimation; private final boolean mSensitiveRevealAnimEndabled; - private final ViewRefactorFlag mAnimatedInsets; - private final ViewRefactorFlag mShelfRefactor; + private final RefactorFlag mAnimatedInsets; + private final RefactorFlag mShelfRefactor; private final boolean mNewAodTransition; @@ -576,6 +577,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSplitShadeStateController = splitShadeStateController; updateSplitNotificationShade(); } + private FeatureFlags mFeatureFlags; private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = new ExpandableView.OnHeightChangedListener() { @@ -628,16 +630,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public NotificationStackScrollLayout(Context context, AttributeSet attrs) { super(context, attrs, 0, 0); Resources res = getResources(); - FeatureFlags featureFlags = Dependency.get(FeatureFlags.class); - mIsSmallLandscapeLockscreenEnabled = featureFlags.isEnabled( + mFeatureFlags = Dependency.get(FeatureFlags.class); + mIsSmallLandscapeLockscreenEnabled = mFeatureFlags.isEnabled( Flags.LOCKSCREEN_ENABLE_LANDSCAPE); - mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); - mNewAodTransition = featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); - mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); - mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); + mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); + mNewAodTransition = mFeatureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); + mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); + mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); mAnimatedInsets = - new ViewRefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); - mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); + new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mShelfRefactor = new RefactorFlag(mFeatureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -2779,8 +2781,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (animationGenerated) { if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { logAddTransientChild(child, container); - container.addTransientView(child, 0); - child.setTransientContainer(container); + child.addToTransientContainer(container, 0); } } else { mSwipedOutViews.remove(child); @@ -2870,7 +2871,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Generate a remove animation for a child view. * * @param child The view to generate the remove animation for. - * @return Whether an animation was generated. + * @return Whether a new animation was generated or an existing animation was detected by this + * method. We need this to determine if a transient view is needed. */ boolean generateRemoveAnimation(ExpandableView child) { String key = ""; @@ -2887,10 +2889,23 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAddedHeadsUpChildren.remove(child); return false; } - if (isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return true; + if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) { + // Skip adding animation for clicked heads up notifications when the + // Shade is closed, because the animation event is generated in + // generateHeadsUpAnimationEvents. Only report that an animation was + // actually generated (thus requesting the transient view be added) + // if a removal animation is in progress. + if (!isExpanded() && isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return child.inRemovalAnimation(); + } + } else { + if (isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return true; + } } if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key 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 9695cb132ba9..50207806ecaa 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 @@ -64,7 +64,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.flags.ViewRefactorFlag; +import com.android.systemui.flags.RefactorFlag; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -207,7 +207,7 @@ public class NotificationStackScrollLayoutController { private boolean mIsInTransitionToAod = false; private final FeatureFlags mFeatureFlags; - private final ViewRefactorFlag mShelfRefactor; + private final RefactorFlag mShelfRefactor; private final NotificationTargetsHelper mNotificationTargetsHelper; private final SecureSettings mSecureSettings; private final NotificationDismissibilityProvider mDismissibilityProvider; @@ -719,7 +719,7 @@ public class NotificationStackScrollLayoutController { mShadeController = shadeController; mNotifIconAreaController = notifIconAreaController; mFeatureFlags = featureFlags; - mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); + mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mNotificationTargetsHelper = notificationTargetsHelper; mSecureSettings = secureSettings; mDismissibilityProvider = dismissibilityProvider; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 8ca18521de63..80f98a68b4f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -27,16 +27,16 @@ import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.keyguard.BouncerPanelExpansionCalculator; -import com.android.systemui.res.R; import com.android.systemui.animation.ShadeInterpolation; +import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.SourceType; +import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; -import com.android.systemui.statusbar.notification.row.FooterView; import java.util.ArrayList; import java.util.List; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 69453c65f57d..e94258f416ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -346,21 +349,19 @@ public class StackStateAnimator { ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) { boolean needsCustomAnimation = false; for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { - final ExpandableView changingView = (ExpandableView) event.mChangingView; + final ExpandableView changingView = event.mChangingView; boolean loggable = false; boolean isHeadsUp = false; - boolean isGroupChild = false; String key = null; if (changingView instanceof ExpandableNotificationRow && mLogger != null) { loggable = true; isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp(); - isGroupChild = changingView.isChildInGroup(); key = ((ExpandableNotificationRow) changingView).getEntry().getKey(); } if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); if (viewState == null || viewState.gone) { // The position for this child was never generated, let's continue. @@ -374,7 +375,11 @@ public class StackStateAnimator { } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { - if (changingView.getVisibility() != View.VISIBLE) { + int changingViewVisibility = changingView.getVisibility(); + if (loggable) { + mLogger.processAnimationEventsRemoval(key, changingViewVisibility, isHeadsUp); + } + if (changingViewVisibility != View.VISIBLE) { changingView.removeFromTransientContainer(); continue; } @@ -410,30 +415,40 @@ public class StackStateAnimator { translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); } - Runnable postAnimation = changingView::removeFromTransientContainer; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { String finalKey = key; - if (isHeadsUp) { - mLogger.logHUNViewDisappearingWithRemoveEvent(key); - postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } else if (isGroupChild) { - mLogger.groupChildRemovalEventProcessed(key); - postAnimation = () -> { - mLogger.groupChildRemovalAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } + final boolean finalIsHeadsHp = isHeadsUp; + startAnimation = () -> { + mLogger.animationStart(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; + } else { + startAnimation = ()-> { + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, getGlobalAnimationFinishedListener()); needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (mHostLayout.isFullySwipedOut(changingView)) { + boolean isFullySwipedOut = mHostLayout.isFullySwipedOut(changingView); + if (loggable) { + mLogger.processAnimationEventsRemoveSwipeOut(key, isFullySwipedOut, isHeadsUp); + } + if (isFullySwipedOut) { changingView.removeFromTransientContainer(); } } else if (event.animationType == NotificationStackScrollLayout @@ -442,7 +457,7 @@ public class StackStateAnimator { row.prepareExpansionChanged(); } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); mTmpState.copyFrom(viewState); if (event.headsUpFromBottom) { @@ -464,22 +479,23 @@ public class StackStateAnimator { } mTmpState.applyToView(changingView); - } else if (event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR || - event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { mHeadsUpDisappearChildren.add(changingView); Runnable endRunnable = null; if (changingView.getParent() == null) { - // This notification was actually removed, so we need to add it transiently + // This notification was actually removed, so we need to add it + // transiently mHostLayout.addTransientView(changingView, 0); changingView.setTransientContainer(mHostLayout); mTmpState.initFrom(changingView); endRunnable = changingView::removeFromTransientContainer; } + boolean needsAnimation = true; if (changingView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; + ExpandableNotificationRow row = + (ExpandableNotificationRow) changingView; if (row.isDismissed()) { needsAnimation = false; } @@ -488,21 +504,43 @@ public class StackStateAnimator { // We need to add the global animation listener, since once no animations are // running anymore, the panel will instantly hide itself. We need to wait until // the animation is fully finished for this though. - Runnable postAnimation = endRunnable; + final Runnable tmpEndRunnable = endRunnable; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { - mLogger.logHUNViewDisappearing(key); - - Runnable finalEndRunnable = endRunnable; String finalKey1 = key; + final boolean finalIsHeadsUp = isHeadsUp; + final String type = + event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + ? "ANIMATION_TYPE_HEADS_UP_DISAPPEAR" + : "ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK"; + startAnimation = () -> { + mLogger.animationStart(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + } else { postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey1); - if (finalEndRunnable != null) finalEndRunnable.run(); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + startAnimation = () -> { + changingView.setInRemovalAnimation(true); }; } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, + getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { endRunnable.run(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index 0b2c4863157c..d635f8938491 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -5,74 +5,104 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.util.visibilityString import javax.inject.Inject -class StackStateLogger @Inject constructor( +class StackStateLogger +@Inject +constructor( @NotificationHeadsUpLog private val buffer: LogBuffer, @NotificationRenderLog private val notificationRenderBuffer: LogBuffer ) { - fun logHUNViewDisappearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 " - }) - } fun logHUNViewAppearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification view appearing $str1 " - }) + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification view appearing $str1 " } + ) } - fun logHUNViewDisappearingWithRemoveEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE" - }) + fun logHUNViewAppearingWithAddEvent(key: String) { + buffer.log( + TAG, + LogLevel.ERROR, + { str1 = logKey(key) }, + { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" } + ) } - fun logHUNViewAppearingWithAddEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" - }) + fun appearAnimationEnded(key: String) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification appear animation ended $str1 " } + ) } - fun disappearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification disappear animation ended $str1 " - }) + fun processAnimationEventsRemoval(key: String, visibility: Int, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + int1 = visibility + bool1 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE for: $str1, " + + "changingViewVisibility: ${visibilityString(int1)}, isHeadsUp: $bool1" + } + ) } - fun appearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification appear animation ended $str1 " - }) + fun processAnimationEventsRemoveSwipeOut( + key: String, + isFullySwipedOut: Boolean, + isHeadsUp: Boolean + ) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + bool1 = isFullySwipedOut + bool2 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE_SWIPED_OUT for: $str1, " + + "isFullySwipedOut: $bool1, isHeadsUp: $bool2" + } + ) } - fun groupChildRemovalEventProcessed(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.DEBUG, { - str1 = logKey(key) - }, { - "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE" - }) + fun animationStart(key: String?, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation Start, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } - fun groupChildRemovalAnimationEnded(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Group child notification removal animation ended $str1 " - }) + + fun animationEnd(key: String, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation End, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } } -private const val TAG = "StackScroll"
\ No newline at end of file +private const val TAG = "StackScroll" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 697d2978db0c..3877bb6660ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -28,15 +28,15 @@ import android.os.UserHandle; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.res.R; import com.android.systemui.dagger.NightDisplayListenerModule; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; @@ -461,8 +461,8 @@ public class AutoTileManager implements UserAwareController { }; @VisibleForTesting - protected SettingObserver getSecureSettingForKey(String key) { - for (SettingObserver s : mAutoAddSettingList) { + protected UserSettingObserver getSecureSettingForKey(String key) { + for (UserSettingObserver s : mAutoAddSettingList) { if (Objects.equals(key, s.getKey())) { return s; } @@ -476,7 +476,7 @@ public class AutoTileManager implements UserAwareController { * When the setting changes to a value different from 0, if the tile has not been auto added * before, it will be added and the listener will be stopped. */ - private class AutoAddSetting extends SettingObserver { + private class AutoAddSetting extends UserSettingObserver { private final String mSpec; AutoAddSetting( 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 6e6318e780dc..9fb6c1bb2fd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -239,6 +239,8 @@ import com.android.wm.shell.startingsurface.StartingSurface; import dagger.Lazy; +import dalvik.annotation.optimization.NeverCompile; + import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; @@ -310,8 +312,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; void onStatusBarWindowStateChanged(@WindowVisibleState int state) { - updateBubblesVisibility(); mStatusBarWindowState = state; + updateBubblesVisibility(); } @Override @@ -1086,6 +1088,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * @deprecated use {@link * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead. */ @VisibleForTesting + @Deprecated void initShadeVisibilityListener() { mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { @Override @@ -1723,7 +1726,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue(); mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( mode != StatusBarMode.LIGHTS_OUT - && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT)); + && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT + && mStatusBarWindowState != WINDOW_STATE_HIDDEN)); } void checkBarMode( @@ -1763,6 +1767,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } + @NeverCompile @Override public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); @@ -2599,7 +2604,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mShouldDelayWakeUpAnimation); updateIsKeyguard(); + // TODO(b/301913237): can't delay transition if config_displayBlanksAfterDoze=true, + // otherwise, the clock will flicker during LOCKSCREEN_TRANSITION_FROM_AOD mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn() + && !mDozeParameters.getDisplayNeedsBlanking() && mFeatureFlags.isEnabled( Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD); if (!mShouldDelayLockscreenTransitionFromAod) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 6b4382f731ea..407148faeb30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -34,7 +34,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener; @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange; +import com.android.systemui.util.kotlin.JavaAdapter; import java.io.PrintWriter; import java.util.ArrayList; @@ -105,7 +106,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp /////////////////////////////////////////////////////////////////////////////////////////////// // Constructor: @Inject - public HeadsUpManagerPhone(@NonNull final Context context, + public HeadsUpManagerPhone( + @NonNull final Context context, HeadsUpManagerLogger logger, StatusBarStateController statusBarStateController, KeyguardBypassController bypassController, @@ -115,7 +117,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Main Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager) { + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor) { super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger); Resources resources = mContext.getResources(); mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time); @@ -136,8 +139,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp updateResources(); } }); - - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); } public void setAnimationStateHandler(AnimationStateHandler handler) { @@ -230,7 +232,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp mTrackingHeadsUp = trackingHeadsUp; } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsExpanded) { mIsExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index fb5a530e3875..0a03af7d9387 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -26,14 +26,20 @@ import android.util.MathUtils; import com.android.app.animation.Interpolators; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.KeyguardStatusView; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.Logger; +import com.android.systemui.log.dagger.KeyguardClockLog; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView; +import javax.inject.Inject; + /** * Utility class to calculate the clock position and top padding of notifications on Keyguard. */ public class KeyguardClockPositionAlgorithm { + private static final String TAG = "KeyguardClockPositionAlgorithm"; /** * Margin between the bottom of the status view and the notification shade. @@ -147,6 +153,13 @@ public class KeyguardClockPositionAlgorithm { */ private boolean mIsClockTopAligned; + private Logger mLogger; + + @Inject + public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) { + mLogger = new Logger(logBuffer, TAG); + } + /** * Refreshes the dimension values. */ @@ -306,6 +319,20 @@ public class KeyguardClockPositionAlgorithm { + fullyDarkBurnInOffset + shift; mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount); + final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount; + final String outputs = "clockY: " + clockY + + " burnInPreventionOffsetY: " + burnInPreventionOffsetY + + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset + + " shift: " + shift + + " mOverStretchAmount: " + mOverStretchAmount + + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY; + mLogger.i(msg -> { + return msg.getStr1() + " -> " + msg.getStr2(); + }, msg -> { + msg.setStr1(inputs); + msg.setStr2(outputs); + return kotlin.Unit.INSTANCE; + }); return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index 1b9e5b304635..5553270ed0ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -36,16 +36,16 @@ import com.android.app.animation.Interpolators; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ContrastColorUtil; import com.android.settingslib.Utils; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.flags.ViewRefactorFlag; +import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationMediaManager; @@ -109,7 +109,7 @@ public class LegacyNotificationIconAreaControllerImpl implements private final ArrayList<Rect> mTintAreas = new ArrayList<>(); private final Context mContext; - private final ViewRefactorFlag mShelfRefactor; + private final RefactorFlag mShelfRefactor; private final boolean mNewAodTransition; @@ -150,7 +150,7 @@ public class LegacyNotificationIconAreaControllerImpl implements mContrastColorUtil = ContrastColorUtil.getInstance(context); mContext = context; mStatusBarStateController = statusBarStateController; - mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); + mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mNewAodTransition = featureFlags.isEnabled(NEW_AOD_TRANSITION); mStatusBarStateController.addCallback(this); mMediaManager = notificationMediaManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt index 4d9de09fded4..fa6d2797a37e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt @@ -3,12 +3,15 @@ package com.android.systemui.statusbar.phone import android.app.StatusBarManager import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.concurrency.DelayableExecutor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import java.io.PrintWriter import javax.inject.Inject @@ -25,14 +28,14 @@ import javax.inject.Inject */ @SysUISingleton class StatusBarHideIconsForBouncerManager @Inject constructor( - private val commandQueue: CommandQueue, - @Main private val mainExecutor: DelayableExecutor, - statusBarWindowStateController: StatusBarWindowStateController, - shadeExpansionStateManager: ShadeExpansionStateManager, - dumpManager: DumpManager + @Application private val scope: CoroutineScope, + private val commandQueue: CommandQueue, + @Main private val mainExecutor: DelayableExecutor, + statusBarWindowStateController: StatusBarWindowStateController, + val shadeInteractor: ShadeInteractor, + dumpManager: DumpManager ) : Dumpable { // State variables set by external classes. - private var panelExpanded: Boolean = false private var isOccluded: Boolean = false private var bouncerShowing: Boolean = false private var topAppHidesStatusBar: Boolean = false @@ -49,10 +52,9 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( statusBarWindowStateController.addListener { state -> setStatusBarStateAndTriggerUpdate(state) } - shadeExpansionStateManager.addFullExpansionListener { isExpanded -> - if (panelExpanded != isExpanded) { - panelExpanded = isExpanded - updateHideIconsForBouncer(animate = false) + scope.launch { + shadeInteractor.isAnyExpanded.collect { + updateHideIconsForBouncer(false) } } } @@ -101,7 +103,7 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( topAppHidesStatusBar && isOccluded && (statusBarWindowHidden || bouncerShowing) - val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing + val hideBecauseKeyguard = !isShadeOrQsExpanded() && !isOccluded && bouncerShowing val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard if (hideIconsForBouncer != shouldHideIconsForBouncer) { hideIconsForBouncer = shouldHideIconsForBouncer @@ -125,9 +127,13 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( } } + private fun isShadeOrQsExpanded(): Boolean { + return shadeInteractor.isAnyExpanded.value + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("---- State variables set externally ----") - pw.println("panelExpanded=$panelExpanded") + pw.println("isShadeOrQsExpanded=${isShadeOrQsExpanded()}") pw.println("isOccluded=$isOccluded") pw.println("bouncerShowing=$bouncerShowing") pw.println("topAppHideStatusBar=$topAppHidesStatusBar") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 62b2445de13a..3adf3385e3cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -296,7 +296,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); private boolean mIsBackAnimationEnabled; - private final boolean mUdfpsNewTouchDetectionEnabled; private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; private final ActivityStarter mActivityStarter; @@ -398,7 +397,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAlternateBouncerInteractor = alternateBouncerInteractor; mIsBackAnimationEnabled = featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM); - mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION); mUdfpsOverlayInteractor = udfpsOverlayInteractor; mActivityStarter = activityStarter; mKeyguardTransitionInteractor = keyguardTransitionInteractor; @@ -1594,7 +1592,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch() && event.getActionMasked() == MotionEvent.ACTION_UP; final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade = - mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled(); + mKeyguardUpdateManager.isUdfpsEnrolled(); final boolean actionOutsideShouldDismissAlternateBouncer = event.getActionMasked() == MotionEvent.ACTION_OUTSIDE && !udfpsOverlayWillForwardEventsOutsideNotificationShade; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index ba73c1098637..6d8ec44ad55e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -39,6 +39,7 @@ import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -86,8 +87,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { ConfigurationController configurationController, HeadsUpManager headsUpManager, ShadeExpansionStateManager shadeExpansionStateManager, + ShadeInteractor shadeInteractor, Provider<SceneInteractor> sceneInteractor, - Provider<JavaAdapter> javaAdapter, + JavaAdapter javaAdapter, SceneContainerFlags sceneContainerFlags, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, PrimaryBouncerInteractor primaryBouncerInteractor, @@ -126,12 +128,12 @@ public final class StatusBarTouchableRegionManager implements Dumpable { }); mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); if (sceneContainerFlags.isEnabled()) { - javaAdapter.get().alwaysCollectFlow( + javaAdapter.alwaysCollectFlow( sceneInteractor.get().isVisible(), - this::onShadeExpansionFullyChanged); + this::onShadeOrQsExpanded); } mPrimaryBouncerInteractor = primaryBouncerInteractor; @@ -151,7 +153,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { pw.println(mTouchableRegion); } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsStatusBarExpanded) { mIsStatusBarExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java index 6dc8065b2822..da91d6a05d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java @@ -15,6 +15,9 @@ */ package com.android.systemui.statusbar.phone; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + +@WeaklyReferencedCallback public interface StatusBarWindowCallback { /** * Invoked when the internal state of NotificationShadeWindowControllerImpl changes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt new file mode 100644 index 000000000000..85fd2afed9ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt @@ -0,0 +1,58 @@ +/* + * 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.systemui.statusbar.phone + +import android.app.Dialog +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.Gravity +import android.view.WindowManager +import com.android.systemui.res.R + +/** A dialog shown as a bottom sheet. */ +open class SystemUIBottomSheetDialog( + context: Context, + theme: Int = R.style.Theme_SystemUI_Dialog, +) : Dialog(context, theme) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + window?.apply { + setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) + addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) + + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + setGravity(Gravity.BOTTOM) + val edgeToEdgeHorizontally = + context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog) + if (edgeToEdgeHorizontally) { + decorView.setPadding(0, 0, 0, 0) + setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT + ) + + val lp = attributes + lp.fitInsetsSides = 0 + lp.horizontalMargin = 0f + attributes = lp + } + } + setCanceledOnTouchOutside(true) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt index 8ff9198da119..8862c77c9c4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.pipeline.airplane.data.repository import android.os.Handler -import android.os.UserHandle import android.provider.Settings.Global import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -66,13 +65,7 @@ constructor( override val isAirplaneMode: StateFlow<Boolean> = conflatedCallbackFlow { val observer = - object : - SettingObserver( - globalSettings, - bgHandler, - Global.AIRPLANE_MODE_ON, - UserHandle.USER_ALL - ) { + object : SettingObserver(globalSettings, bgHandler, Global.AIRPLANE_MODE_ON) { override fun handleValueChanged(value: Int, observedChange: Boolean) { trySend(value == 1) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 945cc6bc2519..53b343c09329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; import com.android.internal.annotations.GuardedBy; import com.android.settingslib.bluetooth.BluetoothCallback; @@ -36,6 +37,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; @@ -81,6 +83,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private int mState; private final BluetoothAdapter mAdapter; + + private final Executor mBackgroundExecutor; /** */ @Inject @@ -90,6 +94,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa DumpManager dumpManager, BluetoothLogger logger, BluetoothRepository bluetoothRepository, + @Background Executor executor, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter) { @@ -98,6 +103,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mBluetoothRepository = bluetoothRepository; mLocalBluetoothManager = localBluetoothManager; mHandler = new H(mainLooper); + mBackgroundExecutor = executor; if (mLocalBluetoothManager != null) { mLocalBluetoothManager.getEventManager().registerCallback(this); mLocalBluetoothManager.getProfileManager().addServiceListener(this); @@ -218,6 +224,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mIsActive; } + @WorkerThread @Override public void setBluetoothEnabled(boolean enabled) { if (mLocalBluetoothManager != null) { @@ -230,6 +237,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mLocalBluetoothManager != null; } + @WorkerThread @Override public String getConnectedDeviceName() { synchronized (mConnectedDevices) { @@ -251,6 +259,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa getDevices(), this::onConnectionStatusFetched); } + // Careful! This may be invoked in the main thread. private void onConnectionStatusFetched(ConnectionStatusModel status) { List<CachedBluetoothDevice> newList = status.getConnectedDevices(); int state = status.getMaxConnectionState(); @@ -282,30 +291,33 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private void updateAudioProfile() { - boolean audioProfileConnected = false; - boolean otherProfileConnected = false; - - for (CachedBluetoothDevice device : getDevices()) { - for (LocalBluetoothProfile profile : device.getProfiles()) { - int profileId = profile.getProfileId(); - boolean isConnected = device.isConnectedProfile(profile); - if (profileId == BluetoothProfile.HEADSET - || profileId == BluetoothProfile.A2DP - || profileId == BluetoothProfile.HEARING_AID - || profileId == BluetoothProfile.LE_AUDIO) { - audioProfileConnected |= isConnected; - } else { - otherProfileConnected |= isConnected; + // We want this in the background as calls inside `LocalBluetoothProfile` end up being + // binder calls + mBackgroundExecutor.execute(() -> { + boolean audioProfileConnected = false; + boolean otherProfileConnected = false; + + for (CachedBluetoothDevice device : getDevices()) { + for (LocalBluetoothProfile profile : device.getProfiles()) { + int profileId = profile.getProfileId(); + boolean isConnected = device.isConnectedProfile(profile); + if (profileId == BluetoothProfile.HEADSET + || profileId == BluetoothProfile.A2DP + || profileId == BluetoothProfile.HEARING_AID + || profileId == BluetoothProfile.LE_AUDIO) { + audioProfileConnected |= isConnected; + } else { + otherProfileConnected |= isConnected; + } } } - } - - boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); - if (audioProfileOnly != mAudioProfileOnly) { - mAudioProfileOnly = audioProfileOnly; - mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); - } + boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); + if (audioProfileOnly != mAudioProfileOnly) { + mAudioProfileOnly = audioProfileOnly; + mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt index 8c61ada3f8ef..8b20283749c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt @@ -62,7 +62,7 @@ open class DeviceProvisionedControllerImpl @Inject constructor( } private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED) - private val frpActiveUri = secureSettings.getUriFor(Settings.Secure.SECURE_FRP_MODE) + private val frpActiveUri = globalSettings.getUriFor(Settings.Global.SECURE_FRP_MODE) private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE) private val deviceProvisioned = AtomicBoolean(false) @@ -148,7 +148,7 @@ open class DeviceProvisionedControllerImpl @Inject constructor( .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) } if (updateFrp) { - frpActive.set(globalSettings.getInt(Settings.Secure.SECURE_FRP_MODE, 0) != 0) + frpActive.set(globalSettings.getInt(Settings.Global.SECURE_FRP_MODE, 0) != 0) } synchronized(lock) { if (updateUser == ALL_USERS) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt index 80f3d76f0897..96717c7542d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt @@ -38,7 +38,8 @@ interface BluetoothRepository { /** * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once * those statuses have been fetched. The fetching occurs on a background thread because IPCs may - * be required to fetch the statuses (see b/271058380). + * be required to fetch the statuses (see b/271058380). However, the callback will be invoked in + * the main thread. */ fun fetchConnectionStatusInBackground( currentDevices: Collection<CachedBluetoothDevice>, diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index f40454915ee3..5fc435aae67f 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -23,7 +23,6 @@ import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.annotation.VisibleForTesting -import com.android.systemui.res.R import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -31,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus @@ -132,7 +132,6 @@ constructor( Settings.Global.ADD_USERS_WHEN_LOCKED, Settings.Global.USER_SWITCHER_ENABLED, ), - userId = UserHandle.USER_SYSTEM, ) .onStart { emit(Unit) } // Forces an initial update. .map { getSettings() } @@ -247,7 +246,7 @@ constructor( private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = - globalSettings.getIntForUser( + globalSettings.getInt( SETTING_SIMPLE_USER_SWITCHER, if ( appContext.resources.getBoolean( @@ -258,18 +257,16 @@ constructor( } else { 0 }, - UserHandle.USER_SYSTEM, ) != 0 val isAddUsersFromLockscreen = - globalSettings.getIntForUser( + globalSettings.getInt( Settings.Global.ADD_USERS_WHEN_LOCKED, 0, - UserHandle.USER_SYSTEM, ) != 0 val isUserSwitcherEnabled = - globalSettings.getIntForUser( + globalSettings.getInt( Settings.Global.USER_SWITCHER_ENABLED, if ( appContext.resources.getBoolean( @@ -280,7 +277,6 @@ constructor( } else { 0 }, - UserHandle.USER_SYSTEM, ) != 0 UserSwitcherSettingsModel( diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 760fe6a96fda..f5edb7bb5b73 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -42,6 +42,7 @@ public class Utils { * list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be * used; try using `synchronized` or making a copy of the list instead. */ + @Deprecated public static <T> void safeForeach(List<T> list, Consumer<T> c) { for (int i = list.size() - 1; i >= 0; i--) { T item = list.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java new file mode 100644 index 000000000000..855bba6cfd24 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java @@ -0,0 +1,35 @@ +/* + * 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.systemui.util.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Descriptive annotation for clearly tagging callback types that are weakly + * referenced during registration. + * + * This is useful in providing hints to Proguard about certain fields that + * should be kept to preserve strong references. + */ +@Retention(RetentionPolicy.CLASS) +@Target({TYPE}) +public @interface WeaklyReferencedCallback {} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt index 73e2f97d92ae..ffbc10aa5f59 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt @@ -19,6 +19,7 @@ package com.android.systemui.util.kotlin class Utils { companion object { fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second) + fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c) fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d) fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) = diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java index 968dcc95ef50..df5162af70c5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java @@ -26,6 +26,7 @@ import android.util.Log; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,6 +65,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection { * An interface for listening to the connection status. * @param <T> The wrapper type. */ + @WeaklyReferencedCallback public interface Callback<T> { /** * Invoked when the service has been successfully connected to. diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java index 768743217cc7..425336d540f5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java @@ -16,6 +16,8 @@ package com.android.systemui.util.service; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + /** * The {@link Observer} interface specifies an entity which listeners * can be informed of changes to the source, which will require updating. Note that this deals @@ -25,6 +27,7 @@ public interface Observer { /** * Callback for receiving updates from the {@link Observer}. */ + @WeaklyReferencedCallback interface Callback { /** * Invoked when the source has changed. diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java index 85fada20a7ad..42389f0ae627 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java @@ -16,22 +16,23 @@ package com.android.systemui.util.settings; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; -import com.android.systemui.settings.UserTracker; - import javax.inject.Inject; +// use UserHandle.USER_SYSTEM everywhere +@SuppressLint("StaticSettingsProvider") class GlobalSettingsImpl implements GlobalSettings { private final ContentResolver mContentResolver; - private final UserTracker mUserTracker; @Inject - GlobalSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) { + GlobalSettingsImpl(ContentResolver contentResolver) { mContentResolver = contentResolver; - mUserTracker = userTracker; } @Override @@ -40,43 +41,23 @@ class GlobalSettingsImpl implements GlobalSettings { } @Override - public UserTracker getUserTracker() { - return mUserTracker; - } - - @Override public Uri getUriFor(String name) { return Settings.Global.getUriFor(name); } @Override - public String getStringForUser(String name, int userHandle) { - return Settings.Global.getStringForUser(mContentResolver, name, - getRealUserHandle(userHandle)); - } - - @Override - public boolean putString(String name, String value, boolean overrideableByRestore) { - throw new UnsupportedOperationException( - "This method only exists publicly for Settings.System and Settings.Secure"); - } - - @Override - public boolean putStringForUser(String name, String value, int userHandle) { - return Settings.Global.putStringForUser(mContentResolver, name, value, - getRealUserHandle(userHandle)); + public String getString(String name) { + return Settings.Global.getString(mContentResolver, name); } @Override - public boolean putStringForUser(String name, String value, String tag, boolean makeDefault, - int userHandle, boolean overrideableByRestore) { - return Settings.Global.putStringForUser( - mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle), - overrideableByRestore); + public boolean putString(String name, String value) { + return Settings.Global.putString(mContentResolver, name, value); } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault) { return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java index 798033e841d5..6031a4e05629 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java @@ -22,5 +22,5 @@ package com.android.systemui.util.settings; * See {@link SettingsProxy} for details. */ -public interface SecureSettings extends SettingsProxy { +public interface SecureSettings extends UserSettingsProxy { } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java index f995436594b1..6532ce8ddf7d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java @@ -20,6 +20,8 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; +import androidx.annotation.NonNull; + import com.android.systemui.settings.UserTracker; import javax.inject.Inject; @@ -75,7 +77,7 @@ class SecureSettingsImpl implements SecureSettings { } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) { return Settings.Secure.putString(mContentResolver, name, value, tag, makeDefault); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java index b6846a34a0bd..6a9edc11add0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java @@ -18,25 +18,22 @@ package com.android.systemui.util.settings; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; -import android.os.UserHandle; import android.provider.Settings; -import com.android.systemui.settings.UserTracker; - /** - * Used to interact with Settings.Secure, Settings.Global, and Settings.System. - * + * Used to interact with mainly with Settings.Global, but can also be used for Settings.System + * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy} + * must be used instead. + * <p> * This interface can be implemented to give instance method (instead of static method) versions - * of Settings.Secure, Settings.Global, and Settings.System. It can be injected into class - * constructors and then faked or mocked as needed in tests. - * - * You can ask for {@link SecureSettings}, {@link GlobalSettings}, or {@link SystemSettings} to be - * injected as needed. - * + * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed + * in tests. + * <p> + * You can ask for {@link GlobalSettings} to be injected as needed. + * <p> * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, * normally found on {@link ContentResolver} instances, unifying setting related actions in one * place. @@ -49,29 +46,6 @@ public interface SettingsProxy { ContentResolver getContentResolver(); /** - * Returns that {@link UserTracker} this instance was constructed with. - */ - UserTracker getUserTracker(); - - /** - * Returns the user id for the associated {@link ContentResolver}. - */ - default int getUserId() { - return getContentResolver().getUserId(); - } - - /** - * Returns the actual current user handle when querying with the current user. Otherwise, - * returns the passed in user id. - */ - default int getRealUserHandle(int userHandle) { - if (userHandle != UserHandle.USER_CURRENT) { - return userHandle; - } - return getUserTracker().getUserId(); - } - - /** * Construct the content URI for a particular name/value pair, * useful for monitoring changes with a ContentObserver. * @param name to look up in the table @@ -82,7 +56,7 @@ public interface SettingsProxy { /** * Convenience wrapper around * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * + * <p> * Implicitly calls {@link #getUriFor(String)} on the passed in name. */ default void registerContentObserver(String name, ContentObserver settingsObserver) { @@ -94,13 +68,13 @@ public interface SettingsProxy { * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' */ default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { - registerContentObserverForUser(uri, settingsObserver, getUserId()); + registerContentObserver(uri, false, settingsObserver); } /** * Convenience wrapper around * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * + * <p> * Implicitly calls {@link #getUriFor(String)} on the passed in name. */ default void registerContentObserver(String name, boolean notifyForDescendants, @@ -114,53 +88,8 @@ public interface SettingsProxy { */ default void registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver) { - registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - getUriFor(name), settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - uri, false, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - registerContentObserverForUser( - getUriFor(name), notifyForDescendants, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle)); + uri, notifyForDescendants, settingsObserver); } /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */ @@ -173,22 +102,7 @@ public interface SettingsProxy { * @param name to look up in the table * @return the corresponding value, or null if not present */ - default String getString(String name) { - return getStringForUser(name, getUserId()); - } - - /**See {@link #getString(String)}. */ - String getStringForUser(String name, int userHandle); - - /** - * Store a name/value pair into the database. Values written by this method will be - * overridden if a restore happens in the future. - * - * @param name to store - * @param value to associate with the name - * @return true if the value was set, false on database errors - */ - boolean putString(String name, String value, boolean overrideableByRestore); + String getString(String name); /** * Store a name/value pair into the database. @@ -196,16 +110,7 @@ public interface SettingsProxy { * @param value to associate with the name * @return true if the value was set, false on database errors */ - default boolean putString(String name, String value) { - return putStringForUser(name, value, getUserId()); - } - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(String name, String value, int userHandle); - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); + boolean putString(String name, String value); /** * Store a name/value pair into the database. @@ -262,12 +167,7 @@ public interface SettingsProxy { * or not a valid integer. */ default int getInt(String name, int def) { - return getIntForUser(name, def, getUserId()); - } - - /** See {@link #getInt(String, int)}. */ - default int getIntForUser(String name, int def, int userHandle) { - String v = getStringForUser(name, userHandle); + String v = getString(name); try { return v != null ? Integer.parseInt(v) : def; } catch (NumberFormatException e) { @@ -292,14 +192,9 @@ public interface SettingsProxy { * * @return The setting's current value. */ - default int getInt(String name) throws Settings.SettingNotFoundException { - return getIntForUser(name, getUserId()); - } - - /** See {@link #getInt(String)}. */ - default int getIntForUser(String name, int userHandle) + default int getInt(String name) throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); + String v = getString(name); try { return Integer.parseInt(v); } catch (NumberFormatException e) { @@ -320,12 +215,7 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putInt(String name, int value) { - return putIntForUser(name, value, getUserId()); - } - - /** See {@link #putInt(String, int)}. */ - default boolean putIntForUser(String name, int value, int userHandle) { - return putStringForUser(name, Integer.toString(value), userHandle); + return putString(name, Integer.toString(value)); } /** @@ -342,12 +232,7 @@ public interface SettingsProxy { * or not a valid boolean. */ default boolean getBool(String name, boolean def) { - return getBoolForUser(name, def, getUserId()); - } - - /** See {@link #getBool(String, boolean)}. */ - default boolean getBoolForUser(String name, boolean def, int userHandle) { - return getIntForUser(name, def ? 1 : 0, userHandle) != 0; + return getInt(name, def ? 1 : 0) != 0; } /** @@ -367,14 +252,9 @@ public interface SettingsProxy { * * @return The setting's current value. */ - default boolean getBool(String name) throws Settings.SettingNotFoundException { - return getBoolForUser(name, getUserId()); - } - - /** See {@link #getBool(String)}. */ - default boolean getBoolForUser(String name, int userHandle) + default boolean getBool(String name) throws Settings.SettingNotFoundException { - return getIntForUser(name, userHandle) != 0; + return getInt(name) != 0; } /** @@ -390,12 +270,7 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putBool(String name, boolean value) { - return putBoolForUser(name, value, getUserId()); - } - - /** See {@link #putBool(String, boolean)}. */ - default boolean putBoolForUser(String name, boolean value, int userHandle) { - return putIntForUser(name, value ? 1 : 0, userHandle); + return putInt(name, value ? 1 : 0); } /** @@ -412,12 +287,12 @@ public interface SettingsProxy { * or not a valid {@code long}. */ default long getLong(String name, long def) { - return getLongForUser(name, def, getUserId()); + String valString = getString(name); + return parseLongOrUseDefault(valString, def); } - /** See {@link #getLong(String, long)}. */ - default long getLongForUser(String name, long def, int userHandle) { - String valString = getStringForUser(name, userHandle); + /** Convert a string to a long, or uses a default if the string is malformed or null */ + static long parseLongOrUseDefault(String valString, long def) { long value; try { value = valString != null ? Long.parseLong(valString) : def; @@ -443,14 +318,15 @@ public interface SettingsProxy { * @throws Settings.SettingNotFoundException Thrown if a setting by the given * name can't be found or the setting value is not an integer. */ - default long getLong(String name) throws Settings.SettingNotFoundException { - return getLongForUser(name, getUserId()); + default long getLong(String name) + throws Settings.SettingNotFoundException { + String valString = getString(name); + return parseLongOrThrow(name, valString); } - /** See {@link #getLong(String)}. */ - default long getLongForUser(String name, int userHandle) + /** Convert a string to a long, or throws an exception if the string is malformed or null */ + static long parseLongOrThrow(String name, String valString) throws Settings.SettingNotFoundException { - String valString = getStringForUser(name, userHandle); try { return Long.parseLong(valString); } catch (NumberFormatException e) { @@ -471,12 +347,7 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putLong(String name, long value) { - return putLongForUser(name, value, getUserId()); - } - - /** See {@link #putLong(String, long)}. */ - default boolean putLongForUser(String name, long value, int userHandle) { - return putStringForUser(name, Long.toString(value), userHandle); + return putString(name, Long.toString(value)); } /** @@ -493,12 +364,12 @@ public interface SettingsProxy { * or not a valid float. */ default float getFloat(String name, float def) { - return getFloatForUser(name, def, getUserId()); + String v = getString(name); + return parseFloat(v, def); } - /** See {@link #getFloat(String)}. */ - default float getFloatForUser(String name, float def, int userHandle) { - String v = getStringForUser(name, userHandle); + /** Convert a string to a float, or uses a default if the string is malformed or null */ + static float parseFloat(String v, float def) { try { return v != null ? Float.parseFloat(v) : def; } catch (NumberFormatException e) { @@ -523,14 +394,15 @@ public interface SettingsProxy { * * @return The setting's current value. */ - default float getFloat(String name) throws Settings.SettingNotFoundException { - return getFloatForUser(name, getUserId()); + default float getFloat(String name) + throws Settings.SettingNotFoundException { + String v = getString(name); + return parseFloatOrThrow(name, v); } - /** See {@link #getFloat(String, float)}. */ - default float getFloatForUser(String name, int userHandle) + /** Convert a string to a float, or throws an exception if the string is malformed or null */ + static float parseFloatOrThrow(String name, String v) throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); if (v == null) { throw new Settings.SettingNotFoundException(name); } @@ -554,11 +426,6 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putFloat(String name, float value) { - return putFloatForUser(name, value, getUserId()); - } - - /** See {@link #putFloat(String, float)} */ - default boolean putFloatForUser(String name, float value, int userHandle) { - return putStringForUser(name, Float.toString(value), userHandle); + return putString(name, Float.toString(value)); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt index 561495e9d092..74843685893c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.Flow object SettingsProxyExt { /** Returns a flow of [Unit] that is invoked each time that content is updated. */ - fun SettingsProxy.observerFlow( + fun UserSettingsProxy.observerFlow( @UserIdInt userId: Int, vararg names: String, ): Flow<Unit> { @@ -44,4 +44,22 @@ object SettingsProxyExt { awaitClose { unregisterContentObserver(observer) } } } + + /** Returns a flow of [Unit] that is invoked each time that content is updated. */ + fun SettingsProxy.observerFlow( + vararg names: String, + ): Flow<Unit> { + return conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + names.forEach { name -> registerContentObserver(name, observer) } + + awaitClose { unregisterContentObserver(observer) } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java index d57d7496381c..c67c60375b2c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java @@ -21,5 +21,5 @@ package com.android.systemui.util.settings; * * See {@link SettingsProxy} for details. */ -public interface SystemSettings extends SettingsProxy { +public interface SystemSettings extends UserSettingsProxy { } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java index fba7ddf5fe34..658b2992bfad 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java @@ -20,6 +20,8 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; +import androidx.annotation.NonNull; + import com.android.systemui.settings.UserTracker; import javax.inject.Inject; @@ -74,7 +76,7 @@ class SystemSettingsImpl implements SystemSettings { } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) { throw new UnsupportedOperationException( "This method only exists publicly for Settings.Secure and Settings.Global"); } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java new file mode 100644 index 000000000000..0d6c0f59b2d0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java @@ -0,0 +1,272 @@ +/* + * 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.systemui.util.settings; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.systemui.settings.UserTracker; + +/** + * Used to interact with per-user Settings.Secure and Settings.System settings (but not + * Settings.Global, since those do not vary per-user) + * <p> + * This interface can be implemented to give instance method (instead of static method) versions + * of Settings.Secure and Settings.System. It can be injected into class constructors and then + * faked or mocked as needed in tests. + * <p> + * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed. + * <p> + * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, + * normally found on {@link ContentResolver} instances, unifying setting related actions in one + * place. + */ +public interface UserSettingsProxy extends SettingsProxy { + + /** + * Returns that {@link UserTracker} this instance was constructed with. + */ + UserTracker getUserTracker(); + + /** + * Returns the user id for the associated {@link ContentResolver}. + */ + default int getUserId() { + return getContentResolver().getUserId(); + } + + /** + * Returns the actual current user handle when querying with the current user. Otherwise, + * returns the passed in user id. + */ + default int getRealUserHandle(int userHandle) { + if (userHandle != UserHandle.USER_CURRENT) { + return userHandle; + } + return getUserTracker().getUserId(); + } + + @Override + default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { + registerContentObserverForUser(uri, settingsObserver, getUserId()); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' + */ + @Override + default void registerContentObserver(Uri uri, boolean notifyForDescendants, + ContentObserver settingsObserver) { + registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId()); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + * + * Implicitly calls {@link #getUriFor(String)} on the passed in name. + */ + default void registerContentObserverForUser( + String name, ContentObserver settingsObserver, int userHandle) { + registerContentObserverForUser( + getUriFor(name), settingsObserver, userHandle); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + */ + default void registerContentObserverForUser( + Uri uri, ContentObserver settingsObserver, int userHandle) { + registerContentObserverForUser( + uri, false, settingsObserver, userHandle); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + * + * Implicitly calls {@link #getUriFor(String)} on the passed in name. + */ + default void registerContentObserverForUser( + String name, boolean notifyForDescendants, ContentObserver settingsObserver, + int userHandle) { + registerContentObserverForUser( + getUriFor(name), notifyForDescendants, settingsObserver, userHandle); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + */ + default void registerContentObserverForUser( + Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, + int userHandle) { + getContentResolver().registerContentObserver( + uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle)); + } + + /** + * Look up a name in the database. + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + @Override + default String getString(String name) { + return getStringForUser(name, getUserId()); + } + + /**See {@link #getString(String)}. */ + String getStringForUser(String name, int userHandle); + + /** + * Store a name/value pair into the database. Values written by this method will be + * overridden if a restore happens in the future. + * + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + boolean putString(String name, String value, boolean overrideableByRestore); + + @Override + default boolean putString(String name, String value) { + return putStringForUser(name, value, getUserId()); + } + + /** See {@link #putString(String, String)}. */ + boolean putStringForUser(String name, String value, int userHandle); + + /** See {@link #putString(String, String)}. */ + boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); + + @Override + default int getInt(String name, int def) { + return getIntForUser(name, def, getUserId()); + } + + /** See {@link #getInt(String, int)}. */ + default int getIntForUser(String name, int def, int userHandle) { + String v = getStringForUser(name, userHandle); + try { + return v != null ? Integer.parseInt(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + @Override + default int getInt(String name) throws Settings.SettingNotFoundException { + return getIntForUser(name, getUserId()); + } + + /** See {@link #getInt(String)}. */ + default int getIntForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String v = getStringForUser(name, userHandle); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { + throw new Settings.SettingNotFoundException(name); + } + } + + @Override + default boolean putInt(String name, int value) { + return putIntForUser(name, value, getUserId()); + } + + /** See {@link #putInt(String, int)}. */ + default boolean putIntForUser(String name, int value, int userHandle) { + return putStringForUser(name, Integer.toString(value), userHandle); + } + + @Override + default boolean getBool(String name, boolean def) { + return getBoolForUser(name, def, getUserId()); + } + + /** See {@link #getBool(String, boolean)}. */ + default boolean getBoolForUser(String name, boolean def, int userHandle) { + return getIntForUser(name, def ? 1 : 0, userHandle) != 0; + } + + @Override + default boolean getBool(String name) throws Settings.SettingNotFoundException { + return getBoolForUser(name, getUserId()); + } + + /** See {@link #getBool(String)}. */ + default boolean getBoolForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + return getIntForUser(name, userHandle) != 0; + } + + @Override + default boolean putBool(String name, boolean value) { + return putBoolForUser(name, value, getUserId()); + } + + /** See {@link #putBool(String, boolean)}. */ + default boolean putBoolForUser(String name, boolean value, int userHandle) { + return putIntForUser(name, value ? 1 : 0, userHandle); + } + + /** See {@link #getLong(String, long)}. */ + default long getLongForUser(String name, long def, int userHandle) { + String valString = getStringForUser(name, userHandle); + return SettingsProxy.parseLongOrUseDefault(valString, def); + } + + /** See {@link #getLong(String)}. */ + default long getLongForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String valString = getStringForUser(name, userHandle); + return SettingsProxy.parseLongOrThrow(name, valString); + } + + /** See {@link #putLong(String, long)}. */ + default boolean putLongForUser(String name, long value, int userHandle) { + return putStringForUser(name, Long.toString(value), userHandle); + } + + /** See {@link #getFloat(String)}. */ + default float getFloatForUser(String name, float def, int userHandle) { + String v = getStringForUser(name, userHandle); + return SettingsProxy.parseFloat(v, def); + } + + /** See {@link #getFloat(String, float)}. */ + default float getFloatForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String v = getStringForUser(name, userHandle); + return SettingsProxy.parseFloatOrThrow(name, v); + } + + /** See {@link #putFloat(String, float)} */ + default boolean putFloatForUser(String name, float value, int userHandle) { + return putStringForUser(name, Float.toString(value), userHandle); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index ea4d31bca035..d65a69c62072 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -77,6 +77,8 @@ import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.concurrency.ThreadFactory; +import dalvik.annotation.optimization.NeverCompile; + import java.io.PrintWriter; import java.util.HashMap; import java.util.List; @@ -286,6 +288,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa return new MediaSessions(context, looper, callbacks); } + @NeverCompile public void dump(PrintWriter pw, String[] args) { pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:"); pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 727d649c8118..929b91cf6993 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -135,6 +135,9 @@ import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; import com.android.systemui.util.RoundedCornerProgressDrawable; +import com.android.systemui.util.settings.SecureSettings; + +import dagger.Lazy; import java.io.PrintWriter; import java.util.ArrayList; @@ -304,6 +307,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private @DevicePostureController.DevicePostureInt int mDevicePosture; private int mOrientation; private final FeatureFlags mFeatureFlags; + private final Lazy<SecureSettings> mSecureSettings; + private int mDialogTimeoutMillis; public VolumeDialogImpl( Context context, @@ -320,7 +325,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DevicePostureController devicePostureController, Looper looper, DumpManager dumpManager, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + Lazy<SecureSettings> secureSettings) { mFeatureFlags = featureFlags; mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); @@ -351,6 +357,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mUseBackgroundBlur = mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur); mInteractionJankMonitor = interactionJankMonitor; + mSecureSettings = secureSettings; + mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS; dumpManager.registerDumpable("VolumeDialogImpl", this); @@ -515,6 +523,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mDialog.setContentView(R.layout.volume_dialog); mDialogView = mDialog.findViewById(R.id.volume_dialog); mDialogView.setAlpha(0); + mDialogTimeoutMillis = mSecureSettings.get().getInt( + Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, DIALOG_TIMEOUT_MILLIS); mDialog.setCanceledOnTouchOutside(true); mDialog.setOnShowListener(dialog -> { mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); @@ -527,7 +537,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .alpha(1) .translationX(0) .setDuration(mDialogShowAnimationDurationMs) - .setListener(getJankListener(getDialogView(), TYPE_SHOW, DIALOG_TIMEOUT_MILLIS)) + .setListener(getJankListener(getDialogView(), TYPE_SHOW, mDialogTimeoutMillis)) .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) .withEndAction(() -> { if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) { @@ -1514,7 +1524,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, AccessibilityManager.FLAG_CONTENT_TEXT | AccessibilityManager.FLAG_CONTENT_CONTROLS); } - return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS, + return mAccessibilityMgr.getRecommendedTimeoutMillis(mDialogTimeoutMillis, AccessibilityManager.FLAG_CONTENT_CONTROLS); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index cc9f3e14216e..e3b3c21d5d0d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.volume.CsdWarningDialog; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; @@ -38,6 +39,7 @@ import com.android.systemui.volume.VolumeDialogImpl; import com.android.systemui.volume.VolumePanelFactory; import dagger.Binds; +import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -63,7 +65,8 @@ public interface VolumeModule { CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, DumpManager dumpManager, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + Lazy<SecureSettings> secureSettings) { VolumeDialogImpl impl = new VolumeDialogImpl( context, volumeDialogController, @@ -79,7 +82,8 @@ public interface VolumeModule { devicePostureController, Looper.getMainLooper(), dumpManager, - featureFlags); + featureFlags, + secureSettings); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); impl.setSilentMode(false); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 62f9a9dcce70..20d4eb907944 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -810,7 +810,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") @@ -826,8 +825,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") @@ -844,8 +842,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") @@ -863,8 +860,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") @@ -880,7 +876,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Lockscreen, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason") @@ -898,7 +893,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Gone, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 989164ebf174..3b8e02f7455a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -20,7 +20,6 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.animation.LayoutTransition; import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; @@ -69,7 +68,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardClockSwitch mKeyguardClockSwitch; @Mock protected FrameLayout mMediaHostContainer; - @Mock protected LayoutTransition mMediaLayoutTransition; @Before public void setup() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 9a908d778943..948942fbce3a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -16,19 +16,24 @@ package com.android.keyguard; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.animation.LayoutTransition; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import com.android.app.animation.Interpolators; +import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.plugins.ClockConfig; import com.android.systemui.plugins.ClockController; import com.android.systemui.res.R; @@ -39,6 +44,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.lang.reflect.Field; + @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner.class) @@ -142,19 +149,7 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll } @Test - public void onInit_addsOnLayoutChangeListenerToMediaHostContainer() { - when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( - mMediaHostContainer); - - mController.onInit(); - - ArgumentCaptor<View.OnLayoutChangeListener> captor = - ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); - verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture()); - } - - @Test - public void clockSwitchHeightChanged_mediaChangingLayoutTransitionEnabled() { + public void clockSwitchHeightChanged_animatesMediaHostContainer() { when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( mMediaHostContainer); @@ -167,6 +162,10 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`. // Below here is the actual test. + ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion; + ViewHierarchyAnimator.Companion spiedAnimator = spy(animator); + setCompanion(spiedAnimator); + View.OnLayoutChangeListener listener = captor.getValue(); mController.setSplitShadeEnabled(true); @@ -174,17 +173,20 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true); when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE); when(mMediaHostContainer.getHeight()).thenReturn(200); - when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition); when(mKeyguardClockSwitch.getHeight()).thenReturn(0); listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */ 0, /* oldBottom = */ 200); - verify(mMediaLayoutTransition).enableTransitionType(LayoutTransition.CHANGING); + verify(spiedAnimator).animateNextUpdate(mMediaHostContainer, + Interpolators.STANDARD, /* duration= */ 500L, /* animateChildren= */ false); + + // Resets ViewHierarchyAnimator.Companion to its original value + setCompanion(animator); } @Test - public void clockSwitchHeightNotChanged_mediaChangingLayoutTransitionNotEnabled() { + public void clockSwitchHeightNotChanged_doesNotAnimateMediaOutputContainer() { when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( mMediaHostContainer); @@ -197,6 +199,10 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`. // Below here is the actual test. + ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion; + ViewHierarchyAnimator.Companion spiedAnimator = spy(animator); + setCompanion(spiedAnimator); + View.OnLayoutChangeListener listener = captor.getValue(); mController.setSplitShadeEnabled(true); @@ -204,36 +210,24 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true); when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE); when(mMediaHostContainer.getHeight()).thenReturn(200); - when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition); when(mKeyguardClockSwitch.getHeight()).thenReturn(200); listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */ 0, /* oldBottom = */ 200); - verify(mMediaLayoutTransition, never()).enableTransitionType(LayoutTransition.CHANGING); - } + verify(spiedAnimator, never()).animateNextUpdate(any(), any(), anyLong(), anyBoolean()); - @Test - public void onMediaHostContainerLayout_disablesChangingLayoutTransition() { - when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( - mMediaHostContainer); - - mController.onInit(); - - ArgumentCaptor<View.OnLayoutChangeListener> captor = - ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); - verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture()); - - // Above here is the same as `onInit_addsOnLayoutChangeListenerToMediaHostContainer`. - // Below here is the actual test. - - View.OnLayoutChangeListener listener = captor.getValue(); - - when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition); + // Resets ViewHierarchyAnimator.Companion to its original value + setCompanion(animator); + } - when(mMediaLayoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).thenReturn( - true); - listener.onLayoutChange(mMediaHostContainer, 1, 2, 3, 4, 1, 2, 3, 4); - verify(mMediaLayoutTransition).disableTransitionType(LayoutTransition.CHANGING); + private void setCompanion(ViewHierarchyAnimator.Companion companion) { + try { + Field field = ViewHierarchyAnimator.class.getDeclaredField("Companion"); + field.setAccessible(true); + field.set(null, companion); + } catch (Exception e) { + throw new RuntimeException(e); + } } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt index 58d372c68c55..86439e557f8b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt @@ -1,6 +1,5 @@ package com.android.keyguard -import android.animation.LayoutTransition import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -36,20 +35,6 @@ class KeyguardStatusViewTest : SysuiTestCase() { } @Test - fun mediaViewHasLayoutTransitionInDisabledState() { - val layoutTransition = (mediaView as ViewGroup).layoutTransition - assertThat(layoutTransition).isNotNull() - assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_APPEARING)) - .isFalse() - assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_DISAPPEARING)) - .isFalse() - assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.APPEARING)).isFalse() - assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.DISAPPEARING)) - .isFalse() - assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).isFalse() - } - - @Test fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() { val translationY = 1234f diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt index a7e7dd074a33..2b51ac5e3187 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.animation import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.core.animation.doOnEnd +import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.doOnEnd @@ -30,6 +31,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest @RunWithLooper +@FlakyTest(bugId = 302149604) class AnimatorTestRuleOrderTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index e9e9624580b3..ebe13feb0f6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -29,7 +29,6 @@ import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.view.MotionEvent import android.view.Surface -import android.view.Surface.ROTATION_0 import android.view.Surface.Rotation import android.view.View import android.view.WindowManager @@ -45,7 +44,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -141,7 +139,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { ) { controllerOverlay = UdfpsControllerOverlay( context, - fingerprintManager, inflater, windowManager, accessibilityManager, @@ -155,7 +152,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { keyguardStateController, unlockedScreenOffAnimationController, udfpsDisplayMode, - secureSettings, REQUEST_ID, reason, controllerCallback, @@ -165,7 +161,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, - udfpsUtils, udfpsKeyguardAccessibilityDelegate, udfpsKeyguardViewModels, ) @@ -212,8 +207,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { val lp = layoutParamsCaptor.value assertThat(lp.x).isEqualTo(0) assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(SENSOR_WIDTH) - assertThat(lp.height).isEqualTo(SENSOR_HEIGHT) + assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) + assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) } } @@ -230,8 +225,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { val lp = layoutParamsCaptor.value assertThat(lp.x).isEqualTo(0) assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(SENSOR_WIDTH) - assertThat(lp.height).isEqualTo(SENSOR_HEIGHT) + assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) + assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) } } @@ -247,9 +242,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { // Sensor should be in the bottom left corner in ROTATION_90. val lp = layoutParamsCaptor.value assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(DISPLAY_WIDTH - SENSOR_WIDTH) - assertThat(lp.width).isEqualTo(SENSOR_HEIGHT) - assertThat(lp.height).isEqualTo(SENSOR_WIDTH) + assertThat(lp.y).isEqualTo(0) + assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) + assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) } } @@ -264,10 +259,10 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { // Sensor should be in the top right corner in ROTATION_270. val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(DISPLAY_HEIGHT - SENSOR_HEIGHT) + assertThat(lp.x).isEqualTo(0) assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(SENSOR_HEIGHT) - assertThat(lp.height).isEqualTo(SENSOR_WIDTH) + assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) + assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) } } @@ -345,11 +340,10 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } @Test - fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) { + fun smallOverlayOnEnrollmentWithA11y() = withRotation(Surface.ROTATION_0) { withReason(REASON_ENROLL_ENROLLING) { // When a11y enabled during enrollment whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true) - whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true) controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( @@ -363,22 +357,4 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height()) } } - - @Test - fun fullScreenOverlayWithNewTouchDetectionEnabled() = withRotation(ROTATION_0) { - withReason(REASON_AUTH_KEYGUARD) { - whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true) - - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() - ) - - // Layout params should use natural display width and height - val lp = layoutParamsCaptor.value - assertThat(lp.width).isEqualTo(overlayParams.naturalDisplayWidth) - assertThat(lp.height).isEqualTo(overlayParams.naturalDisplayHeight) - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index a36f4e9ac217..dcb53984ad87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -88,7 +88,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; @@ -105,7 +104,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; @@ -122,7 +120,6 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import javax.inject.Provider; @@ -206,8 +203,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock - private AlternateUdfpsTouchProvider mAlternateTouchProvider; - @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private SinglePointerTouchProcessor mSinglePointerTouchProcessor; @@ -216,8 +211,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock - private SecureSettings mSecureSettings; - @Mock private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; @Mock private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @@ -239,7 +232,6 @@ public class UdfpsControllerTest extends SysuiTestCase { private ScreenLifecycle.Observer mScreenObserver; private FingerprintSensorPropertiesInternal mOpticalProps; private FingerprintSensorPropertiesInternal mUltrasonicProps; - private UdfpsUtils mUdfpsUtils; @Mock private InputManager mInputManager; @Mock @@ -250,8 +242,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mContext.getOrCreateTestableResources() .addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false); - mUdfpsUtils = new UdfpsUtils(); - when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)) .thenReturn(mUdfpsView); when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) @@ -292,24 +282,13 @@ public class UdfpsControllerTest extends SysuiTestCase { // Create a fake background executor. mBiometricExecutor = new FakeExecutor(new FakeSystemClock()); - initUdfpsController(true /* hasAlternateTouchProvider */); - } - - - private void initUdfpsController(boolean hasAlternateTouchProvider) { - initUdfpsController(mOpticalProps, hasAlternateTouchProvider); + initUdfpsController(mOpticalProps); } - private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps, - boolean hasAlternateTouchProvider) { + private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps) { reset(mFingerprintManager); reset(mScreenLifecycle); - final Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider = - hasAlternateTouchProvider ? Optional.of( - (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider) - : Optional.empty(); - mUdfpsController = new UdfpsController( mContext, new FakeExecution(), @@ -339,15 +318,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, - alternateTouchProvider, mBiometricExecutor, mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker, mAlternateBouncerInteractor, - mSecureSettings, mInputManager, - mUdfpsUtils, mock(KeyguardFaceAuthInteractor.class), mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels @@ -374,17 +350,15 @@ public class UdfpsControllerTest extends SysuiTestCase { public void onActionDownTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); + MotionEvent downEvent = obtainMotionEvent(ACTION_DOWN, 0, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); @@ -408,16 +382,14 @@ public class UdfpsControllerTest extends SysuiTestCase { throws RemoteException { // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_MOVE is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); if (stale) { mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId); @@ -436,22 +408,22 @@ public class UdfpsControllerTest extends SysuiTestCase { public void onMultipleTouch_whenCanDismissLockScreen_entersDeviceOnce() throws RemoteException { // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); - // WHEN multiple touches are received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + // GIVEN that the overlay is showing + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); mBiometricExecutor.runAllReady(); moveEvent.recycle(); @@ -593,22 +565,17 @@ public class UdfpsControllerTest extends SysuiTestCase { private static class TestParams { public final FingerprintSensorPropertiesInternal sensorProps; - public final boolean hasAlternateTouchProvider; - TestParams(FingerprintSensorPropertiesInternal sensorProps, - boolean hasAlternateTouchProvider) { + TestParams(FingerprintSensorPropertiesInternal sensorProps) { this.sensorProps = sensorProps; - this.hasAlternateTouchProvider = hasAlternateTouchProvider; } } private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) { for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, mUltrasonicProps)) { - for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) { - initUdfpsController(sensorProps, hasAlternateTouchProvider); - testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider)); - } + initUdfpsController(sensorProps); + testParamsConsumer.accept(new TestParams(sensorProps)); } } @@ -621,23 +588,33 @@ public class UdfpsControllerTest extends SysuiTestCase { private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized( TestParams testParams) throws RemoteException { reset(mUdfpsView); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner. + final int pointerId = 0; final int displayWidth = 1080; final int displayHeight = 1920; - final float scaleFactor = 0.75f; // This means the native resolution is 1440x2560. + final float scaleFactor = 1f; // This means the native resolution is 1440x2560. final float touchMinor = 10f; final float touchMajor = 20f; + final float orientation = 30f; // Expecting a touch at the very bottom right corner in native orientation and resolution. - final int expectedX = (int) (displayWidth / scaleFactor); - final int expectedY = (int) (displayHeight / scaleFactor); + final float expectedX = displayWidth / scaleFactor; + final float expectedY = displayHeight / scaleFactor; final float expectedMinor = touchMinor / scaleFactor; final float expectedMajor = touchMajor / scaleFactor; // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN a valid touch on sensor + NormalizedTouchData touchData = new NormalizedTouchData(pointerId, displayWidth, + displayHeight, touchMinor, touchMajor, orientation, 0L, 0L); + TouchProcessorResult processorDownResult = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.DOWN, 1, touchData); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorDownResult); // Show the overlay. mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, @@ -654,21 +631,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); // Test ROTATION_90 - reset(mAlternateTouchProvider); reset(mFingerprintManager); mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, @@ -677,21 +645,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, displayHeight, 0, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); // Test ROTATION_270 - reset(mAlternateTouchProvider); reset(mFingerprintManager); mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, @@ -700,21 +659,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, 0, displayWidth, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); // Test ROTATION_180 - reset(mAlternateTouchProvider); reset(mFingerprintManager); mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, @@ -724,18 +674,10 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); } @Test @@ -744,46 +686,36 @@ public class UdfpsControllerTest extends SysuiTestCase { } private void fingerDownParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker, + reset(mUdfpsView, mFingerprintManager, mLatencyTracker, mKeyguardUpdateMonitor); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - // GIVEN that the overlay is showing + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.DOWN, 1 /* pointerId */, touchData); + + initUdfpsController(testParams.sensorProps); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); // WHEN ACTION_DOWN is received + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - mFgExecutor.runAllReady(); - // THEN the touch provider is notified about onPointerDown. - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f), - eq(0f)); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyInt(), anyFloat(), anyFloat()); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f)); - verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - } + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); // AND display configuration begins if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { @@ -800,33 +732,20 @@ public class UdfpsControllerTest extends SysuiTestCase { // AND onDisplayConfigured notifies FingerprintManager about onUiReady mOnDisplayConfiguredCaptor.getValue().run(); mBiometricExecutor.runAllReady(); - if (testParams.hasAlternateTouchProvider) { - InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker); - inOrder.verify(mAlternateTouchProvider).onUiReady(); - inOrder.verify(mLatencyTracker).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mFingerprintManager, never()).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); - } else { - InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); - inOrder.verify(mFingerprintManager).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId)); - inOrder.verify(mLatencyTracker).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mAlternateTouchProvider, never()).onUiReady(); - } + InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); + inOrder.verify(mFingerprintManager).onUdfpsUiEvent( + eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId)); + inOrder.verify(mLatencyTracker).onActionEnd( + eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); } else { verify(mFingerprintManager, never()).onUdfpsUiEvent( eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); - verify(mAlternateTouchProvider, never()).onUiReady(); verify(mLatencyTracker, never()).onActionEnd( eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); } } - - @Test public void aodInterrupt() { runWithAllParams(this::aodInterruptParameterized); @@ -834,8 +753,9 @@ public class UdfpsControllerTest extends SysuiTestCase { private void aodInterruptParameterized(TestParams testParams) throws RemoteException { mUdfpsController.cancelAodSendFingerUpAction(); - reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor); + reset(mUdfpsView, mFingerprintManager, mKeyguardUpdateMonitor); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); // GIVEN that the overlay is showing and screen is on and fp is running mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, @@ -855,19 +775,8 @@ public class UdfpsControllerTest extends SysuiTestCase { } mBiometricExecutor.runAllReady(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), - eq(3f) /* minor */, eq(2f) /* major */); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyInt(), anyFloat(), anyFloat()); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */, - eq(2f) /* major */); - verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - } + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); } @Test @@ -907,10 +816,12 @@ public class UdfpsControllerTest extends SysuiTestCase { private void onFingerUp_displayConfigurationParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); + + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); @@ -918,7 +829,8 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); // WHEN up-action received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); mBiometricExecutor.runAllReady(); @@ -931,7 +843,8 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isDisplayConfigured()).thenReturn(false); // WHEN up-action received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); mBiometricExecutor.runAllReady(); @@ -1015,16 +928,19 @@ public class UdfpsControllerTest extends SysuiTestCase { private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the ACTION_UP event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); @@ -1033,7 +949,8 @@ public class UdfpsControllerTest extends SysuiTestCase { } // WHEN ACTION_UP is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); mBiometricExecutor.runAllReady(); @@ -1043,16 +960,13 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isDisplayConfigured()).thenReturn(false); // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - // WHEN ACTION_MOVE is received - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); mFgExecutor.runAllReady(); if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { @@ -1122,24 +1036,16 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException { - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true); // WHEN ACTION_HOVER is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); enterEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); - mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent); - moveEvent.recycle(); // THEN tick haptic is played verify(mVibrator).vibrate( @@ -1159,24 +1065,16 @@ public class UdfpsControllerTest extends SysuiTestCase { public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled() throws RemoteException { when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - // GIVEN that the overlay is showing and a11y touch exploration enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true); // WHEN ACTION_HOVER is received - verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); enterEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); - mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent); - moveEvent.recycle(); // THEN context click haptic is played verify(mVibrator).performHapticFeedback( @@ -1187,26 +1085,16 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); // THEN NO haptic played verify(mVibrator, never()).vibrate( @@ -1221,80 +1109,26 @@ public class UdfpsControllerTest extends SysuiTestCase { public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled() throws RemoteException { when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); // THEN NO haptic played verify(mVibrator, never()).performHapticFeedback(any(), anyInt()); } @Test - public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath() - throws RemoteException { - // Disable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_MOVE is received - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // AND ACTION_UP is received - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the old FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt()); - } - - @Test public void fingerDown_falsingManagerInformed() throws RemoteException { final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenAcceptFingerDownEvent(); + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( @@ -1308,85 +1142,46 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); } - @Test - public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath() - throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp = - givenAcceptFingerDownEvent(); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDownAndUp.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDownAndUp.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the new FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - } - - private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent() + private Pair<TouchProcessorResult, TouchProcessorResult> givenFingerEvent( + InteractionEvent event1, InteractionEvent event2, boolean a11y) throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1 /* pointerId */, touchData); + event1, 1 /* pointerId */, touchData); final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.UP, 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + event2, 1 /* pointerId */, touchData); // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + initUdfpsController(mOpticalProps); // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + if (a11y) { + verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); + } else { + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + } return new Pair<>(processorResultDown, processorResultUp); } @Test - public void onTouch_WithNewTouchDetection_forwardToKeyguard() throws RemoteException { + public void onTouch_forwardToKeyguard() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( InteractionEvent.UNCHANGED, -1 /* pointerOnSensorId */, touchData); - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); @@ -1402,47 +1197,23 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN the touch is forwarded to Keyguard verify(mStatusBarKeyguardViewManager).onTouch(downEvent); - downEvent.recycle(); } @Test - public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + public void onTouch_pilferPointer() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); // WHEN ACTION_DOWN is received when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); + touchProcessorResult.first); MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); // WHEN ACTION_MOVE is received after - final TouchProcessorResult processorResultUnchanged = - new TouchProcessorResult.ProcessedTouch( - InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData); when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUnchanged); + touchProcessorResult.second); event.setAction(ACTION_MOVE); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); @@ -1453,25 +1224,13 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_doNotPilferPointer() throws RemoteException { + public void onTouch_doNotPilferPointer() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultUnchanged = new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + -1 /* pointerId */, touchData); - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to not accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); @@ -1491,36 +1250,17 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_pilferPointerWhenAltBouncerShowing() + public void onTouch_pilferPointerWhenAltBouncerShowing() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultUnchanged = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to not accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); - // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + // WHEN alternate bouncer is showing when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); // WHEN ACTION_DOWN is received and touch is not within sensor when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUnchanged); + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); @@ -1531,32 +1271,10 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer() + public void onTouch_doNotProcessTouchWhenPullingUpBouncer() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultMove = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_MOVE event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true); @@ -1564,7 +1282,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // WHEN ACTION_MOVE is received and touch is within sensor when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultMove); + touchProcessorResult.first); MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); mBiometricExecutor.runAllReady(); @@ -1578,40 +1296,19 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test - public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible() + public void onTouch_qsDrag_processesTouchWhenAlternateBouncerVisible() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultMove = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_MOVE event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - // GIVEN swipe down for QS when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false); when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(1f); // WHEN ACTION_MOVE is received and touch is within sensor when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultMove); + touchProcessorResult.first); MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); mBiometricExecutor.runAllReady(); @@ -1689,43 +1386,4 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN vibrate is used verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); } - - @Test - public void aodInterrupt_withNewTouchDetection() throws RemoteException { - mUdfpsController.cancelAodSendFingerUpAction(); - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // GIVEN that the overlay is showing and screen is on and fp is running - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, 0, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - - // WHEN fingerprint is requested because of AOD interrupt - mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); - - // Check case where touch driver sends touch to UdfpsView as well - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - - mBiometricExecutor.runAllReady(); - - // THEN only one onPointerDown is sent - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java index 3276e6624004..e512adcc0542 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java @@ -30,7 +30,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeExpansionChangeEvent; @@ -116,7 +115,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { } public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(false, false); + return createUdfpsKeyguardViewController(false); } public void captureKeyGuardViewManagerCallback() { @@ -126,8 +125,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { } protected UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController( - boolean useModernBouncer, boolean useExpandedOverlay) { - mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay); + boolean useModernBouncer) { UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy( mView, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java index b018a3e2ca6c..21928cd606ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java @@ -18,15 +18,12 @@ package com.android.systemui.biometrics; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.TestableLooper; -import android.view.MotionEvent; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -44,8 +41,7 @@ public class UdfpsKeyguardViewLegacyControllerTest extends UdfpsKeyguardViewLegacyControllerBaseTest { @Override public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(/* useModernBouncer */ false, - /* useExpandedOverlay */ false); + return createUdfpsKeyguardViewController(/* useModernBouncer */ false); } @Test @@ -216,37 +212,4 @@ public class UdfpsKeyguardViewLegacyControllerTest extends sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); assertTrue(mController.shouldPauseAuth()); } - - @Test - // TODO(b/259264861): Tracking Bug - public void testUdfpsExpandedOverlayOn() { - // GIVEN view is attached and useExpandedOverlay is true - mController = createUdfpsKeyguardViewController(false, true); - mController.onViewAttached(); - captureKeyGuardViewManagerCallback(); - - // WHEN a touch is received - mKeyguardViewManagerCallback.onTouch( - MotionEvent.obtain(0, 0, 0, 0, 0, 0)); - - // THEN udfpsController onTouch is not called - assertTrue(mView.mUseExpandedOverlay); - verify(mUdfpsController, never()).onTouch(any()); - } - - @Test - // TODO(b/259264861): Tracking Bug - public void testUdfpsExpandedOverlayOff() { - // GIVEN view is attached and useExpandedOverlay is false - mController.onViewAttached(); - captureKeyGuardViewManagerCallback(); - - // WHEN a touch is received - mKeyguardViewManagerCallback.onTouch( - MotionEvent.obtain(0, 0, 0, 0, 0, 0)); - - // THEN udfpsController onTouch is called - assertFalse(mView.mUseExpandedOverlay); - verify(mUdfpsController).onTouch(any()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index da4548bc14c0..02ee53879ea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -106,10 +106,7 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : mock(SystemClock::class.java), mKeyguardUpdateMonitor, ) - return createUdfpsKeyguardViewController( - /* useModernBouncer */ true, /* useExpandedOverlay */ - false - ) + return createUdfpsKeyguardViewController(/* useModernBouncer */ true) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 0c8e7a5d356a..9fbe09619ff1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.biometrics -import android.graphics.PointF -import android.graphics.RectF import android.hardware.biometrics.SensorLocationInternal import android.testing.TestableLooper import android.testing.ViewUtils @@ -42,7 +40,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.nullable import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -import org.mockito.Mockito.`when` as whenever private const val SENSOR_X = 50 private const val SENSOR_Y = 250 @@ -80,56 +77,7 @@ class UdfpsViewTest : SysuiTestCase() { ViewUtils.detachView(view) } - @Test - fun layoutSizeFitsSensor() { - val params = withArgCaptor<RectF> { - verify(animationViewController).onSensorRectUpdated(capture()) - } - assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS) - assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS) - } - - @Test - fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true) - - @Test - fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false) - - private fun isWithinSensorArea(paused: Boolean) { - whenever(animationViewController.shouldPauseAuth()).thenReturn(paused) - whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f)) - val end = (SENSOR_RADIUS * 2) - 1 - for (x in 1 until end) { - for (y in 1 until end) { - assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused) - } - } - } - - @Test - fun isWithinSensorAreaWhenTranslated() { - val offset = PointF(100f, 200f) - whenever(animationViewController.touchTranslation).thenReturn(offset) - val end = (SENSOR_RADIUS * 2) - 1 - for (x in 0 until offset.x.toInt() step 2) { - for (y in 0 until offset.y.toInt() step 2) { - assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse() - } - } - for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) { - for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) { - assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue() - } - } - } - - @Test - fun isNotWithinSensorArea() { - whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f)) - assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())) - .isFalse() - assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse() - } + // TODO: Add test to verify view is size of screen @Test fun startAndStopIllumination() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt new file mode 100644 index 000000000000..30a5497d0a14 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt @@ -0,0 +1,115 @@ +/* + * 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.systemui.communal.data.repository + +import android.content.pm.UserInfo +import android.provider.Settings +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.log.LogBuffer +import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalTutorialRepositoryImplTest : SysuiTestCase() { + private lateinit var secureSettings: FakeSettings + private lateinit var userRepository: FakeUserRepository + private lateinit var userTracker: FakeUserTracker + private lateinit var logBuffer: LogBuffer + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + logBuffer = FakeLogBuffer.Factory.create() + secureSettings = FakeSettings() + userRepository = FakeUserRepository() + val listOfUserInfo = listOf(MAIN_USER_INFO) + userRepository.setUserInfos(listOfUserInfo) + + userTracker = FakeUserTracker() + userTracker.set( + userInfos = listOfUserInfo, + selectedUserIndex = 0, + ) + } + + @Test + fun tutorialSettingState_defaultToNotStarted() = + testScope.runTest { + val repository = initCommunalTutorialRepository() + val tutorialSettingState = collectLastValue(repository.tutorialSettingState)() + assertThat(tutorialSettingState) + .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED) + } + + @Test + fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() = + testScope.runTest { + val repository = initCommunalTutorialRepository() + setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_STARTED) + val tutorialSettingState = collectLastValue(repository.tutorialSettingState)() + assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED) + } + + @Test + fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() = + testScope.runTest { + val repository = initCommunalTutorialRepository() + setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + val tutorialSettingState = collectLastValue(repository.tutorialSettingState)() + assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + } + + private fun initCommunalTutorialRepository(): CommunalTutorialRepositoryImpl { + return CommunalTutorialRepositoryImpl( + testScope.backgroundScope, + testDispatcher, + userRepository, + secureSettings, + userTracker, + logBuffer + ) + } + + private fun setTutorialStateSetting( + @Settings.Secure.HubModeTutorialState state: Int, + user: UserInfo = MAIN_USER_INFO + ) { + secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id) + } + + companion object { + private val MAIN_USER_INFO = + UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt new file mode 100644 index 000000000000..0a9a15e06b1b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -0,0 +1,113 @@ +/* + * 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.systemui.communal.domain.interactor + +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class CommunalTutorialInteractorTest : SysuiTestCase() { + + @Mock private lateinit var userTracker: UserTracker + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var underTest: CommunalTutorialInteractor + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var keyguardInteractor: KeyguardInteractor + private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + val withDeps = KeyguardInteractorFactory.create() + keyguardInteractor = withDeps.keyguardInteractor + keyguardRepository = withDeps.repository + communalTutorialRepository = FakeCommunalTutorialRepository() + + underTest = + CommunalTutorialInteractor( + keyguardInteractor = keyguardInteractor, + communalTutorialRepository = communalTutorialRepository, + ) + + whenever(userTracker.userHandle).thenReturn(mock()) + } + + @Test + fun tutorialUnavailable_whenKeyguardNotVisible() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) + keyguardRepository.setKeyguardShowing(false) + assertThat(isTutorialAvailable).isFalse() + } + + @Test + fun tutorialUnavailable_whenTutorialIsCompleted() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + assertThat(isTutorialAvailable).isFalse() + } + + @Test + fun tutorialAvailable_whenTutorialNotStarted() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) + assertThat(isTutorialAvailable).isTrue() + } + + @Test + fun tutorialAvailable_whenTutorialIsStarted() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) + assertThat(isTutorialAvailable).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt index 41a8be9663b7..33a666700877 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt @@ -6,6 +6,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import org.junit.Before import org.junit.Test @@ -18,6 +19,7 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class DefaultCommunalBlueprintTest : SysuiTestCase() { + @Mock private lateinit var hubSection: DefaultCommunalHubSection @Mock private lateinit var widgetSection: DefaultCommunalWidgetSection private lateinit var blueprint: DefaultCommunalBlueprint @@ -25,13 +27,14 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - blueprint = DefaultCommunalBlueprint(widgetSection) + blueprint = DefaultCommunalBlueprint(hubSection, widgetSection) } @Test fun addView() { val constraintLayout = ConstraintLayout(context, null) blueprint.replaceViews(null, constraintLayout) + verify(hubSection).addViews(constraintLayout) verify(widgetSection).addViews(constraintLayout) } @@ -39,6 +42,7 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { fun applyConstraints() { val cs = ConstraintSet() blueprint.applyConstraints(cs) + verify(hubSection).applyConstraints(cs) verify(widgetSection).applyConstraints(cs) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt index 68ea7ebb3358..6c2e13691fba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.demomode.DemoMode.ACTION_DEMO import com.android.systemui.demomode.DemoMode.COMMAND_STATUS import com.android.systemui.dump.DumpManager -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -48,7 +48,7 @@ class DemoModeControllerTest : SysuiTestCase() { @Mock private lateinit var dumpManager: DumpManager - private val globalSettings = FakeSettings() + private val globalSettings = FakeGlobalSettings() private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt index 14c5ec0361f6..c12a581fb2c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -36,7 +36,6 @@ import java.util.function.Consumer import org.junit.Assert import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyString @@ -479,7 +478,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { verify(flagManager, times(numReads)) .readFlagValue(eq(name), any<FlagSerializer<*>>()) verify(flagManager).nameToSettingsKey(eq(name)) - verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt()) + verify(globalSettings).putString(eq("key-$name"), eq(data)) verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any()) } .verifyNoMoreInteractions() diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index b1cf0517ddd1..2d3f69d95204 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -78,6 +78,8 @@ import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.settings.FakeGlobalSettings; +import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -105,8 +107,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private LockPatternUtils mLockPatternUtils; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private TelephonyListenerManager mTelephonyListenerManager; - @Mock private GlobalSettings mGlobalSettings; - @Mock private SecureSettings mSecureSettings; + private GlobalSettings mGlobalSettings; + private SecureSettings mSecureSettings; @Mock private Resources mResources; @Mock private ConfigurationController mConfigurationController; @Mock private UserTracker mUserTracker; @@ -148,6 +150,9 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { when(mResources.getConfiguration()).thenReturn( getContext().getResources().getConfiguration()); + mGlobalSettings = new FakeGlobalSettings(); + mSecureSettings = new FakeSettings(); + mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext, mWindowManagerFuncs, mAudioManager, @@ -592,8 +597,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { UserInfo currentUser = mockCurrentUser(FLAG_ADMIN); when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser); - when(mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, - 0, currentUser.id)).thenReturn(1); + mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1, + currentUser.id); GlobalActionsDialogLite.BugReportAction bugReportAction = mGlobalActionsDialogLite.makeBugReportActionForTesting(); @@ -605,8 +610,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { UserInfo currentUser = mockCurrentUser(0); when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser); - doReturn(1).when(mGlobalSettings) - .getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, currentUser.id); + mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1, + currentUser.id); GlobalActionsDialogLite.BugReportAction bugReportAction = mGlobalActionsDialogLite.makeBugReportActionForTesting(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt index 632d149c9520..f37306276848 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt @@ -16,23 +16,17 @@ package com.android.systemui.keyevent.domain.interactor -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.back.domain.interactor.BackActionInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @SmallTest @@ -40,108 +34,27 @@ import org.mockito.junit.MockitoJUnit class KeyEventInteractorTest : SysuiTestCase() { @JvmField @Rule var mockitoRule = MockitoJUnit.rule() - private lateinit var keyguardInteractorWithDependencies: - KeyguardInteractorFactory.WithDependencies - @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor - @Mock private lateinit var backActionInteractor: BackActionInteractor + private lateinit var repository: FakeKeyEventRepository private lateinit var underTest: KeyEventInteractor @Before fun setup() { - keyguardInteractorWithDependencies = KeyguardInteractorFactory.create() + repository = FakeKeyEventRepository() underTest = KeyEventInteractor( - backActionInteractor, - keyguardKeyEventInteractor, + repository, ) } @Test - fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() { - val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK) - val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK) - - // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor - whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown)) - .thenReturn(false) - whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp)) - .thenReturn(false) - - // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested - assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue() - // THEN back event isn't handled on ACTION_DOWN - verify(backActionInteractor, never()).onBackRequested() - - // WHEN back key event ACTION_UP - assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue() - // THEN back event is handled on ACTION_UP - verify(backActionInteractor).onBackRequested() - } - - @Test - fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() { - val keyEvent = - KeyEvent( - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_SPACE, - ) - whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false) - assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse() - } - - @Test - fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() { - val keyEvent = - KeyEvent( - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_SPACE, - ) - whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true) - assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue() - } - - @Test - fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() { - val keyEvent = - KeyEvent( - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_SPACE, - ) - whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false) - assertThat(underTest.interceptMediaKey(keyEvent)).isFalse() - } - - @Test - fun interceptMediaKey_handledByKeyguardKeyEventInteractor() { - val keyEvent = - KeyEvent( - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_SPACE, - ) - whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true) - assertThat(underTest.interceptMediaKey(keyEvent)).isTrue() - } - - @Test - fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() { - val keyEvent = - KeyEvent( - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_SPACE, - ) - whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false) - assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse() - } - - @Test - fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() { - val keyEvent = - KeyEvent( - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_SPACE, - ) - whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true) - assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue() - } + fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() = + runTest { + val isPowerDown by collectLastValue(underTest.isPowerButtonDown) + repository.setPowerButtonDown(false) + assertThat(isPowerDown).isFalse() + + repository.setPowerButtonDown(true) + assertThat(isPowerDown).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt new file mode 100644 index 000000000000..af00a48d571b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt @@ -0,0 +1,147 @@ +/* + * 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.systemui.keyevent.domain.interactor + +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.back.domain.interactor.BackActionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SysUIKeyEventHandlerTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + private lateinit var keyguardInteractorWithDependencies: + KeyguardInteractorFactory.WithDependencies + @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor + @Mock private lateinit var backActionInteractor: BackActionInteractor + + private lateinit var underTest: SysUIKeyEventHandler + + @Before + fun setup() { + keyguardInteractorWithDependencies = KeyguardInteractorFactory.create() + underTest = + SysUIKeyEventHandler( + backActionInteractor, + keyguardKeyEventInteractor, + ) + } + + @Test + fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() { + val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK) + val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK) + + // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown)) + .thenReturn(false) + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp)) + .thenReturn(false) + + // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested + assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue() + // THEN back event isn't handled on ACTION_DOWN + verify(backActionInteractor, never()).onBackRequested() + + // WHEN back key event ACTION_UP + assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue() + // THEN back event is handled on ACTION_UP + verify(backActionInteractor).onBackRequested() + } + + @Test + fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false) + assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse() + } + + @Test + fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true) + assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue() + } + + @Test + fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false) + assertThat(underTest.interceptMediaKey(keyEvent)).isFalse() + } + + @Test + fun interceptMediaKey_handledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true) + assertThat(underTest.interceptMediaKey(keyEvent)).isTrue() + } + + @Test + fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false) + assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse() + } + + @Test + fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() { + val keyEvent = + KeyEvent( + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SPACE, + ) + whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true) + assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index faf97517ac59..977f1db44258 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -25,13 +25,13 @@ 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.EnableZenModeDialog -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.mockito.argumentCaptor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 1e80fb69c107..26fcb234843d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -22,8 +22,8 @@ import android.content.res.Resources import android.provider.Settings 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.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.util.FakeSharedPreferences @@ -70,8 +70,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { val resources: Resources = mock() whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults)) .thenReturn(emptyArray()) - whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) - .thenReturn(true) + whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)).thenReturn(true) whenever(context.resources).thenReturn(resources) testDispatcher = UnconfinedTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 9ee22c89405d..b32905fd3b79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -204,8 +204,7 @@ class KeyguardInteractorTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Lockscreen, progress = flowOf(0f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) runCurrent() assertThat(isAnimate).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt index 900413c14475..a5d74572eb98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt @@ -35,7 +35,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Rule import org.junit.Test @@ -47,7 +46,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardKeyEventInteractorTest : SysuiTestCase() { @@ -59,8 +57,6 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP) private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK) - private lateinit var keyguardInteractorWithDependencies: - KeyguardInteractorFactory.WithDependencies private lateinit var powerInteractor: PowerInteractor @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @@ -75,14 +71,12 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { fun setup() { whenever(mediaSessionLegacyHelperWrapper.getHelper(any())) .thenReturn(mediaSessionLegacyHelper) - keyguardInteractorWithDependencies = KeyguardInteractorFactory.create() powerInteractor = PowerInteractorFactory.create().powerInteractor underTest = KeyguardKeyEventInteractor( context, statusBarStateController, - keyguardInteractorWithDependencies.keyguardInteractor, statusBarKeyguardViewManager, shadeController, mediaSessionLegacyHelperWrapper, @@ -134,73 +128,58 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { } @Test - fun dispatchKeyEvent_menuActionUp_awakeKeyguard_showsPrimaryBouncer() { + fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_awakeShadeLocked_collapsesShade() { + fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_asleepKeyguard_neverCollapsesShade() { + fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() { powerInteractor.setAsleepForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionsDoNothing(KeyEvent.KEYCODE_MENU) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_awakeKeyguard_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) - - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_enterActionUp_awakeKeyguard_showsPrimaryBouncer() { - powerInteractor.setAwakeForTest() - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false) - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_enterActionUp_awakeKeyguard_primaryBouncerAlreadyShowing() { + fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() { powerInteractor.setAwakeForTest() - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(true) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - verifyActionsDoNothing(KeyEvent.KEYCODE_ENTER) - } - - @Test - fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER) + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test @@ -270,42 +249,4 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { .isFalse() verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any()) } - - private fun verifyActionUpCollapsesTheShade(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() - } - - private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true)) - } - - private fun verifyActionsDoNothing(keycode: Int) { - // action down: does nothing - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: doesNothing - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 2cf0e77b5994..5d5ece0f8dcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -433,7 +433,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -457,7 +459,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN biometrics succeeds with wake and unlock from dream mode keyguardRepository.setBiometricUnlockState( @@ -487,7 +491,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the primary bouncer is set to show bouncerRepository.setPrimaryShow(true) @@ -515,7 +521,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the device begins to sleep keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -547,7 +555,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the keyguard is occluded and the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -783,7 +793,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // WHEN the alternateBouncer stops showing and then the primary bouncer shows bouncerRepository.setPrimaryShow(true) @@ -808,7 +820,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod available and starting to sleep bouncerRepository.setPrimaryShow(false) @@ -838,7 +852,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep // to sleep @@ -869,7 +885,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing and device not sleeping bouncerRepository.setPrimaryShow(false) @@ -980,7 +998,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to PRIMARY_BOUNCER bouncerRepository.setPrimaryShow(true) runTransitionAndSetWakefulness( - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER) + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.PRIMARY_BOUNCER + ) // WHEN the primary bouncer stops showing and lockscreen hosted dream still active bouncerRepository.setPrimaryShow(false) @@ -1161,6 +1181,57 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun dreamingToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN the keyguard is occluded and device wakes up and is no longer dreaming + keyguardRepository.setDreaming(false) + keyguardRepository.setKeyguardOccluded(true) + powerInteractor.setAwakeForTest() + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun lockscreenToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to LOCKSCREEN + runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + runCurrent() + + // WHEN the keyguard is occluded + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun aodToOccluded() = testScope.runTest { // GIVEN a prior transition has run to AOD @@ -1286,8 +1357,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } private suspend fun TestScope.runTransitionAndSetWakefulness( - from: KeyguardState, - to: KeyguardState + from: KeyguardState, + to: KeyguardState ) { transitionRepository.sendTransitionStep( TransitionStep( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 7940b450b932..50ee02609635 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -23,6 +23,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView @@ -65,6 +66,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection @Mock private lateinit var aodBurnInSection: AodBurnInSection + @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection @Before fun setup() { @@ -83,6 +85,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, + communalTutorialIndicatorSection, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index 93640975901b..d7802aabb298 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -60,10 +60,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) repository = FakeKeyguardTransitionRepository() val featureFlags = - FakeFeatureFlags().apply { - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) - set(Flags.UDFPS_NEW_TOUCH_DETECTION, true) - } + FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) } val interactor = KeyguardTransitionInteractorFactory.create( scope = TestScope().backgroundScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index fb0a4be25aa0..59d81049ad46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -48,7 +48,6 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.InstanceIdSequenceFake -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager @@ -63,6 +62,7 @@ import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor @@ -2204,6 +2204,85 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() { + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control with PlaybackState actions is added, times out, + // and then the session is destroyed + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + mediaDataManager.setTimedOut(KEY, timedOut = true) + sessionCallbackCaptor.value.invoke(KEY) + + // It is fully removed. + verify(listener).onMediaDataRemoved(eq(KEY)) + verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + verify(listener, never()) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + } + + @Test + fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() { + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control using session actions is added, and then the session is destroyed + // without timing out first + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + sessionCallbackCaptor.value.invoke(KEY) + + // It is fully removed + verify(listener).onMediaDataRemoved(eq(KEY)) + verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + verify(listener, never()) + .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) + } + + @Test + fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() { + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control using session actions and that does allow resumption is added, + addNotificationAndLoad() + val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {}) + mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable) + + // And then the session is destroyed without timing out first + sessionCallbackCaptor.value.invoke(KEY) + + // It is converted to a resume player + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.resumption).isTrue() + assertThat(mediaDataCaptor.value.active).isFalse() + verify(logger) + .logActiveConvertedToResume( + anyInt(), + eq(PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId) + ) + } + + @Test fun testSessionDestroyed_noNotificationKey_stillRemoved() { whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt index 85d3fbad5a6e..deefab670c71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import android.media.MediaRoute2Info import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController @@ -34,15 +35,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.LocalBluetoothProfileManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice -import com.android.systemui.res.R +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq @@ -95,6 +99,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable @Mock private lateinit var route: RoutingSessionInfo + @Mock private lateinit var selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @Mock private lateinit var configurationController: ConfigurationController @@ -107,6 +112,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() + private val featureFlags = FakeFeatureFlagsClassic() @Before fun setUp() { @@ -124,7 +130,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { localBluetoothManager, fakeFgExecutor, fakeBgExecutor, - dumpster + dumpster, + featureFlags, ) manager.addListener(listener) @@ -143,6 +150,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false) } @After @@ -454,9 +462,54 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun mr2ReturnsRouteWithNullName_useLocalDeviceName() { + fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session name is null, and is a system session for a PhoneMediaDevice + val phoneDevice = mock(PhoneMediaDevice::class.java) + whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) + whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) + whenever(route.isSystemSession).thenReturn(true) + + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the PhoneMediaDevice string + val data = captureDeviceData(KEY) + assertThat(data.name) + .isEqualTo( + context.getString(com.android.settingslib.R.string.media_transfer_this_device_name) + ) + } + + @Test + fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session does not have a name, and is a system session + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(route.isSystemSession).thenReturn(true) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the selected route name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -672,13 +725,108 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.showBroadcastButton).isFalse() } - fun captureCallback(): LocalMediaManager.DeviceCallback { + // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled + + @Test + fun loadMediaDataWithNullToken_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData) + // Run and reset the executors and listeners so we only focus on new events. + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + reset(listener) + + // Ensure we'll get device info when using the address + val fullMediaDevice = mock(MediaDevice::class.java) + val address = "fakeAddress" + val nameFromDevice = "nameFromDevice" + val iconFromDevice = mock(Drawable::class.java) + whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) + whenever(fullMediaDevice.name).thenReturn(nameFromDevice) + whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) + + // WHEN the about-to-connect device changes to non-null + val deviceCallback = captureCallback() + val nameFromParam = "nameFromParam" + val iconFromParam = mock(Drawable::class.java) + deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) + assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) + + // THEN the about-to-connect device based on the address is returned + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(nameFromDevice) + assertThat(data.name).isNotEqualTo(nameFromParam) + assertThat(data.icon).isEqualTo(iconFromDevice) + assertThat(data.icon).isNotEqualTo(iconFromParam) + } + + @Test + fun deviceNameFromMR2RouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a valid routing session + whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN it uses the route name (instead of device name) + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns null for routing session + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is disabled and name is set to null + val data = captureDeviceData(KEY) + assertThat(data.enabled).isFalse() + assertThat(data.name).isNull() + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a routing session that does not have a name + whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is enabled and uses the current connected device name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.enabled).isTrue() + } + + // End duplicate tests + + private fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) return captor.getValue() } - fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { + private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback { override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} @@ -699,7 +847,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { return callback } - fun setupLeAudioConfiguration(isLeAudio: Boolean) { + private fun setupLeAudioConfiguration(isLeAudio: Boolean) { whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) whenever(localBluetoothProfileManager.leAudioBroadcastProfile) .thenReturn(localBluetoothLeBroadcast) @@ -707,7 +855,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) } - fun setupBroadcastPackage(currentName: String) { + private fun setupBroadcastPackage(currentName: String) { whenever(lmm.packageName).thenReturn(PACKAGE) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) @@ -715,7 +863,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { context.setMockPackageManager(packageManager) } - fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { + private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) return captor.getValue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index de57b603c7fe..5296f1abd756 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -24,7 +24,6 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController @@ -32,6 +31,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index f25cd24dfcb0..34360d2ddd5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -7,12 +7,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,6 +38,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private val view: MediaProjectionAppSelectorView = mock() private val policyResolver: ScreenCaptureDevicePolicyResolver = mock() + private val thumbnailLoader = FakeThumbnailLoader() + private val controller = MediaProjectionAppSelectorController( taskListProvider, @@ -42,7 +48,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { personalUserHandle, scope, appSelectorComponentName, - callerPackageName + callerPackageName, + thumbnailLoader, ) @Before @@ -69,6 +76,22 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { } @Test + fun init_refreshesThumbnailsOfForegroundTasks() = runTest { + val tasks = + listOf( + createRecentTask(taskId = 1, isForegroundTask = false), + createRecentTask(taskId = 2, isForegroundTask = true), + createRecentTask(taskId = 3, isForegroundTask = true), + createRecentTask(taskId = 4, isForegroundTask = false), + ) + taskListProvider.tasks = tasks + + controller.init() + + assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3) + } + + @Test fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() { val tasks = listOf( @@ -188,14 +211,16 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private fun createRecentTask( taskId: Int, topActivityComponent: ComponentName? = null, - userId: Int = personalUserHandle.identifier + userId: Int = personalUserHandle.identifier, + isForegroundTask: Boolean = false ): RecentTask { return RecentTask( taskId = taskId, topActivityComponent = topActivityComponent, baseIntentComponent = ComponentName("com", "Test"), userId = userId, - colorBackground = 0 + colorBackground = 0, + isForegroundTask = isForegroundTask, ) } @@ -205,4 +230,18 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { override suspend fun loadRecentTasks(): List<RecentTask> = tasks } + + private class FakeThumbnailLoader : RecentTaskThumbnailLoader { + + val capturedTaskIds = mutableListOf<Int>() + + override suspend fun loadThumbnail(taskId: Int): ThumbnailData? { + return null + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? { + capturedTaskIds += taskId + return null + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt new file mode 100644 index 000000000000..db275ec190ac --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt @@ -0,0 +1,109 @@ +package com.android.systemui.mediaprojection.appselector.data + +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.res.Configuration +import android.graphics.ColorSpace +import android.graphics.Point +import android.graphics.Rect +import android.hardware.HardwareBuffer +import android.testing.AndroidTestingRunner +import android.view.Surface +import android.window.TaskSnapshot +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.recents.model.ThumbnailData +import com.android.systemui.shared.system.ActivityManagerWrapper +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + private val activityManager = mock<ActivityManagerWrapper>() + private val loader = ActivityTaskManagerThumbnailLoader(dispatcher, activityManager) + + @Test + fun loadThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val thumbnailData = ThumbnailData() + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isNull() + } + + @Test + fun loadThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val snapshot = createTaskSnapshot() + val thumbnailData = ThumbnailData(snapshot) + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isEqualTo(thumbnailData) + } + + @Test + fun captureThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 321 + val emptyThumbnailData = ThumbnailData() + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(emptyThumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isNull() + } + + @Test + fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 321 + val thumbnailData = ThumbnailData(createTaskSnapshot()) + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isEqualTo(thumbnailData) + } + + private fun createTaskSnapshot() = + TaskSnapshot( + /* id= */ 123, + /* captureTime= */ 0, + /* topActivityComponent= */ ComponentName("package", "class"), + /* snapshot= */ HardwareBuffer.create( + /* width= */ 100, + /* height= */ 100, + HardwareBuffer.RGBA_8888, + /* layers= */ 1, + /* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN + ), + ColorSpace.get(ColorSpace.Named.SRGB), + Configuration.ORIENTATION_PORTRAIT, + Surface.ROTATION_0, + /* taskSize= */ Point(100, 100), + /* contentInsets= */ Rect(), + /* letterboxInsets= */ Rect(), + /* isLowResolution= */ false, + /* isRealSnapshot= */ true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + /* appearance= */ 0, + /* isTranslucent= */ false, + /* hasImeSurface= */ false + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index d35a21236ae8..2c7ee56e9408 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -11,7 +11,7 @@ import com.android.systemui.util.mockito.whenever import com.android.wm.shell.recents.RecentTasks import com.android.wm.shell.util.GroupedRecentTaskInfo import com.google.common.truth.Truth.assertThat -import java.util.* +import java.util.Optional import java.util.function.Consumer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -52,12 +52,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3).inOrder() } @Test @@ -66,8 +61,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2)) + assertThat(result.map { it.taskId }).containsExactly(1, 2).inOrder() } @Test @@ -81,15 +75,46 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - createRecentTask(taskId = 4), - createRecentTask(taskId = 5), - createRecentTask(taskId = 6), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3, 4, 5, 6).inOrder() + } + + @Test + fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result[0].isForegroundTask).isFalse() + } + + @Test + fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() { + givenRecentTasks( + createSingleTask(taskId = 1), + createSingleTask(taskId = 2), + createSingleTask(taskId = 3), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }).containsExactly(false, true, false).inOrder() + } + + @Test + fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + createTaskPair(taskId1 = 2, taskId2 = 3), + createSingleTask(taskId = 4), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }) + .containsExactly(false, true, true, false) + .inOrder() } @Suppress("UNCHECKED_CAST") @@ -106,7 +131,8 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId = 0, topActivityComponent = null, baseIntentComponent = null, - colorBackground = null + colorBackground = null, + isForegroundTask = false, ) private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo = diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt index 906420d03bed..9630ee32d456 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt @@ -46,6 +46,7 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() { private val mockContext = mock<Context>() private val resources = mock<Resources>() private val windowManager = mock<WindowManager>() + private val windowMetricsProvider = mock<WindowMetricsProvider>() private val sizeUpdates = arrayListOf<Rect>() private val testConfigurationController = FakeConfigurationController() @@ -112,13 +113,12 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() { } private fun givenTaskbarSize(size: Int) { - val windowInsets = - WindowInsets.Builder() - .setInsets(Type.tappableElement(), Insets.of(Rect(0, 0, 0, size))) - .build() + val insets = Insets.of(Rect(0, 0, 0, size)) + val windowInsets = WindowInsets.Builder().setInsets(Type.tappableElement(), insets).build() val windowMetrics = WindowMetrics(windowManager.maximumWindowMetrics.bounds, windowInsets) whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics) whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics) + whenever(windowMetricsProvider.currentWindowInsets).thenReturn(insets) } private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) { @@ -126,6 +126,7 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() { val windowMetrics = WindowMetrics(bounds, { null }, 1.0f) whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics) whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics) + whenever(windowMetricsProvider.maximumWindowBounds).thenReturn(bounds) val minDimension = min(width, height) @@ -147,7 +148,11 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() { } } - return TaskPreviewSizeProvider(mockContext, windowManager, testConfigurationController) + return TaskPreviewSizeProvider( + mockContext, + windowMetricsProvider, + testConfigurationController + ) .also { it.addCallback(listener) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt new file mode 100644 index 000000000000..fe18454ec06c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt @@ -0,0 +1,44 @@ +package com.android.systemui.mediaprojection.appselector.view + +import android.graphics.Insets +import android.graphics.Rect +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.core.view.WindowInsetsCompat +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class WindowMetricsProviderImplTest : SysuiTestCase() { + + private val windowManager = mock<WindowManager>() + private val windowMetricsProvider = WindowMetricsProviderImpl(windowManager) + + @Test + fun getMaximumWindowBounds_returnsValueFromWMMaxWindowMetrics() { + val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400) + val metrics = + WindowMetrics(bounds, /* windowInsetsSupplier= */ { null }, /* density= */ 1.0f) + whenever(windowManager.maximumWindowMetrics).thenReturn(metrics) + + assertThat(windowMetricsProvider.maximumWindowBounds).isEqualTo(bounds) + } + + @Test + fun getCurrentWindowInsets_returnsFromWMCurrentWindowMetrics() { + val bounds = Rect() + val insets = + Insets.of(Rect(/* left= */ 123, /* top= */ 456, /* right= */ 789, /* bottom= */ 1012)) + val windowInsets = + android.view.WindowInsets.Builder() + .setInsets(WindowInsetsCompat.Type.tappableElement(), insets) + .build() + whenever(windowManager.currentWindowMetrics).thenReturn(WindowMetrics(bounds, windowInsets)) + + assertThat(windowMetricsProvider.currentWindowInsets).isEqualTo(insets) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index 8019fb513be2..6248bb1009dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -57,7 +57,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.NotificationChannels; -import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.settings.GlobalSettings; import org.junit.Before; @@ -77,7 +77,7 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { public static final String FORMATTED_45M = "0h 45m"; public static final String FORMATTED_HOUR = "1h 0m"; private final NotificationManager mMockNotificationManager = mock(NotificationManager.class); - private final GlobalSettings mGlobalSettings = new FakeSettings(); + private final GlobalSettings mGlobalSettings = new FakeGlobalSettings(); private PowerNotificationWarnings mPowerNotificationWarnings; @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt index 4be689065d3f..8f06fe2e3050 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt @@ -35,7 +35,7 @@ private typealias Callback = (Int, Boolean) -> Unit @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -class SettingObserverTest : SysuiTestCase() { +class UserSettingObserverTest : SysuiTestCase() { companion object { private const val TEST_SETTING = "setting" @@ -46,7 +46,7 @@ class SettingObserverTest : SysuiTestCase() { } private lateinit var testableLooper: TestableLooper - private lateinit var setting: SettingObserver + private lateinit var setting: UserSettingObserver private lateinit var secureSettings: SecureSettings private lateinit var callback: Callback @@ -56,17 +56,19 @@ class SettingObserverTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() - setting = object : SettingObserver( - secureSettings, - Handler(testableLooper.looper), - TEST_SETTING, - USER, - DEFAULT_VALUE - ) { - override fun handleValueChanged(value: Int, observedChange: Boolean) { - callback(value, observedChange) + setting = + object : + UserSettingObserver( + secureSettings, + Handler(testableLooper.looper), + TEST_SETTING, + USER, + DEFAULT_VALUE + ) { + override fun handleValueChanged(value: Int, observedChange: Boolean) { + callback(value, observedChange) + } } - } // Default empty callback callback = { _, _ -> Unit } @@ -162,4 +164,4 @@ class SettingObserverTest : SysuiTestCase() { setting.isListening = true assertThat(setting.value).isEqualTo(DEFAULT_VALUE) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 67587e3a8914..6cc52d70611a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -29,12 +29,14 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -73,16 +75,18 @@ import org.mockito.MockitoSession; @SmallTest @RunWith(AndroidJUnit4.class) public class TileLifecycleManagerTest extends SysuiTestCase { - private static final int TEST_FAIL_TIMEOUT = 5000; private final PackageManagerAdapter mMockPackageManagerAdapter = mock(PackageManagerAdapter.class); private final BroadcastDispatcher mMockBroadcastDispatcher = mock(BroadcastDispatcher.class); private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class); + private final ActivityManager mActivityManager = mock(ActivityManager.class); + private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; private UserHandle mUser; + private FakeSystemClock mClock; private FakeExecutor mExecutor; private HandlerThread mThread; private Handler mHandler; @@ -112,13 +116,15 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); - mExecutor = new FakeExecutor(new FakeSystemClock()); + mClock = new FakeSystemClock(); + mExecutor = new FakeExecutor(mClock); mStateManager = new TileLifecycleManager(mHandler, mWrappedContext, mock(IQSService.class), mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); } @@ -294,11 +300,32 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onStartListening(); mStateManager.executeSetBindService(true); mExecutor.runAllReady(); - mStateManager.setBindRetryDelay(0); - mExecutor.runAllReady(); mStateManager.onServiceDisconnected(mTileServiceComponentName); + mClock.advanceTime(5000); + + // Two calls: one for the first bind, one for the restart. + verifyBind(2); + verify(mMockTileService, times(2)).onStartListening(); + } + + @Test + public void testKillProcessLowMemory() throws Exception { + doAnswer(invocation -> { + ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0); + memoryInfo.lowMemory = true; + return null; + }).when(mActivityManager).getMemoryInfo(any()); + mStateManager.onStartListening(); + mStateManager.executeSetBindService(true); mExecutor.runAllReady(); + mStateManager.onServiceDisconnected(mTileServiceComponentName); + + // Longer delay than a regular one + mClock.advanceTime(5000); + verifyBind(1); + verify(mMockTileService, times(1)).onStartListening(); + mClock.advanceTime(20000); // Two calls: one for the first bind, one for the restart. verifyBind(2); verify(mMockTileService, times(2)).onStartListening(); @@ -319,6 +346,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); @@ -340,6 +368,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); @@ -361,6 +390,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 4bc16a52f8dc..d0118218134c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -304,7 +304,7 @@ public class TileServicesTest extends SysuiTestCase { CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController, commandQueue, statusBarIconController, panelInteractor, - customTileAddedRepository, executor); + mTileLifecycleManagerFactory, customTileAddedRepository, executor); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 4ada44c40f49..9b1f8303f1ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -25,7 +25,6 @@ import android.view.ContextThemeWrapper import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.settingslib.drawable.UserIconDrawable -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.shared.model.ContentDescription @@ -35,6 +34,7 @@ import com.android.systemui.qs.FakeFgsManagerController import com.android.systemui.qs.QSSecurityFooterUtils import com.android.systemui.qs.footer.FooterActionsTestUtils import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig +import com.android.systemui.res.R import com.android.systemui.security.data.model.SecurityModel import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.policy.FakeSecurityController @@ -44,7 +44,7 @@ import com.android.systemui.statusbar.policy.MockUserSwitcherControllerWrapper 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.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch @@ -128,7 +128,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { fun userSwitcher() = runTest { val picture: Drawable = mock() val userInfoController = FakeUserInfoController(FakeInfo(picture = picture)) - val settings = FakeSettings() + val settings = FakeGlobalSettings() val userId = 42 val userTracker = FakeUserTracker(userId) val userSwitcherControllerWrapper = @@ -167,14 +167,9 @@ class FooterActionsViewModelTest : SysuiTestCase() { // for the current user will be fired to notify us of that change. isUserSwitcherEnabled = true - // Update the setting for a random user: nothing should change, given that at this point we - // weren't notified of the change yet. - utils.setUserSwitcherEnabled(settings, true, 3) - assertThat(currentUserSwitcher()).isNull() - // Update the setting for the observed user: now we will be notified and the button should // be there. - utils.setUserSwitcherEnabled(settings, true, userId) + utils.setUserSwitcherEnabled(settings, true) val userSwitcher = currentUserSwitcher() assertThat(userSwitcher).isNotNull() assertThat(userSwitcher!!.icon) @@ -372,9 +367,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { ), ) - val job = launch { - underTest.observeDeviceMonitoringDialogRequests(quickSettingsContext = mock()) - } + val job = launch { underTest.observeDeviceMonitoringDialogRequests(mock()) } advanceUntilIdle() assertThat(nDialogRequests).isEqualTo(3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt index a0ff2ab330b8..dcda005b6109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.TestCoroutineScheduler @@ -95,8 +96,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.backgroundScope, dispatcher, ) - `when`(deviceItemInteractor.deviceItemUpdate) - .thenReturn(MutableStateFlow(null).asStateFlow()) + `when`(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow()) `when`(bluetoothStateInteractor.bluetoothStateUpdate) .thenReturn(MutableStateFlow(null).asStateFlow()) `when`(deviceItemInteractor.deviceItemUpdateRequest) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt index 3593075c70fd..428f79cf9b1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt @@ -27,10 +27,11 @@ import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule @@ -78,7 +79,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { @Before fun setUp() { - dispatcher = StandardTestDispatcher() + dispatcher = UnconfinedTestDispatcher() testScope = TestScope(dispatcher) interactor = DeviceItemInteractor( @@ -107,9 +108,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ true }, deviceItem1)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).isEmpty() + assertThat(latest).isEqualTo(emptyList<DeviceItem>()) } } @@ -121,9 +123,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ false }, deviceItem1)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).isEmpty() + assertThat(latest).isEqualTo(emptyList<DeviceItem>()) } } @@ -135,10 +138,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ true }, deviceItem1)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).hasSize(1) - assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem1) + assertThat(latest).isEqualTo(listOf(deviceItem1)) } } @@ -150,11 +153,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ false }, deviceItem1), createFactory({ true }, deviceItem2)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).hasSize(2) - assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem2) - assertThat(interactor.deviceItemUpdate.value!![1]).isEqualTo(deviceItem2) + assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2)) } } @@ -177,10 +179,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value) - .isEqualTo(listOf(deviceItem2, deviceItem1)) + assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1)) } } @@ -200,10 +202,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value) - .isEqualTo(listOf(deviceItem2, deviceItem1)) + assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1)) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt index 421c35595026..fe80f702a3ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt @@ -21,7 +21,7 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -36,7 +36,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class RetailModeSettingsRepositoryTest : SysuiTestCase() { - private val globalSettings = FakeSettings() + private val globalSettings = FakeGlobalSettings() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 2e16577bb24f..61dd69a8126b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -52,7 +52,6 @@ import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -463,8 +462,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fromScene = getCurrentSceneInUi(), toScene = to.key, progress = progressFlow, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 740c6d9329df..432bd0f2a050 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -120,8 +119,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(reflectedTransitionState).isEqualTo(transitionState.value) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 31d26c0d88f9..8b23d183f1a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -83,8 +83,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(reflectedTransitionState).isEqualTo(transitionState.value) @@ -122,8 +121,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = underTest.desiredScene.value.key, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(transitionTo).isEqualTo(SceneKey.Shade) @@ -160,8 +158,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) val transitioning by @@ -180,8 +177,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.QuickSettings, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) underTest.setTransitionState(transitionState) @@ -198,8 +194,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) val transitioning by @@ -227,8 +222,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(transitioning).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 3b9621e5c6e0..7b13de657657 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -109,8 +109,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Shade, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(isVisible).isTrue() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") @@ -123,8 +122,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Gone, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(isVisible).isTrue() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index 3ae1f35b8134..da4dc850fcae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals import org.junit.After import org.junit.Before import org.junit.Test @@ -111,6 +112,15 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { } @Test + fun showDialog_singleAppIsDefault() { + dialog.show() + + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) + val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app) + assertEquals(spinner.adapter.getItem(0), singleApp) + } + + @Test fun showDialog_cancelClicked_dialogIsDismissed() { dialog.show() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index a105c15b630a..d8821aa6aa00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -63,8 +63,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Before fun setUp() { - whenever(controllerFactory.create(eq(0))).thenReturn(controller0) - whenever(controllerFactory.create(eq(1))).thenReturn(controller1) + whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0) + whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1) } @Test @@ -74,8 +74,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val onSaved = { _: Uri -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -107,8 +107,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { callback ) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory, never()).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory, never()).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -139,7 +139,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Test fun executeScreenshots_allowedTypes_allCaptured() = testScope.runTest { - whenever(controllerFactory.create(any())).thenReturn(controller0) + whenever(controllerFactory.create(any(), any())).thenReturn(controller0) setDisplays( display(TYPE_INTERNAL, id = 0), diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 6205d90197a2..5091a7004f79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -86,7 +86,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { ) .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) - whenever(controllerFactory.create(any())).thenReturn(controller) + whenever(controllerFactory.create(any(), any())).thenReturn(controller) // Stub request processor as a synchronous no-op for tests with the flag enabled doAnswer { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 8d8c70e26ab2..700c9ae9006a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -84,6 +84,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.view.LongPressHandlingView; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; @@ -120,6 +121,8 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; +import com.android.systemui.scene.SceneTestUtils; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; @@ -137,6 +140,7 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; @@ -157,6 +161,7 @@ import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; import com.android.systemui.statusbar.phone.LightBarController; @@ -167,6 +172,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -175,8 +181,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; @@ -196,6 +204,8 @@ import java.util.List; import java.util.Optional; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.flow.StateFlowKt; +import kotlinx.coroutines.test.TestScope; public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @@ -323,16 +333,19 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mEmptySpaceClickListenerCaptor; @Mock protected ActivityStarter mActivityStarter; @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; - @Mock private ShadeInteractor mShadeInteractor; @Mock private JavaAdapter mJavaAdapter; @Mock private CastController mCastController; @Mock private KeyguardRootView mKeyguardRootView; @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; protected final int mMaxUdfpsBurnInOffsetY = 5; protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected FakeKeyguardRepository mFakeKeyguardRepository; protected KeyguardInteractor mKeyguardInteractor; + protected SceneTestUtils mUtils = new SceneTestUtils(this); + protected TestScope mTestScope = mUtils.getTestScope(); + protected ShadeInteractor mShadeInteractor; protected PowerInteractor mPowerInteractor; protected NotificationPanelViewController.TouchHandler mTouchHandler; protected ConfigurationController mConfigurationController; @@ -368,10 +381,31 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); mShadeRepository = new FakeShadeRepository(); mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); + when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( + StateFlowKt.MutableStateFlow(false)); + mShadeInteractor = new ShadeInteractor( + mTestScope.getBackgroundScope(), + new FakeDeviceProvisioningRepository(), + new FakeDisableFlagsRepository(), + mDozeParameters, + new FakeSceneContainerFlags(), + mUtils::sceneInteractor, + mFakeKeyguardRepository, + mKeyguardTransitionInteractor, + mPowerInteractor, + new FakeUserSetupRepository(), + new FakeUserRepository(), + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController() + ), + mShadeRepository + ); SystemClock systemClock = new FakeSystemClock(); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mInteractionJankMonitor, () -> mShadeInteractor); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -530,7 +564,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDumpManager, mock(HeadsUpManager.class), new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager), + mInteractionJankMonitor, + () -> mShadeInteractor), mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, @@ -667,7 +702,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardViewConfigurator, mKeyguardFaceAuthInteractor, new ResourcesSplitShadeStateController(), - mPowerInteractor); + mPowerInteractor, + mKeyguardClockPositionAlgorithm); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index eb006100d535..d4fb387f9670 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -90,7 +90,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.google.common.util.concurrent.MoreExecutors; @@ -230,7 +230,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index b4f9e8dcfb39..677d9db0b97c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -49,7 +49,7 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.flags.SystemPropertiesHelper -import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository @@ -144,7 +144,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock lateinit var dragDownHelper: DragDownHelper @Mock lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel - @Mock lateinit var keyEventInteractor: KeyEventInteractor + @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor private val notificationExpansionRepository = NotificationExpansionRepository() @@ -251,7 +251,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { securityModel = mock(KeyguardSecurityModel::class.java), ), BouncerLogger(logcatLogBuffer("BouncerLog")), - keyEventInteractor, + sysUIKeyEventHandler, primaryBouncerInteractor, alternateBouncerInteractor, ) @@ -475,21 +475,21 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { fun forwardsDispatchKeyEvent() { val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B) interactionEventHandler.dispatchKeyEvent(keyEvent) - verify(keyEventInteractor).dispatchKeyEvent(keyEvent) + verify(sysUIKeyEventHandler).dispatchKeyEvent(keyEvent) } @Test fun forwardsDispatchKeyEventPreIme() { val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B) interactionEventHandler.dispatchKeyEventPreIme(keyEvent) - verify(keyEventInteractor).dispatchKeyEventPreIme(keyEvent) + verify(sysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent) } @Test fun forwardsInterceptMediaKey() { val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP) interactionEventHandler.interceptMediaKey(keyEvent) - verify(keyEventInteractor).interceptMediaKey(keyEvent) + verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 189c9e25d9b0..a4a2ca0a6a21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -48,7 +48,7 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.flags.SystemPropertiesHelper -import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository @@ -253,7 +253,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { securityModel = Mockito.mock(KeyguardSecurityModel::class.java), ), BouncerLogger(logcatLogBuffer("BouncerLog")), - Mockito.mock(KeyEventInteractor::class.java), + Mockito.mock(SysUIKeyEventHandler::class.java), primaryBouncerInteractor, alternateBouncerInteractor, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 65174bab7f63..8a876c80194d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -95,15 +95,16 @@ import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.systemui.util.kotlin.JavaAdapter; +import dagger.Lazy; + import org.junit.After; import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import dagger.Lazy; import kotlinx.coroutines.test.TestScope; public class QuickSettingsControllerBaseTest extends SysuiTestCase { @@ -162,7 +163,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { @Mock protected DumpManager mDumpManager; @Mock protected UiEventLogger mUiEventLogger; @Mock protected CastController mCastController; - @Mock protected UserInteractor mUserInteractor; protected FakeDisableFlagsRepository mDisableFlagsRepository = new FakeDisableFlagsRepository(); protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); @@ -186,7 +186,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mInteractionJankMonitor, () -> mShadeInteractor); FakeDeviceProvisioningRepository deviceProvisioningRepository = new FakeDeviceProvisioningRepository(); @@ -266,7 +266,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mUserInteractor, + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index bcb060ddb417..81382a44def6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -60,7 +60,6 @@ import dagger.BindsInstance import dagger.Component import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -595,8 +594,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -633,8 +631,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -670,8 +667,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -947,8 +943,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -985,8 +980,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1023,8 +1017,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1061,8 +1054,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1097,9 +1089,8 @@ class ShadeInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = SceneKey.Lockscreen, toScene = SceneKey.QuickSettings, - progress = MutableStateFlow(0f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + progress = progress, + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index bb20d94e7d39..607cdab12f56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -17,7 +17,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -85,8 +84,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.QuickSettings, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) @@ -104,8 +102,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.QuickSettings, toScene = SceneKey.Shade, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) @@ -123,8 +120,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Shade, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java new file mode 100644 index 000000000000..3a9c24a7109c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java @@ -0,0 +1,597 @@ +/* + * 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.systemui.statusbar; + +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeSettings; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase { + @Mock + private NotificationPresenter mPresenter; + @Mock + private UserManager mUserManager; + @Mock + private UserTracker mUserTracker; + + // Dependency mocks: + @Mock + private NotificationVisibilityProvider mVisibilityProvider; + @Mock + private CommonNotifCollection mNotifCollection; + @Mock + private DevicePolicyManager mDevicePolicyManager; + @Mock + private NotificationClickNotifier mClickNotifier; + @Mock + private OverviewProxyService mOverviewProxyService; + @Mock + private KeyguardManager mKeyguardManager; + @Mock + private DeviceProvisionedController mDeviceProvisionedController; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private KeyguardStateController mKeyguardStateController; + + private UserInfo mCurrentUser; + private UserInfo mSecondaryUser; + private UserInfo mWorkUser; + private FakeSettings mSettings; + private TestNotificationLockscreenUserManager mLockscreenUserManager; + private NotificationEntry mCurrentUserNotif; + private NotificationEntry mSecondaryUserNotif; + private NotificationEntry mWorkProfileNotif; + private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); + private Executor mBackgroundExecutor = Runnable::run; // Direct executor + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); + + int currentUserId = ActivityManager.getCurrentUser(); + when(mUserTracker.getUserId()).thenReturn(currentUserId); + mSettings = new FakeSettings(); + mSettings.setUserId(ActivityManager.getCurrentUser()); + mCurrentUser = new UserInfo(currentUserId, "", 0); + mSecondaryUser = new UserInfo(currentUserId + 1, "", 0); + mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0, + UserManager.USER_TYPE_PROFILE_MANAGED); + + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); + when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( + mCurrentUser, mWorkUser)); + when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( + mSecondaryUser)); + mDependency.injectTestDependency(Dependency.MAIN_HANDLER, + Handler.createAsync(Looper.myLooper())); + + Notification notifWithPrivateVisibility = new Notification(); + notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; + mCurrentUserNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mCurrentUser.id)) + .build(); + mSecondaryUserNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mSecondaryUser.id)) + .build(); + mWorkProfileNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mWorkUser.id)) + .build(); + + mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); + mLockscreenUserManager.setUpWithPresenter(mPresenter); + } + + private void changeSetting(String setting) { + final Collection<Uri> lockScreenUris = new ArrayList<>(); + lockScreenUris.add(Settings.Secure.getUriFor(setting)); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, + lockScreenUris, 0); + } + + @Test + public void testLockScreenShowNotificationsFalse() { + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); + } + + @Test + public void testLockScreenShowNotificationsTrue() { + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); + } + + @Test + public void testLockScreenAllowPrivateNotificationsTrue() { + mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testLockScreenAllowPrivateNotificationsFalse() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testLockScreenAllowsWorkPrivateNotificationsFalse() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); + } + + @Test + public void testLockScreenAllowsWorkPrivateNotificationsTrue() { + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); + } + + @Test + public void testCurrentUserPrivateNotificationsNotRedacted() { + // GIVEN current user doesn't allow private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN current user's notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testCurrentUserPrivateNotificationsRedacted() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN current user's notification isn't redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testWorkPrivateNotificationsRedacted() { + // GIVEN work profile doesn't private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN work profile notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); + } + + @Test + public void testWorkPrivateNotificationsNotRedacted() { + // GIVEN work profile allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN work profile notification isn't redacted + assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); + } + + @Test + public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() { + // GIVEN work profile allows private notifications to show but the other users don't + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mWorkUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the work profile notification doesn't need to be redacted + assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + + // THEN the current user and secondary user notifications do need to be redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testWorkProfileRedacted_otherUsersNotRedacted() { + // GIVEN work profile doesn't allow private notifications to show but the other users do + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mWorkUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the work profile notification needs to be redacted + assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + + // THEN the current user and secondary user notifications don't need to be redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testSecondaryUserNotRedacted_currentUserRedacted() { + // GIVEN secondary profile allows private notifications to show but the current user + // doesn't allow private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSecondaryUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // THEN the secondary profile notification still needs to be redacted because the current + // user's setting takes precedence + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testUserSwitchedCallsOnUserSwitching() { + mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id, + mContext); + verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); + } + + @Test + public void testIsLockscreenPublicMode() { + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); + mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); + } + + @Test + public void testUpdateIsPublicMode() { + when(mKeyguardStateController.isMethodSecure()).thenReturn(true); + + NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class); + mLockscreenUserManager.addNotificationStateChangedListener(listener); + mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + + // first call explicitly sets user 0 to not public; notifies + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener).onNotificationStateChanged(); + clearInvocations(listener); + + // calling again has no changes; does not notify + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener, never()).onNotificationStateChanged(); + + // Calling again with keyguard now showing makes user 0 public; notifies + when(mKeyguardStateController.isShowing()).thenReturn(true); + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener).onNotificationStateChanged(); + clearInvocations(listener); + + // calling again has no changes; does not notify + mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); + assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); + verify(listener, never()).onNotificationStateChanged(); + } + + @Test + public void testDevicePolicyDoesNotAllowNotifications() { + // User allows them + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides notifs on lockscreen + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testDevicePolicyDoesNotAllowNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testDevicePolicy_noPrivateNotifications() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test + public void testDevicePolicy_noPrivateNotifications_userAll() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() + .setNotification(new Notification()) + .setUser(UserHandle.ALL) + .build())); + } + + @Test + public void testDevicePolicyPrivateNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test + public void testHideNotifications_primary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testHideNotifications_secondary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testHideNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testShowNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { + // DevicePolicy allows notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(0); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + // KeyguardManager does not allow notifications + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no + // callback, so it's only updated when the setting is + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testUserAllowsNotificationsInPublic_settingsChange() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + + // User disables + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + private class TestNotificationLockscreenUserManager + extends NotificationLockscreenUserManagerImpl { + public TestNotificationLockscreenUserManager(Context context) { + super( + context, + mBroadcastDispatcher, + mDevicePolicyManager, + mUserManager, + mUserTracker, + (() -> mVisibilityProvider), + (() -> mNotifCollection), + mClickNotifier, + (() -> mOverviewProxyService), + NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager, + mStatusBarStateController, + Handler.createAsync(Looper.myLooper()), + Handler.createAsync(Looper.myLooper()), + mBackgroundExecutor, + mDeviceProvisionedController, + mKeyguardStateController, + mSettings, + mock(DumpManager.class), + mock(LockPatternUtils.class), + mFakeFeatureFlags); + } + + public BroadcastReceiver getBaseBroadcastReceiverForTest() { + return mBaseBroadcastReceiver; + } + + public UserTracker.Callback getUserTrackerCallbackForTest() { + return mUserChangedCallback; + } + + public ContentObserver getLockscreenSettingsObserverForTest() { + return mLockscreenSettingsObserver; + } + + public ContentObserver getSettingsObserverForTest() { + return mSettingsObserver; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 19863ecaf723..a5f5fc7e36f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,17 +16,23 @@ package com.android.systemui.statusbar; +import static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; import static android.os.UserHandle.USER_ALL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -38,12 +44,15 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; @@ -59,6 +68,8 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.UserTracker; @@ -74,12 +85,16 @@ import com.android.systemui.util.settings.FakeSettings; import com.google.android.collect.Lists; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -121,11 +136,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private NotificationEntry mCurrentUserNotif; private NotificationEntry mSecondaryUserNotif; private NotificationEntry mWorkProfileNotif; + private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); + private Executor mBackgroundExecutor = Runnable::run; // Direct executor @Before public void setUp() { MockitoAnnotations.initMocks(this); + mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + int currentUserId = ActivityManager.getCurrentUser(); when(mUserTracker.getUserId()).thenReturn(currentUserId); mSettings = new FakeSettings(); @@ -138,103 +157,144 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( mCurrentUser, mWorkUser)); + when(mUserManager.getUsers()).thenReturn(Lists.newArrayList( + mCurrentUser, mWorkUser, mSecondaryUser)); when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( mSecondaryUser)); mDependency.injectTestDependency(Dependency.MAIN_HANDLER, Handler.createAsync(Looper.myLooper())); Notification notifWithPrivateVisibility = new Notification(); - notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; + notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE; mCurrentUserNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mCurrentUser.id)) .build(); + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif); mSecondaryUserNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mSecondaryUser.id)) .build(); + mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry( + mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif); mWorkProfileNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mWorkUser.id)) .build(); + mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif); mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); mLockscreenUserManager.setUpWithPresenter(mPresenter); } + private void changeSetting(String setting) { + final Collection<Uri> lockScreenUris = new ArrayList<>(); + lockScreenUris.add(Settings.Secure.getUriFor(setting)); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, + lockScreenUris, 0); + } + @Test public void testLockScreenShowNotificationsFalse() { mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testLockScreenShowNotificationsTrue() { mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testLockScreenAllowPrivateNotificationsTrue() { - mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); } @Test public void testLockScreenAllowPrivateNotificationsFalse() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); } @Test public void testLockScreenAllowsWorkPrivateNotificationsFalse() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); } @Test public void testLockScreenAllowsWorkPrivateNotificationsTrue() { - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); } @Test - public void testCurrentUserPrivateNotificationsNotRedacted() { + public void testCurrentUserPrivateNotificationsRedacted() { // GIVEN current user doesn't allow private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN current user's notification is redacted assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); } @Test - public void testCurrentUserPrivateNotificationsRedacted() { + public void testCurrentUserPrivateNotificationsNotRedacted() { // GIVEN current user allows private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN current user's notification isn't redacted assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); } @Test + public void testCurrentUserPrivateNotificationsRedactedChannel() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // but doesn't allow it at the channel level + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_PRIVATE); + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + // THEN the notification is redacted + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test public void testWorkPrivateNotificationsRedacted() { // GIVEN work profile doesn't private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN work profile notification is redacted assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -244,9 +304,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkPrivateNotificationsNotRedacted() { // GIVEN work profile allows private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN work profile notification isn't redacted assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -256,13 +316,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() { // GIVEN work profile allows private notifications to show but the other users don't - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mWorkUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the work profile notification doesn't need to be redacted assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -275,13 +337,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testWorkProfileRedacted_otherUsersNotRedacted() { // GIVEN work profile doesn't allow private notifications to show but the other users do - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mWorkUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the work profile notification needs to be redacted assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -295,11 +359,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testSecondaryUserNotRedacted_currentUserRedacted() { // GIVEN secondary profile allows private notifications to show but the current user // doesn't allow private notifications to show - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mCurrentUser.id); - mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); // THEN the secondary profile notification still needs to be redacted because the current // user's setting takes precedence @@ -323,6 +388,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testUpdateIsPublicMode() { when(mKeyguardStateController.isMethodSecure()).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(false); NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class); mLockscreenUserManager.addNotificationStateChangedListener(listener); @@ -330,24 +396,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { // first call explicitly sets user 0 to not public; notifies mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener).onNotificationStateChanged(); clearInvocations(listener); // calling again has no changes; does not notify mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener, never()).onNotificationStateChanged(); // Calling again with keyguard now showing makes user 0 public; notifies when(mKeyguardStateController.isShowing()).thenReturn(true); mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener).onNotificationStateChanged(); clearInvocations(listener); // calling again has no changes; does not notify mLockscreenUserManager.updatePublicMode(); + TestableLooper.get(this).processAllMessages(); assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); verify(listener, never()).onNotificationStateChanged(); } @@ -356,14 +426,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testDevicePolicyDoesNotAllowNotifications() { // User allows them mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifs on lockscreen when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -371,19 +441,18 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } - @Ignore("b/286230167") @Test public void testDevicePolicyDoesNotAllowNotifications_userAll() { // User allows them mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -392,98 +461,105 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - @Ignore("b/286230167") public void testDevicePolicyDoesNotAllowNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); - // TODO (b/286230167): enable assertion verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testDevicePolicy_noPrivateNotifications() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); - // TODO (b/286230167): enable assertion. It's currently called 4 times. - //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testDevicePolicy_noPrivateNotifications_userAll() { + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); + NotificationEntry notifEntry = new NotificationEntryBuilder() + .setNotification(new Notification()) + .setUser(UserHandle.ALL) + .build(); + notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() - .setNotification(new Notification()) - .setUser(UserHandle.ALL) - .build())); + assertTrue(mLockscreenUserManager.needsRedaction(notifEntry)); } @Test public void testDevicePolicyPrivateNotifications_secondary() { + Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy hides sensitive content when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mSecondaryUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); - // TODO (b/286230167): enable assertion. It's currently called 5 times. - //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); } @Test public void testHideNotifications_primary() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } @@ -491,16 +567,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testHideNotifications_secondary() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } - @Ignore("b/286230167") @Test public void testHideNotifications_workProfile() { - mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id)); } @@ -508,67 +585,62 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testHideNotifications_secondary_userSwitch() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); - TestNotificationLockscreenUserManager lockscreenUserManager - = new TestNotificationLockscreenUserManager(mContext); - lockscreenUserManager.setUpWithPresenter(mPresenter); - - lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); - assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } @Test public void testShowNotifications_secondary_userSwitch() { mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); - TestNotificationLockscreenUserManager lockscreenUserManager - = new TestNotificationLockscreenUserManager(mContext); - lockscreenUserManager.setUpWithPresenter(mPresenter); - - lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); - assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); } - @Ignore("b/286230167") @Test public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() { + // KeyguardManager does not allow notifications + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); // DevicePolicy allows notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(0); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - // KeyguardManager does not - when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); - assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { - // User allows notifications - mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); // DevicePolicy allows notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(0); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); - // KeyguardManager does not + // KeyguardManager does not allow notifications when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no + // callback, so it's only updated when the setting is + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } @@ -576,31 +648,30 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testUserAllowsNotificationsInPublic_settingsChange() { // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); // User disables mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } - @Ignore("b/286230167") @Test public void testUserAllowsNotificationsInPublic_devicePolicyChange() { // User allows notifications mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); - mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); // DevicePolicy disables notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); - BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); - when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -608,6 +679,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); } + @Test + public void testNewUserAdded() { + int newUserId = 14; + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId); + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver broadcastReceiver = + mLockscreenUserManager.getBaseBroadcastReceiverForTest(); + final Bundle extras = new Bundle(); + final Intent intent = new Intent(Intent.ACTION_USER_ADDED); + intent.putExtras(extras); + intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId); + broadcastReceiver.onReceive(mContext, intent); + + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId)); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId)); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId)); + } + private class TestNotificationLockscreenUserManager extends NotificationLockscreenUserManagerImpl { public TestNotificationLockscreenUserManager(Context context) { @@ -623,12 +716,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { (() -> mOverviewProxyService), NotificationLockscreenUserManagerTest.this.mKeyguardManager, mStatusBarStateController, - Handler.createAsync(Looper.myLooper()), + Handler.createAsync(TestableLooper.get( + NotificationLockscreenUserManagerTest.this).getLooper()), + Handler.createAsync(TestableLooper.get( + NotificationLockscreenUserManagerTest.this).getLooper()), + mBackgroundExecutor, mDeviceProvisionedController, mKeyguardStateController, mSettings, mock(DumpManager.class), - mock(LockPatternUtils.class)); + mock(LockPatternUtils.class), + mFakeFeatureFlags); } public BroadcastReceiver getBaseBroadcastReceiverForTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 3327e42b930f..1b26e19aa4a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -23,9 +23,33 @@ import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.mock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -40,17 +64,22 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class StatusBarStateControllerImplTest : SysuiTestCase() { + private val utils = SceneTestUtils(this) + private val testScope = utils.testScope + private lateinit var shadeInteractor: ShadeInteractor + private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor + private lateinit var fromPrimaryBouncerTransitionInteractor: + FromPrimaryBouncerTransitionInteractor @Mock lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock private lateinit var mockDarkAnimator: ObjectAnimator - @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock lateinit var mockDarkAnimator: ObjectAnimator private lateinit var controller: StatusBarStateControllerImpl private lateinit var uiEventLogger: UiEventLoggerFake @@ -65,10 +94,74 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { controller = object : StatusBarStateControllerImpl( uiEventLogger, mock(DumpManager::class.java), - interactionJankMonitor, shadeExpansionStateManager + interactionJankMonitor, + { shadeInteractor } ) { override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator } } + + val powerInteractor = PowerInteractor( + FakePowerRepository(), + FalsingCollectorFake(), + mock(), + controller) + val keyguardRepository = FakeKeyguardRepository() + val keyguardTransitionRepository = FakeKeyguardTransitionRepository() + val featureFlags = FakeFeatureFlagsClassic() + val shadeRepository = FakeShadeRepository() + val sceneContainerFlags = FakeSceneContainerFlags() + val configurationRepository = FakeConfigurationRepository() + val keyguardInteractor = KeyguardInteractor( + keyguardRepository, + FakeCommandQueue(), + powerInteractor, + featureFlags, + sceneContainerFlags, + FakeDeviceEntryRepository(), + FakeKeyguardBouncerRepository(), + configurationRepository, + shadeRepository, + utils::sceneInteractor) + val keyguardTransitionInteractor = KeyguardTransitionInteractor( + testScope.backgroundScope, + keyguardTransitionRepository, + { keyguardInteractor }, + { fromLockscreenTransitionInteractor }, + { fromPrimaryBouncerTransitionInteractor }) + fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + shadeRepository, + powerInteractor) + fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + mock(), + powerInteractor) + shadeInteractor = ShadeInteractor( + testScope.backgroundScope, + FakeDeviceProvisioningRepository(), + FakeDisableFlagsRepository(), + mock(), + sceneContainerFlags, + utils::sceneInteractor, + keyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + FakeUserSetupRepository(), + FakeUserRepository(), + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ResourcesSplitShadeStateController()), + shadeRepository, + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index b120c4747cb9..f72142ffc6d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -11,10 +11,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.systemui.statusbar.notification.row; +package com.android.systemui.statusbar.notification.footer.ui.view; import static com.google.common.truth.Truth.assertThat; @@ -31,8 +31,8 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 8f39ee6a6e9f..b922ab39912b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -22,6 +22,7 @@ import static android.app.Notification.VISIBILITY_SECRET; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -36,6 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.NotificationChannel; import android.content.Context; import android.os.Handler; import android.os.UserHandle; @@ -51,6 +53,9 @@ import com.android.systemui.CoreStartable; import com.android.systemui.SysuiTestCase; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -62,11 +67,15 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.utils.os.FakeHandler; +import dagger.BindsInstance; +import dagger.Component; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,9 +86,6 @@ import org.mockito.MockitoAnnotations; import java.util.Map; import java.util.function.Consumer; -import dagger.BindsInstance; -import dagger.Component; - @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -93,7 +99,9 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private SysuiStatusBarStateController mStatusBarStateController; @Mock private UserTracker mUserTracker; - private final FakeSettings mFakeSettings = new FakeSettings(); + private final FakeSettings mSecureSettings = new FakeSettings(); + private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); + private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; private NotificationEntry mEntry; @@ -113,8 +121,9 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { mHighPriorityProvider, mStatusBarStateController, mUserTracker, - mFakeSettings, - mFakeSettings); + mSecureSettings, + mGlobalSettings, + mFeatureFlags); mKeyguardNotificationVisibilityProvider = component.getProvider(); for (CoreStartable startable : component.getCoreStartables().values()) { startable.start(); @@ -223,7 +232,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); verify(listener).accept(anyString()); } @@ -234,7 +243,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true); verify(listener).accept(anyString()); } @@ -242,8 +251,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() { when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); GroupEntry parent = new GroupEntryBuilder() .setKey("parent") .addChild(mEntry) @@ -264,8 +273,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void keyguardShowing_hideSilentNotifications_perUserSetting() { when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) .setImportance(IMPORTANCE_LOW) @@ -277,8 +286,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void keyguardShowing_hideSilentNotifications_perUserSetting_withHighPriorityParent() { when(mKeyguardStateController.isShowing()).thenReturn(true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); GroupEntry parent = new GroupEntryBuilder() .setKey("parent") .addChild(mEntry) @@ -300,10 +309,10 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void hideSilentOnLockscreenSetting() { // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); // WHEN the show silent notifs on lockscreen setting is false - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); // WHEN the notification is not high priority and not ambient mEntry = new NotificationEntryBuilder() @@ -319,10 +328,10 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void showSilentOnLockscreenSetting() { // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); // WHEN the show silent notifs on lockscreen setting is true - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); // WHEN the notification is not high priority and not ambient mEntry = new NotificationEntryBuilder() @@ -338,7 +347,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void defaultSilentOnLockscreenSettingIsHide() { // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); // WHEN the notification is not high priority and not ambient mEntry = new NotificationEntryBuilder() @@ -348,7 +357,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mHighPriorityProvider.isExplicitlyHighPriority(any())).thenReturn(false); // WhHEN the show silent notifs on lockscreen setting is unset - assertNull(mFakeSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)); + assertNull( + mSecureSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)); assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @@ -359,7 +369,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Global.ZEN_MODE, true); + mGlobalSettings.putBool(Settings.Global.ZEN_MODE, true); verify(listener).accept(anyString()); } @@ -370,7 +380,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); verify(listener).accept(anyString()); } @@ -421,6 +431,28 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void publicMode_settingsDisallow() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) + .thenReturn(false); + + mEntry.setRanking(new RankingBuilder() + .setChannel(new NotificationChannel("1", "1", 4)) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) + .setKey(mEntry.getKey()).build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void publicMode_settingsDisallow_mainThread() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); @@ -430,12 +462,38 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) .thenReturn(false); + mEntry.setRanking(new RankingBuilder() + .setChannel(new NotificationChannel("1", "1", 4)) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) + .setKey(mEntry.getKey()).build()); + // THEN filter out the entry assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test public void publicMode_notifDisallowed() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_SECRET); + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_SECRET).build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void publicMode_notifDisallowed_mainThread() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); @@ -470,8 +528,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void highPriorityCharacteristicsIgnored() { // GIVEN an 'unfiltered-keyguard-showing' state with silent notifications hidden setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); // WHEN the notification doesn't exceed the threshold to show on the lockscreen, but does // have the "high priority characteristics" that would promote it to high priority @@ -503,6 +561,54 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test + public void notificationChannelVisibilityNoOverride() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN a VISIBILITY_PRIVATE notification + NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)); + entryBuilder.modifyNotification(mContext) + .setVisibility(VISIBILITY_PRIVATE); + mEntry = entryBuilder.build(); + // ranking says secret because of DPC or Setting + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setVisibilityOverride(VISIBILITY_SECRET) + .setImportance(IMPORTANCE_HIGH) + .build()); + + // WHEN we're in an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.) + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void notificationChannelVisibilitySecret() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN a VISIBILITY_PRIVATE notification + NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)); + entryBuilder.modifyNotification(mContext) + .setVisibility(VISIBILITY_PRIVATE); + // And a VISIBILITY_SECRET NotificationChannel + NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH); + channel.setLockscreenVisibility(VISIBILITY_SECRET); + mEntry = entryBuilder.build(); + // WHEN we're in an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + + mEntry.setRanking(new RankingBuilder(mEntry.getRanking()) + .setChannel(channel) + .build()); + + // THEN hide the entry based on visibility. + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test public void notificationVisibilityPrivate() { // GIVEN a VISIBILITY_PRIVATE notification NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() @@ -557,7 +663,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .build()); // WHEN its parent does exceed threshold tot show on the lockscreen - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); when(mHighPriorityProvider.isExplicitlyHighPriority(parent)).thenReturn(true); // THEN filter out the entry regardless of parent @@ -632,7 +738,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @BindsInstance SysuiStatusBarStateController statusBarStateController, @BindsInstance UserTracker userTracker, @BindsInstance SecureSettings secureSettings, - @BindsInstance GlobalSettings globalSettings + @BindsInstance GlobalSettings globalSettings, + @BindsInstance FeatureFlagsClassic featureFlags ); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt deleted file mode 100644 index 47c5e5b021ae..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt +++ /dev/null @@ -1,77 +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.systemui.statusbar.notification.logging - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogcatEchoTracker -import com.android.systemui.log.core.LogLevel -import com.android.systemui.statusbar.notification.stack.StackStateLogger -import com.google.common.truth.Truth -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class StackStateLoggerTest : SysuiTestCase() { - private val logBufferCounter = LogBufferCounter() - private lateinit var logger: StackStateLogger - - @Before - fun setup() { - logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer) - } - - @Test - fun groupChildRemovalEvent() { - logger.groupChildRemovalEventProcessed(KEY) - verifyDidLog(1) - logger.groupChildRemovalAnimationEnded(KEY) - verifyDidLog(1) - } - - class LogBufferCounter { - val recentLogs = mutableListOf<Pair<String, LogLevel>>() - val tracker = - object : LogcatEchoTracker { - override val logInBackgroundThread: Boolean = false - override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false - override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { - recentLogs.add(tagName to level) - return true - } - } - val logBuffer = - LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false) - - fun verifyDidLog(times: Int) { - Truth.assertThat(recentLogs).hasSize(times) - recentLogs.clear() - } - } - - private fun verifyDidLog(times: Int) { - logBufferCounter.verifyDidLog(times) - } - - companion object { - private val KEY = "PACKAGE_NAME" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt index 8c3bfd55ecf1..f7632aa37d4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -30,9 +30,12 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase +import com.android.systemui.TestUiOffloadThread +import com.android.systemui.UiOffloadThread import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler +import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -60,6 +63,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { fun setUp() { looper = TestableLooper.get(this) allowTestableLooperAsMainThread() + // Use main thread instead of UI offload thread to fix flakes. + mDependency.injectTestDependency( + UiOffloadThread::class.java, + TestUiOffloadThread(looper.looper) + ) + helper = NotificationTestHelper(mContext, mDependency, looper) row = helper.createRow() // Some code in the view iterates through parents so we need some extra containers around @@ -88,12 +97,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. val pi3 = getPendingIntent(action3) pi3.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isTrue() assertThat(action3.isEnabled).isFalse() @@ -109,12 +117,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. // Cancel the intent and check action is now false. val pi = getPendingIntent(action) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() // Create a NEW action and make sure that one will also be cancelled with same PI. @@ -134,12 +142,13 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent(getPendingIntent(action2)) wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. + looper.processAllMessages() val pi = getPendingIntent(action2) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action2) + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isFalse() assertThat(action3.isEnabled).isFalse() @@ -152,10 +161,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) getPendingIntent(action).cancel() + looper.processAllMessages() + ViewUtils.attachView(root) - waitForUiOffloadThread() looper.processAllMessages() + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() } @@ -173,7 +184,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) ViewUtils.detachView(root) - waitForUiOffloadThread() looper.processAllMessages() val captor = ArgumentCaptor.forClass(CancelListener::class.java) @@ -194,7 +204,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent(spy) val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Grab set attach listener @@ -213,7 +222,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { ) action.setTagInternal(R.id.pending_intent_tag, newPi) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Listeners for original pending intent need to be cleaned up now. @@ -251,4 +259,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { assertThat(pendingIntent).isNotNull() return pendingIntent } + + private fun waitForActionDisabled(action: View) { + waitForCondition { + looper.processAllMessages() + !action.isEnabled + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 236bcb416573..033c96ae84b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -65,12 +65,12 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.ExpandHelper; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.res.R; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; @@ -81,9 +81,9 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; +import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.FooterView; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -164,6 +164,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR); mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION); + mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 93faa77bef5c..49906dca0344 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -5,18 +5,18 @@ import android.content.pm.PackageManager import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager +import com.android.systemui.res.R import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.EmptyShadeView import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.footer.ui.view.FooterView +import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView -import com.android.systemui.statusbar.notification.row.FooterView -import com.android.systemui.statusbar.notification.row.FooterView.FooterViewState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.util.mockito.mock import com.google.common.truth.Expect diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 416694b29f69..1d8a3461e546 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -50,15 +50,15 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dagger.NightDisplayListenerModule; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; @@ -288,7 +288,7 @@ public class AutoTileManagerTest extends SysuiTestCase { inOrderSafety.verify(mSafetyController).removeCallback(any()); inOrderSafety.verify(mSafetyController).addCallback(any()); - SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); + UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); assertEquals(USER + 1, setting.getCurrentUser()); assertTrue(setting.isListening()); } @@ -342,7 +342,7 @@ public class AutoTileManagerTest extends SysuiTestCase { inOrderSafety.verify(mSafetyController).removeCallback(any()); inOrderSafety.verify(mSafetyController).addCallback(any()); - SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); + UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); assertEquals(USER + 1, setting.getCurrentUser()); assertFalse(setting.isListening()); } 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 f18af61dd314..c8cbe42fb0d5 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 @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -1110,6 +1112,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // THEN no NPE when fingerprintManager is null } + @Test + public void bubbleBarVisibility() { + createCentralSurfaces(); + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); + verify(mBubbles).onStatusBarVisibilityChanged(false); + + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + verify(mBubbles).onStatusBarVisibilityChanged(true); + } + /** * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard} * to reconfigure the keyguard to reflect the requested showing/occluded states. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index cda2a74609bd..48b95d407246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -34,7 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.kotlin.JavaAdapter; import org.junit.After; import org.junit.Before; @@ -56,6 +57,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import kotlinx.coroutines.flow.StateFlowKt; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -70,8 +73,9 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Mock private KeyguardBypassController mBypassController; @Mock private ConfigurationControllerImpl mConfigurationController; @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper; - @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private UiEventLogger mUiEventLogger; + @Mock private JavaAdapter mJavaAdapter; + @Mock private ShadeInteractor mShadeInteractor; private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone { TestableHeadsUpManagerPhone( @@ -85,7 +89,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor ) { super( context, @@ -98,7 +103,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { handler, accessibilityManagerWrapper, uiEventLogger, - shadeExpansionStateManager + javaAdapter, + shadeInteractor ); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; @@ -117,7 +123,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mTestHandler, mAccessibilityManagerWrapper, mUiEventLogger, - mShadeExpansionStateManager + mJavaAdapter, + mShadeInteractor ); } @@ -129,6 +136,7 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Before @Override public void setUp() { + when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); final AccessibilityManagerWrapper accessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 03f5f005ee47..3556703a2fa8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -30,9 +30,11 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.util.BurnInHelperKt; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.FakeLogBuffer; +import com.android.systemui.res.R; import org.junit.After; import org.junit.Before; @@ -51,8 +53,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private static final float OPAQUE = 1.f; private static final float TRANSPARENT = 0.f; - @Mock - private Resources mResources; + @Mock private Resources mResources; private KeyguardClockPositionAlgorithm mClockPositionAlgorithm; private KeyguardClockPositionAlgorithm.Result mClockPosition; @@ -80,7 +81,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { .mockStatic(BurnInHelperKt.class) .startMocking(); - mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(); + LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create(); + mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer); when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0); mClockPositionAlgorithm.loadDimens(mResources); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index b36d09df5929..45e9224aa253 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; -import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -37,6 +35,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.service.trust.TrustAgentService; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -175,7 +175,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true); mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false); - mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, true); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); mFeatureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt index d35ce76d7a9a..8ecf6f82806a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt @@ -18,12 +18,11 @@ package com.android.systemui.statusbar.pipeline.airplane.data.repository import android.os.Handler import android.os.Looper -import android.os.UserHandle import android.provider.Settings.Global import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -48,15 +47,14 @@ class AirplaneModeRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var logger: TableLogBuffer private lateinit var bgHandler: Handler private lateinit var scope: CoroutineScope - private lateinit var settings: FakeSettings + private lateinit var settings: FakeGlobalSettings @Before fun setUp() { MockitoAnnotations.initMocks(this) bgHandler = Handler(Looper.getMainLooper()) scope = CoroutineScope(IMMEDIATE) - settings = FakeSettings() - settings.userId = UserHandle.USER_ALL + settings = FakeGlobalSettings() underTest = AirplaneModeRepositoryImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index e76163575738..a1da16737aa4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -75,6 +75,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private BluetoothAdapter mMockAdapter; private List<CachedBluetoothDevice> mDevices; + private FakeExecutor mBackgroundExecutor; + @Before public void setup() throws Exception { mTestableLooper = TestableLooper.get(this); @@ -91,6 +93,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { when(mMockBluetoothManager.getProfileManager()) .thenReturn(mock(LocalBluetoothProfileManager.class)); mMockDumpManager = mock(DumpManager.class); + mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); BluetoothRepository bluetoothRepository = new FakeBluetoothRepository(mMockBluetoothManager); @@ -101,6 +104,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mMockDumpManager, mock(BluetoothLogger.class), bluetoothRepository, + mBackgroundExecutor, mTestableLooper.getLooper(), mMockBluetoothManager, mMockAdapter); @@ -205,6 +209,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onAclConnectionStateChanged(device, BluetoothProfile.STATE_CONNECTED); mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET); + mBackgroundExecutor.runAllReady(); assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive()); assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); @@ -290,6 +295,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.LE_AUDIO, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -300,6 +306,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEADSET, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -310,6 +317,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.A2DP, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -320,6 +328,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEARING_AID, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -337,6 +346,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device2); mBluetoothControllerImpl.onDeviceAdded(device3); + mBackgroundExecutor.runAllReady(); + assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -349,6 +360,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device1); mBluetoothControllerImpl.onDeviceAdded(device2); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isFalse(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt index 6094135c6364..361fa5b169d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.wrapper.BuildInfo @@ -67,19 +68,22 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { private lateinit var mainExecutor: FakeExecutor private lateinit var testableLooper: TestableLooper - private lateinit var settings: FakeSettings + private lateinit var secureSettings: FakeSettings + private lateinit var globalSettings: FakeGlobalSettings + @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) mainExecutor = FakeExecutor(FakeSystemClock()) - settings = FakeSettings() + secureSettings = FakeSettings() + globalSettings = FakeGlobalSettings() `when`(userTracker.userId).thenReturn(START_USER) whenever(buildInfo.isDebuggable).thenReturn(false) controller = DeviceProvisionedControllerImpl( - settings, - settings, + secureSettings, + globalSettings, userTracker, dumpManager, buildInfo, @@ -108,7 +112,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testProvisionedWhenCreated() { - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) init() assertThat(controller.isDeviceProvisioned).isTrue() @@ -116,7 +120,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testFrpActiveWhenCreated() { - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) init() assertThat(controller.isFrpActive).isTrue() @@ -124,7 +128,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testUserSetupWhenCreated() { - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) init() assertThat(controller.isUserSetup(START_USER)) @@ -134,7 +138,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { fun testDeviceProvisionedChange() { init() - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) testableLooper.processAllMessages() // background observer assertThat(controller.isDeviceProvisioned).isTrue() @@ -144,7 +148,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { fun testFrpActiveChange() { init() - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) testableLooper.processAllMessages() // background observer assertThat(controller.isFrpActive).isTrue() @@ -154,7 +158,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { fun testUserSetupChange() { init() - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) testableLooper.processAllMessages() // background observer assertThat(controller.isUserSetup(START_USER)).isTrue() @@ -165,7 +169,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() val otherUser = 10 - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) testableLooper.processAllMessages() // background observer assertThat(controller.isUserSetup(START_USER)).isFalse() @@ -175,7 +179,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testCurrentUserSetup() { val otherUser = 10 - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) init() assertThat(controller.isCurrentUserSetup).isFalse() @@ -219,7 +223,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() controller.addCallback(listener) - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) testableLooper.processAllMessages() mainExecutor.runAllReady() @@ -234,7 +238,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() controller.addCallback(listener) - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) testableLooper.processAllMessages() mainExecutor.runAllReady() @@ -249,7 +253,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() controller.addCallback(listener) - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) testableLooper.processAllMessages() mainExecutor.runAllReady() @@ -266,9 +270,9 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { controller.removeCallback(listener) switchUser(10) - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) testableLooper.processAllMessages() mainExecutor.runAllReady() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java index 66c5aaa3ed07..6825f650c421 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java @@ -38,7 +38,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.ZenModeController.Callback; -import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.FakeGlobalSettings; import org.junit.Before; import org.junit.Test; @@ -67,7 +67,7 @@ public class ZenModeControllerImplTest extends SysuiTestCase { UserTracker mUserTracker; private ZenModeControllerImpl mController; - private final FakeSettings mGlobalSettings = new FakeSettings(); + private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); @Before public void setUp() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index e249cece5a1e..0d78ae9c7b11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -29,7 +29,7 @@ import com.android.systemui.settings.FakeUserTracker import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -56,14 +56,14 @@ class UserRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: UserRepositoryImpl - private lateinit var globalSettings: FakeSettings + private lateinit var globalSettings: FakeGlobalSettings private lateinit var tracker: FakeUserTracker @Before fun setUp() { MockitoAnnotations.initMocks(this) - globalSettings = FakeSettings() + globalSettings = FakeGlobalSettings() tracker = FakeUserTracker() } @@ -282,20 +282,17 @@ class UserRepositoryImplTest : SysuiTestCase() { com.android.internal.R.bool.config_expandLockScreenUserSwitcher, true, ) - globalSettings.putIntForUser( + globalSettings.putInt( UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER, if (isSimpleUserSwitcher) 1 else 0, - UserHandle.USER_SYSTEM, ) - globalSettings.putIntForUser( + globalSettings.putInt( Settings.Global.ADD_USERS_WHEN_LOCKED, if (isAddUsersFromLockscreen) 1 else 0, - UserHandle.USER_SYSTEM, ) - globalSettings.putIntForUser( + globalSettings.putInt( Settings.Global.USER_SWITCHER_ENABLED, if (isUserSwitcherEnabled) 1 else 0, - UserHandle.USER_SYSTEM, ) } 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 28fc5db150b7..b8f747b8e961 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -42,6 +42,7 @@ import android.app.KeyguardManager; import android.content.res.Configuration; import android.media.AudioManager; import android.os.SystemClock; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Log; @@ -70,6 +71,10 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.FakeConfigurationController; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.SecureSettings; + +import dagger.Lazy; import org.junit.After; import org.junit.Before; @@ -122,6 +127,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock CsdWarningDialog mCsdWarningDialog; @Mock DevicePostureController mPostureController; + @Mock + private Lazy<SecureSettings> mLazySecureSettings; private final CsdWarningDialog.Factory mCsdWarningDialogFactory = new CsdWarningDialog.Factory() { @@ -133,6 +140,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { private FakeFeatureFlags mFeatureFlags; private int mLongestHideShowAnimationDuration = 250; + private FakeSettings mSecureSettings; @Rule public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); @@ -162,6 +170,10 @@ public class VolumeDialogImplTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); + mSecureSettings = new FakeSettings(); + + when(mLazySecureSettings.get()).thenReturn(mSecureSettings); + mDialog = new VolumeDialogImpl( getContext(), mVolumeDialogController, @@ -177,7 +189,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mPostureController, mTestableLooper.getLooper(), mDumpManager, - mFeatureFlags); + mFeatureFlags, + mLazySecureSettings); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -242,6 +255,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test + public void testSetTimeoutValue_ComputeTimeout() { + mSecureSettings.putInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, 7000); + Mockito.reset(mAccessibilityMgr); + mDialog.init(0, null); + mDialog.rescheduleTimeoutH(); + verify(mAccessibilityMgr).getRecommendedTimeoutMillis( + 7000, + AccessibilityManager.FLAG_CONTENT_CONTROLS); + } + + @Test public void testComputeTimeout_tooltip() { Mockito.reset(mAccessibilityMgr); mDialog.showCaptionsTooltip(); 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 c8327029026d..123362ac57d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -156,7 +156,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; @@ -450,7 +450,7 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java index 41dbc147dfc5..b820ca612bfc 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java @@ -68,13 +68,26 @@ public final class AnimatorTestRule implements TestRule { private final Object mLock = new Object(); private final TestHandler mTestHandler = new TestHandler(); + private final long mStartTime; + private long mTotalTimeDelta = 0; + + /** + * Construct an AnimatorTestRule with a custom start time. + * @see #AnimatorTestRule() + */ + public AnimatorTestRule(long startTime) { + mStartTime = startTime; + } + /** - * initializing the start time with {@link SystemClock#uptimeMillis()} reduces the discrepancies - * with various internals of classes like ValueAnimator which can sometimes read that clock via + * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}. + * Initializing the start time with this clock reduces the discrepancies with various internals + * of classes like ValueAnimator which can sometimes read that clock via * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}. */ - private final long mStartTime = SystemClock.uptimeMillis(); - private long mTotalTimeDelta = 0; + public AnimatorTestRule() { + this(SystemClock.uptimeMillis()); + } @NonNull @Override diff --git a/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt index 43a26f34ef2e..ca5e1d075856 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt +++ b/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt @@ -41,7 +41,7 @@ class PlatformAnimatorIsolationRule : TestRule { private fun onError() = exceptionDeferrer.fail( "Test's animations are not isolated! " + - "Did you forget to add an AnimatorTestRule to your test class?" + "Did you forget to add an AnimatorTestRule as a @Rule?" ) fun throwDeferred() = exceptionDeferrer.throwDeferred() diff --git a/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt index 7a97029ca6b0..95335a639ea2 100644 --- a/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt +++ b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt @@ -37,7 +37,7 @@ class AndroidXAnimatorIsolationRule : TestRule { private fun onError() = exceptionDeferrer.fail( "Test's animations are not isolated! " + - "Did you forget to add an AnimatorTestRule to your test class?" + "Did you forget to add an AnimatorTestRule as a @Rule?" ) fun throwDeferred() = exceptionDeferrer.throwDeferred() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index d6632a3c5ea7..f7e0120d3843 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,8 +15,6 @@ */ package com.android.systemui; -import static com.android.systemui.animation.FakeDialogLaunchAnimatorKt.fakeDialogLaunchAnimator; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -37,19 +35,15 @@ import androidx.core.animation.AndroidXAnimatorIsolationRule; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.FakeBroadcastDispatcher; import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; -import org.junit.ClassRule; import org.junit.Rule; import org.mockito.Mockito; @@ -69,8 +63,8 @@ public abstract class SysuiTestCase { private Handler mHandler; // set the lowest order so it's the outermost rule - @ClassRule(order = Integer.MIN_VALUE) - public static AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule = + @Rule(order = Integer.MIN_VALUE) + public AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule = new AndroidXAnimatorIsolationRule(); @Rule @@ -94,10 +88,7 @@ public abstract class SysuiTestCase { if (isRobolectricTest()) { mContext = mContext.createDefaultDisplayContext(); } - SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext); - initializer.init(true); - mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency()); - Dependency.setInstance(mDependency); + mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext); mFakeBroadcastDispatcher = new FakeBroadcastDispatcher( mContext, mContext.getMainExecutor(), @@ -124,13 +115,6 @@ public abstract class SysuiTestCase { // reference and are never sent to the Context. This will also prevent a real // BroadcastDispatcher from actually registering receivers. mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher); - mDependency.injectMockDependency(KeyguardUpdateMonitor.class); - - // Make sure that all tests on any SystemUIDialog does not crash because this dependency - // is missing (constructing the actual one would throw). - // TODO(b/219008720): Remove this. - mDependency.injectMockDependency(SystemUIDialogManager.class); - mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator()); } protected boolean shouldFailOnLeakedReceiver() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt new file mode 100644 index 000000000000..c791f4f70920 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt @@ -0,0 +1,26 @@ +package com.android.systemui + +import android.annotation.SuppressLint +import android.content.Context +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.animation.fakeDialogLaunchAnimator +import com.android.systemui.statusbar.phone.SystemUIDialogManager + +@SuppressLint("VisibleForTests") +fun installSysuiTestDependency(context: Context): TestableDependency { + val initializer: SystemUIInitializer = SystemUIInitializerImpl(context) + initializer.init(true) + + val dependency = TestableDependency(initializer.sysUIComponent.createDependency()) + Dependency.setInstance(dependency) + + dependency.injectMockDependency(KeyguardUpdateMonitor::class.java) + + // Make sure that all tests on any SystemUIDialog does not crash because this dependency + // is missing (constructing the actual one would throw). + // TODO(b/219008720): Remove this. + dependency.injectMockDependency(SystemUIDialogManager::class.java) + dependency.injectTestDependency(DialogLaunchAnimator::class.java, fakeDialogLaunchAnimator()) + return dependency +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java new file mode 100644 index 000000000000..fdd26ebeab70 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +/** + * UiOffloadThread that can be used for testing as part of {@link TestableDependency}. + */ +public class TestUiOffloadThread extends UiOffloadThread { + private final Handler mTestHandler; + + public TestUiOffloadThread(Looper looper) { + mTestHandler = new Handler(looper); + } + + @Override + public Future<?> execute(Runnable runnable) { + Looper myLooper = Looper.myLooper(); + if (myLooper != null && myLooper.isCurrentThread()) { + try { + runnable.run(); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + final CompletableFuture<?> future = new CompletableFuture<>(); + mTestHandler.post(() -> { + try { + runnable.run(); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt index 0ced19e3d5f3..ba9c5eda1b63 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt @@ -28,11 +28,21 @@ import org.junit.runners.model.Statement * advanced together. */ class AnimatorTestRule : TestRule { + // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(), + // then copy that time to the platform rule so that the two clocks are in sync. private val androidxRule = androidx.core.animation.AnimatorTestRule() - private val platformRule = android.animation.AnimatorTestRule() + private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime) private val advanceAndroidXTimeBy = Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) } + /** Access the mStartTime field; bypassing the restriction of being on a looper thread. */ + private val androidx.core.animation.AnimatorTestRule.startTime: Long + get() = + javaClass.getDeclaredField("mStartTime").let { field -> + field.isAccessible = true + field.getLong(this) + } + /** * Chain is for simplicity not to force a particular order; order should not matter, because * each rule affects a different AnimationHandler classes, and no callbacks to code under test @@ -55,4 +65,11 @@ class AnimatorTestRule : TestRule { // animation from one to start later than the other. platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy) } + + /** + * Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is + * a different time than the time tracked by {@link SystemClock}. + */ + val currentTime: Long + get() = androidxRule.currentTime } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt new file mode 100644 index 000000000000..902e8521acd1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt @@ -0,0 +1,19 @@ +package com.android.systemui.communal.data.repository + +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED +import android.provider.Settings.Secure.HubModeTutorialState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** Fake implementation of [CommunalTutorialRepository] */ +class FakeCommunalTutorialRepository() : CommunalTutorialRepository { + private val _tutorialSettingState = MutableStateFlow(HUB_MODE_TUTORIAL_NOT_STARTED) + override val tutorialSettingState: StateFlow<Int> = _tutorialSettingState + override suspend fun setTutorialState(@HubModeTutorialState state: Int) { + setTutorialSettingState(state) + } + + fun setTutorialSettingState(@HubModeTutorialState state: Int) { + _tutorialSettingState.value = state + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt new file mode 100644 index 000000000000..95b5316fbab5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt @@ -0,0 +1,30 @@ +/* + * Copyright 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.systemui.keyevent.data.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeKeyEventRepository() : KeyEventRepository { + private val _isPowerButtonDown = MutableStateFlow(false) + override val isPowerButtonDown: Flow<Boolean> = _isPowerButtonDown.asStateFlow() + + fun setPowerButtonDown(isDown: Boolean) { + _isPowerButtonDown.value = isDown + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt index bf77b1a050cd..911eafae5c27 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt @@ -53,7 +53,7 @@ import com.android.systemui.user.data.repository.UserSwitcherRepository import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.mockito.mock -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.settings.GlobalSettings import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher @@ -69,8 +69,8 @@ class FooterActionsTestUtils( private val scheduler: TestCoroutineScheduler, ) { /** Enable or disable the user switcher in the settings. */ - fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) { - settings.putBoolForUser(Settings.Global.USER_SWITCHER_ENABLED, enabled, userId) + fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean) { + settings.putBool(Settings.Global.USER_SWITCHER_ENABLED, enabled) // The settings listener is processing messages on the bgHandler (usually backed by a // testableLooper in tests), so let's make sure we process the callback before continuing. @@ -152,7 +152,7 @@ class FooterActionsTestUtils( userTracker: UserTracker = FakeUserTracker(), userSwitcherController: UserSwitcherController = mock(), userInfoController: UserInfoController = FakeUserInfoController(), - settings: GlobalSettings = FakeSettings(), + settings: GlobalSettings = FakeGlobalSettings(), ): UserSwitcherRepository { return UserSwitcherRepositoryImpl( context, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java new file mode 100644 index 000000000000..db5eaffee76b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.settings; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FakeGlobalSettings implements GlobalSettings { + private final Map<String, String> mValues = new HashMap<>(); + private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>(); + + public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global"); + + public FakeGlobalSettings() { + } + + @Override + public ContentResolver getContentResolver() { + return null; + } + + @Override + public void registerContentObserver(Uri uri, boolean notifyDescendants, + ContentObserver settingsObserver) { + List<ContentObserver> observers; + mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>()); + observers = mContentObserversAllUsers.get(uri.toString()); + observers.add(settingsObserver); + } + + @Override + public void unregisterContentObserver(ContentObserver settingsObserver) { + for (Map.Entry<String, List<ContentObserver>> entry : + mContentObserversAllUsers.entrySet()) { + entry.getValue().remove(settingsObserver); + } + } + + @Override + public Uri getUriFor(String name) { + return Uri.withAppendedPath(CONTENT_URI, name); + } + + @Override + public String getString(String name) { + return mValues.get(getUriFor(name).toString()); + } + + @Override + public boolean putString(String name, String value) { + return putString(name, value, null, false); + } + + @Override + public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault) { + String key = getUriFor(name).toString(); + mValues.put(key, value); + + Uri uri = getUriFor(name); + for (ContentObserver observer : + mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) { + observer.dispatchChange(false, List.of(uri), 0); + } + return true; + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java index 4b973162e42f..a49188687100 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java @@ -23,6 +23,8 @@ import android.net.Uri; import android.os.UserHandle; import android.util.Pair; +import androidx.annotation.NonNull; + import com.android.systemui.settings.UserTracker; import java.util.ArrayList; @@ -30,7 +32,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class FakeSettings implements SecureSettings, GlobalSettings, SystemSettings { +public class FakeSettings implements SecureSettings, SystemSettings { private final Map<SettingsKey, String> mValues = new HashMap<>(); private final Map<SettingsKey, List<ContentObserver>> mContentObservers = new HashMap<>(); @@ -64,7 +66,7 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti } @Override - public void registerContentObserverForUser(Uri uri, boolean notifyDescendents, + public void registerContentObserverForUser(Uri uri, boolean notifyDescendants, ContentObserver settingsObserver, int userHandle) { List<ContentObserver> observers; if (userHandle == UserHandle.USER_ALL) { @@ -147,7 +149,7 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) { return putString(name, value); } diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index ced97cc0b39e..7ac7859f3c83 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -90,8 +90,14 @@ message MetricsEvent { // Make sound through the speaker. ALERT_BEEP = 2; - // Flash a notificaiton light. + // Flash a notification light. ALERT_BLINK = 4; + + // Alert was attenuated by polite notif. feature. + ALERT_POLITE = 8; + + // Alert was muted by polite notif. feature. + ALERT_MUTED = 16; } // Reasons that a notification might be dismissed. diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp new file mode 100644 index 000000000000..91acc3d0deb4 --- /dev/null +++ b/ravenwood/Android.bp @@ -0,0 +1,33 @@ +package { + // 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: "ravenwood-annotations", + srcs: [ + "annotations-src/**/*.java", + ], + visibility: ["//visibility:public"], +} + +// File that contains the standard command line arguments to hoststubgen. +filegroup { + name: "ravenwood-standard-options", + srcs: [ + "ravenwood-standard-options.txt", + ], + visibility: ["//visibility:public"], +} + +java_library { + name: "ravenwood-annotations-lib", + srcs: [":ravenwood-annotations"], + sdk_version: "core_current", + host_supported: true, + visibility: ["//visibility:public"], +} diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS new file mode 100644 index 000000000000..c06b3b9c2d11 --- /dev/null +++ b/ravenwood/OWNERS @@ -0,0 +1,3 @@ +jsharkey@google.com +omakoto@google.com +jaggies@google.com diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java new file mode 100644 index 000000000000..be7b923244bf --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java @@ -0,0 +1,38 @@ +/* + * 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 android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * Add this with a fully-specified method name (e.g. {@code "com.package.Class.methodName"}) + * of a callback to get a callback at the class load time. + * + * The method must be {@code public static} with a single argument that takes + * {@link Class}. + */ +@Target({TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodClassLoadHook { + String value(); +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java new file mode 100644 index 000000000000..1644ffc57dca --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java @@ -0,0 +1,37 @@ +/* + * 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 android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodKeep { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java new file mode 100644 index 000000000000..eb883e228a40 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java @@ -0,0 +1,34 @@ +/* + * 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 android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + */ +@Target({TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodNativeSubstitutionClass { + String value(); +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java new file mode 100644 index 000000000000..ffa1fa50fa4e --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java @@ -0,0 +1,36 @@ +/* + * 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 android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodRemove { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java new file mode 100644 index 000000000000..6d747da10207 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java @@ -0,0 +1,36 @@ +/* + * 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 android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + */ +@Target({METHOD}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodSubstitute { + // TODO We should add "_host" as default. We're not doing it yet, because extractign the default + // value with ASM doesn't seem trivial. (? not sure.) + String suffix(); +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java new file mode 100644 index 000000000000..a329d841abbe --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java @@ -0,0 +1,35 @@ +/* + * 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 android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * TODO: Create "whole-class-throw"? + */ +@Target({METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodThrow { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java new file mode 100644 index 000000000000..ae6f42dbeaa6 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java @@ -0,0 +1,37 @@ +/* + * 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 android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * TODO: Create "whole-class-throw"? + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodWholeClassKeep { +} diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt new file mode 100644 index 000000000000..6e1384f368b8 --- /dev/null +++ b/ravenwood/ravenwood-standard-options.txt @@ -0,0 +1,37 @@ +# File containing standard options to HostStubGen for Ravenwood + +--debug + +# Keep all classes / methods / fields, but make the methods throw. +--default-throw + +# Uncomment below lines to enable each feature. +# --enable-non-stub-method-check + +#--default-method-call-hook +# com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall +#--default-class-load-hook +# com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + +# Standard annotations. +# Note, each line is a single argument, so we need newlines after each `--xxx-annotation`. +--keep-annotation + android.ravenwood.annotations.RavenwoodKeep + +--keep-class-annotation + android.ravenwood.annotations.RavenwoodWholeClassKeep + +--throw-annotation + android.ravenwood.annotations.RavenwoodThrow + +--remove-annotation + android.ravenwood.annotations.RavenwoodRemove + +--substitute-annotation + android.ravenwood.annotations.RavenwoodSubstitute + +--native-substitute-annotation + android.ravenwood.annotations.RavenwoodNativeSubstitutionClass + +--class-load-hook-annotation + android.ravenwood.annotations.RavenwoodClassLoadHook diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 6fa9c0809f75..10ac2ebc9b2f 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -40,4 +40,11 @@ flag { namespace: "accessibility" description: "Whether to set min span of ScaleGestureDetector to zero." bug: "295327792" -}
\ No newline at end of file +} + +flag { + name: "deprecate_package_list_observer" + namespace: "accessibility" + description: "Stops using the deprecated PackageListObserver." + bug: "304561459" +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 60d4ee61fdd4..aa6d800510e1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -841,32 +841,32 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // package changes monitor.register(mContext, null, UserHandle.ALL, true); - // Register an additional observer for new packages using PackageManagerInternal, which - // generally notifies observers much sooner than the BroadcastReceiver-based PackageMonitor. - final PackageManagerInternal pm = LocalServices.getService( - PackageManagerInternal.class); - if (pm != null) { - pm.getPackageList(new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - synchronized (mLock) { - if (userId == mCurrentUserId) { - onSomePackagesChangedLocked(); + if (!Flags.deprecatePackageListObserver()) { + final PackageManagerInternal pm = LocalServices.getService( + PackageManagerInternal.class); + if (pm != null) { + pm.getPackageList(new PackageManagerInternal.PackageListObserver() { + @Override + public void onPackageAdded(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + synchronized (mLock) { + if (userId == mCurrentUserId) { + onSomePackagesChangedLocked(); + } } } - } - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - synchronized (mLock) { - if (userId == mCurrentUserId) { - onPackageRemovedLocked(packageName); + @Override + public void onPackageRemoved(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + synchronized (mLock) { + if (userId == mCurrentUserId) { + onPackageRemovedLocked(packageName); + } } } - } - }); + }); + } } // user change and unlock diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 30b9d0b59467..01064ac83fb2 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -873,7 +873,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH transitionToDelegatingStateAndClear(); - } else if (mDetectTripleTap + } else if (mDetectSingleFingerTripleTap // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay // to ensure reachability of // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) @@ -989,7 +989,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Shortcut acts as the 2 initial taps if (mShortcutTriggered) return tapCount() + 2 >= numTaps; - final boolean multitapTriggered = mDetectTripleTap + final boolean multitapTriggered = mDetectSingleFingerTripleTap && tapCount() >= numTaps && isMultiTap(mPreLastDown, mLastDown) && isMultiTap(mPreLastUp, mLastUp); @@ -1205,7 +1205,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * @return true if tap is out of distance slop */ boolean isTapOutOfDistanceSlop() { - if (!mDetectTripleTap) return false; + if (!mDetectSingleFingerTripleTap) return false; if (mPreLastDown == null || mLastDown == null) { return false; } @@ -1282,7 +1282,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + ", mMagnifiedInteractionState=" + mPanningScalingState + ", mViewportDraggingState=" + mViewportDraggingState + ", mSinglePanningState=" + mSinglePanningState - + ", mDetectTripleTap=" + mDetectTripleTap + + ", mDetectSingleFingerTripleTap=" + mDetectSingleFingerTripleTap + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger + ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 28946939e6fd..8476a5e30e27 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -57,11 +57,11 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo protected final boolean mDetectShortcutTrigger; /** - * {@code true} if this detector should detect and respond to triple-tap + * {@code true} if this detector should detect and respond to single-finger triple-tap * gestures for engaging and disengaging magnification, * {@code false} if it should ignore such gestures */ - protected final boolean mDetectTripleTap; + protected final boolean mDetectSingleFingerTripleTap; /** Callback interface to report that magnification is interactive with a user. */ public interface Callback { @@ -85,12 +85,12 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo private final AccessibilityTraceManager mTrace; protected final Callback mCallback; - protected MagnificationGestureHandler(int displayId, boolean detectTripleTap, + protected MagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap, boolean detectShortcutTrigger, AccessibilityTraceManager trace, @NonNull Callback callback) { mDisplayId = displayId; - mDetectTripleTap = detectTripleTap; + mDetectSingleFingerTripleTap = detectSingleFingerTripleTap; mDetectShortcutTrigger = detectShortcutTrigger; mTrace = trace; mCallback = callback; @@ -128,7 +128,7 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo } private boolean shouldDispatchTransformedEvent(MotionEvent event) { - if ((!mDetectTripleTap && !mDetectShortcutTrigger) || !event.isFromSource( + if ((!mDetectSingleFingerTripleTap && !mDetectShortcutTrigger) || !event.isFromSource( SOURCE_TOUCHSCREEN)) { return true; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index c58e9a6d0a2f..2d9dcb96d53d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -111,7 +111,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent, policyFlags)); mDelegatingState = new DelegatingState(mMotionEventDispatcherDelegate); - mDetectingState = new DetectingState(context, mDetectTripleTap); + mDetectingState = new DetectingState(context); mViewportDraggingState = new ViewportDraggingState(); mObservePanningScalingState = new PanningScalingGestureState( new PanningScalingHandler(context, MAX_SCALE, MIN_SCALE, true, @@ -448,22 +448,14 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl private final MagnificationGesturesObserver mGesturesObserver; - /** - * {@code true} if this detector should detect and respond to triple-tap - * gestures for engaging and disengaging magnification, - * {@code false} if it should ignore such gestures - */ - private final boolean mDetectTripleTap; - - DetectingState(@UiContext Context context, boolean detectTripleTap) { - mDetectTripleTap = detectTripleTap; - final MultiTap multiTap = new MultiTap(context, mDetectTripleTap ? 3 : 1, - mDetectTripleTap + DetectingState(@UiContext Context context) { + final MultiTap multiTap = new MultiTap(context, mDetectSingleFingerTripleTap ? 3 : 1, + mDetectSingleFingerTripleTap ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, null); final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context, - mDetectTripleTap ? 3 : 1, - mDetectTripleTap + mDetectSingleFingerTripleTap ? 3 : 1, + mDetectSingleFingerTripleTap ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, null); mGesturesObserver = new MagnificationGesturesObserver(this, @@ -488,7 +480,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public boolean shouldStopDetection(MotionEvent motionEvent) { return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId) - && !mDetectTripleTap; + && !mDetectSingleFingerTripleTap; } @Override diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index 123b65c039ba..b37bbd6ea27f 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -8,6 +8,13 @@ flag { } flag { + name: "fill_fields_from_current_session_only" + namespace: "autofill" + description: "Only fill autofill fields that are part of the current session." + bug: "270722825" +} + +flag { name: "relayout" namespace: "autofill" description: "Mitigation for relayout issue" diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index c7b53c55d89b..72242d265e79 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -58,6 +58,7 @@ import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.autofill.FillEventHistory; +import android.service.autofill.Flags; import android.service.autofill.UserData; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; @@ -226,6 +227,12 @@ public final class AutofillManagerService @GuardedBy("mFlagLock") private int mMaxInputLengthForAutofill; + @GuardedBy("mFlagLock") + private boolean mAutofillCredmanIntegrationEnabled; + + @GuardedBy("mFlagLock") + private boolean mIsFillFieldsFromCurrentSessionOnly; + // Default flag values for Autofill PCC private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = ""; @@ -701,12 +708,16 @@ public final class AutofillManagerService DeviceConfig.NAMESPACE_AUTOFILL, AutofillFeatureFlags.DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL, AutofillFeatureFlags.DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL); + mAutofillCredmanIntegrationEnabled = Flags.autofillCredmanIntegration(); + mIsFillFieldsFromCurrentSessionOnly = Flags.fillFieldsFromCurrentSessionOnly(); if (verbose) { Slog.v(mTag, "setDeviceConfigProperties() for PCC: " + "mPccClassificationEnabled=" + mPccClassificationEnabled + ", mPccPreferProviderOverPcc=" + mPccPreferProviderOverPcc + ", mPccUseFallbackDetection=" + mPccUseFallbackDetection - + ", mPccProviderHints=" + mPccProviderHints); + + ", mPccProviderHints=" + mPccProviderHints + + ", mAutofillCredmanIntegrationEnabled=" + + mAutofillCredmanIntegrationEnabled); } } } @@ -965,6 +976,15 @@ public final class AutofillManagerService } /** + * Whether the Autofill-Credman integration feature flag is enabled. + */ + public boolean isAutofillCredmanIntegrationEnabled() { + synchronized (mFlagLock) { + return mAutofillCredmanIntegrationEnabled; + } + } + + /** * Whether the Autofill Provider shouldbe preferred over PCC results for selecting datasets. */ public boolean preferProviderOverPcc() { @@ -1004,6 +1024,15 @@ public final class AutofillManagerService } } + /** + * Return if autofill should only fill in fields from current session. + */ + public boolean getIsFillFieldsFromCurrentSessionOnly() { + synchronized (mFlagLock) { + return mIsFillFieldsFromCurrentSessionOnly; + } + } + @Nullable @VisibleForTesting static Map<String, String[]> getAllowedCompatModePackages(String setting) { @@ -2096,6 +2125,9 @@ public final class AutofillManagerService pw.print(";"); pw.print("mPccProviderHints="); pw.println(mPccProviderHints); + pw.print(";"); + pw.print("mAutofillCredmanIntegrationEnabled="); + pw.println(mAutofillCredmanIntegrationEnabled); } // Dump per-user services dumpLocked("", pw); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 5b8bdd57ccbb..518b81f19467 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -19,6 +19,7 @@ package com.android.server.autofill; import static android.service.autofill.FillEventHistory.Event.NO_SAVE_UI_REASON_NONE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; +import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD; import static android.view.autofill.AutofillManager.ACTION_START_SESSION; import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED; import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; @@ -103,6 +104,10 @@ final class AutofillManagerServiceImpl extends AbstractPerUserSystemService<AutofillManagerServiceImpl, AutofillManagerService> { private static final String TAG = "AutofillManagerServiceImpl"; + + private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME = + new ComponentName("com.android.credentialmanager", + "com.android.credentialmanager.autofill.CredentialAutofillService"); private static final int MAX_SESSION_ID_CREATE_TRIES = 2048; /** Minimum interval to prune abandoned sessions */ @@ -532,9 +537,16 @@ final class AutofillManagerServiceImpl assertCallerLocked(clientActivity, compatMode); - // It's null when the session is just for augmented autofill - final ComponentName serviceComponentName = mInfo == null ? null + ComponentName serviceComponentName = mInfo == null ? null : mInfo.getServiceInfo().getComponentName(); + + if (isAutofillCredmanIntegrationEnabled() + && ((flags & FLAG_SCREEN_HAS_CREDMAN_FIELD) != 0)) { + // Hardcode to credential manager proxy service + Slog.i(TAG, "Routing to CredentialAutofillService"); + serviceComponentName = CREDMAN_SERVICE_COMPONENT_NAME; + } + final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock, sessionId, taskId, clientUid, clientActivityToken, clientCallback, hasCallback, mUiLatencyHistory, mWtfHistory, serviceComponentName, @@ -1747,6 +1759,10 @@ final class AutofillManagerServiceImpl } } + public boolean isAutofillCredmanIntegrationEnabled() { + return mMaster.isAutofillCredmanIntegrationEnabled(); + } + /** * Called when the {@link AutofillManagerService#mFieldClassificationResolver} * changed (among other places). diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java deleted file mode 100644 index 715697d82cad..000000000000 --- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.autofill; - -import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; - -import static com.android.server.autofill.Helper.sVerbose; - -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.AppGlobals; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.ICancellationSignal; -import android.os.RemoteException; -import android.service.autofill.Dataset; -import android.service.autofill.FillResponse; -import android.service.autofill.IFillCallback; -import android.service.autofill.SaveInfo; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.autofill.IAutoFillManagerClient; -import android.view.inputmethod.InlineSuggestionsRequest; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.AndroidFuture; - -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Maintains a client suggestions session with the - * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}. - * - */ -final class ClientSuggestionsSession { - - private static final String TAG = "ClientSuggestionsSession"; - private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS; - - private final int mSessionId; - private final IAutoFillManagerClient mClient; - private final Handler mHandler; - private final ComponentName mComponentName; - - private final RemoteFillService.FillServiceCallbacks mCallbacks; - - private final Object mLock = new Object(); - @GuardedBy("mLock") - private AndroidFuture<FillResponse> mPendingFillRequest; - @GuardedBy("mLock") - private int mPendingFillRequestId = INVALID_REQUEST_ID; - - ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler, - ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) { - mSessionId = sessionId; - mClient = client; - mHandler = handler; - mComponentName = componentName; - mCallbacks = callbacks; - } - - void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) { - final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); - final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>(); - final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>(); - - mHandler.post(() -> { - if (sVerbose) { - Slog.v(TAG, "calling onFillRequest() for id=" + requestId); - } - - try { - mClient.requestFillFromClient(requestId, inlineRequest, - new FillCallbackImpl(fillRequest, futureRef, cancellationSink)); - } catch (RemoteException e) { - fillRequest.completeExceptionally(e); - } - }); - - fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS); - futureRef.set(fillRequest); - - synchronized (mLock) { - mPendingFillRequest = fillRequest; - mPendingFillRequestId = requestId; - } - - fillRequest.whenComplete((res, err) -> mHandler.post(() -> { - synchronized (mLock) { - mPendingFillRequest = null; - mPendingFillRequestId = INVALID_REQUEST_ID; - } - if (err == null) { - processAutofillId(res); - mCallbacks.onFillRequestSuccess(requestId, res, - mComponentName.getPackageName(), flags); - } else { - Slog.e(TAG, "Error calling on client fill request", err); - if (err instanceof TimeoutException) { - dispatchCancellationSignal(cancellationSink.get()); - mCallbacks.onFillRequestTimeout(requestId); - } else if (err instanceof CancellationException) { - dispatchCancellationSignal(cancellationSink.get()); - } else { - mCallbacks.onFillRequestFailure(requestId, err.getMessage()); - } - } - })); - } - - /** - * Gets the application info for the component. - */ - @Nullable - static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) { - try { - ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo( - comp.getPackageName(), - PackageManager.GET_META_DATA, - userId); - if (si != null) { - return si; - } - } catch (RemoteException e) { - } - return null; - } - - /** - * Gets the user-visible name of the application. - */ - @Nullable - @GuardedBy("mLock") - static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) { - return appInfo == null ? null : appInfo.loadSafeLabel( - context.getPackageManager(), 0 /* do not ellipsize */, - TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM); - } - - /** - * Gets the user-visible icon of the application. - */ - @Nullable - @GuardedBy("mLock") - static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) { - return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager()); - } - - int cancelCurrentRequest() { - synchronized (mLock) { - return mPendingFillRequest != null && mPendingFillRequest.cancel(false) - ? mPendingFillRequestId - : INVALID_REQUEST_ID; - } - } - - /** - * The {@link AutofillId} which the client gets from its view is not contain the session id, - * but Autofill framework is using the {@link AutofillId} with a session id. So before using - * those ids in the Autofill framework, applies the current session id. - * - * @param res which response need to apply for a session id - */ - private void processAutofillId(FillResponse res) { - if (res == null) { - return; - } - - final List<Dataset> datasets = res.getDatasets(); - if (datasets != null && !datasets.isEmpty()) { - for (int i = 0; i < datasets.size(); i++) { - final Dataset dataset = datasets.get(i); - if (dataset != null) { - applySessionId(dataset.getFieldIds()); - } - } - } - - final SaveInfo saveInfo = res.getSaveInfo(); - if (saveInfo != null) { - applySessionId(saveInfo.getOptionalIds()); - applySessionId(saveInfo.getRequiredIds()); - applySessionId(saveInfo.getSanitizerValues()); - applySessionId(saveInfo.getTriggerId()); - } - } - - private void applySessionId(List<AutofillId> ids) { - if (ids == null || ids.isEmpty()) { - return; - } - - for (int i = 0; i < ids.size(); i++) { - applySessionId(ids.get(i)); - } - } - - private void applySessionId(AutofillId[][] ids) { - if (ids == null) { - return; - } - for (int i = 0; i < ids.length; i++) { - applySessionId(ids[i]); - } - } - - private void applySessionId(AutofillId[] ids) { - if (ids == null) { - return; - } - for (int i = 0; i < ids.length; i++) { - applySessionId(ids[i]); - } - } - - private void applySessionId(AutofillId id) { - if (id == null) { - return; - } - id.setSessionId(mSessionId); - } - - private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) { - if (signal == null) { - return; - } - try { - signal.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Error requesting a cancellation", e); - } - } - - private class FillCallbackImpl extends IFillCallback.Stub { - final AndroidFuture<FillResponse> mFillRequest; - final AtomicReference<AndroidFuture<FillResponse>> mFutureRef; - final AtomicReference<ICancellationSignal> mCancellationSink; - - FillCallbackImpl(AndroidFuture<FillResponse> fillRequest, - AtomicReference<AndroidFuture<FillResponse>> futureRef, - AtomicReference<ICancellationSignal> cancellationSink) { - mFillRequest = fillRequest; - mFutureRef = futureRef; - mCancellationSink = cancellationSink; - } - - @Override - public void onCancellable(ICancellationSignal cancellation) { - AndroidFuture<FillResponse> future = mFutureRef.get(); - if (future != null && future.isCancelled()) { - dispatchCancellationSignal(cancellation); - } else { - mCancellationSink.set(cancellation); - } - } - - @Override - public void onSuccess(FillResponse response) { - mFillRequest.complete(response); - } - - @Override - public void onFailure(int requestId, CharSequence message) { - String errorMessage = message == null ? "" : String.valueOf(message); - mFillRequest.completeExceptionally( - new RuntimeException(errorMessage)); - } - } -} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 0220deca18c1..07e9c50e845a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -16,7 +16,6 @@ package com.android.server.autofill; -import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS; import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES; import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE; import static android.service.autofill.Dataset.PICK_REASON_NO_PCC; @@ -44,7 +43,6 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED; import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; -import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; @@ -110,8 +108,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.graphics.Bitmap; import android.graphics.Rect; @@ -481,9 +477,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl(); - @Nullable - private ClientSuggestionsSession mClientSuggestionsSession; - private final ClassificationState mClassificationState = new ClassificationState(); // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a @@ -625,9 +618,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** Whether the current {@link FillResponse} is expired. */ private boolean mExpiredResponse; - /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */ - private boolean mClientSuggestionsEnabled; - /** Whether the fill dialog UI is disabled. */ private boolean mFillDialogDisabled; @@ -673,19 +663,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mWaitForInlineRequest = inlineSuggestionsRequest != null; mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; - maybeRequestFillFromServiceLocked(); + maybeRequestFillLocked(); viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); } } - void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) { - mPendingFillRequest = null; - mWaitForInlineRequest = inlineRequest != null; - mPendingInlineSuggestionsRequest = inlineRequest; - } - @GuardedBy("mLock") - void maybeRequestFillFromServiceLocked() { + void maybeRequestFillLocked() { if (mPendingFillRequest == null) { return; } @@ -696,15 +680,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - if (mPendingInlineSuggestionsRequest.isServiceSupported()) { - mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), - mPendingFillRequest.getFillContexts(), - mPendingFillRequest.getHints(), - mPendingFillRequest.getClientState(), - mPendingFillRequest.getFlags(), - mPendingInlineSuggestionsRequest, - mPendingFillRequest.getDelayedFillIntentSender()); - } + mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), + mPendingFillRequest.getFillContexts(), + mPendingFillRequest.getHints(), + mPendingFillRequest.getClientState(), + mPendingFillRequest.getFlags(), + mPendingInlineSuggestionsRequest, + mPendingFillRequest.getDelayedFillIntentSender()); } mLastFillRequest = mPendingFillRequest; @@ -826,7 +808,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState : mDelayedFillPendingIntent.getIntentSender()); mPendingFillRequest = request; - maybeRequestFillFromServiceLocked(); + maybeRequestFillLocked(); } if (mActivityToken != null) { @@ -1152,39 +1134,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Cancels the last request sent to the {@link #mRemoteFillService} or the - * {@link #mClientSuggestionsSession}. + * Cancels the last request sent to the {@link #mRemoteFillService}. */ @GuardedBy("mLock") private void cancelCurrentRequestLocked() { - if (mRemoteFillService == null && mClientSuggestionsSession == null) { - wtf(null, "cancelCurrentRequestLocked() called without a remote service or a " - + "client suggestions session. mForAugmentedAutofillOnly: %s", - mSessionFlags.mAugmentedAutofillOnly); + if (mRemoteFillService == null) { + wtf(null, "cancelCurrentRequestLocked() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); return; } + final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); - if (mRemoteFillService != null) { - final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); - - // Remove the FillContext as there will never be a response for the service - if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { - final int numContexts = mContexts.size(); + // Remove the FillContext as there will never be a response for the service + if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { + final int numContexts = mContexts.size(); - // It is most likely the last context, hence search backwards - for (int i = numContexts - 1; i >= 0; i--) { - if (mContexts.get(i).getRequestId() == canceledRequest) { - if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); - mContexts.remove(i); - break; - } + // It is most likely the last context, hence search backwards + for (int i = numContexts - 1; i >= 0; i--) { + if (mContexts.get(i).getRequestId() == canceledRequest) { + if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); + mContexts.remove(i); + break; } } } - - if (mClientSuggestionsSession != null) { - mClientSuggestionsSession.cancelCurrentRequest(); - } } private boolean isViewFocusedLocked(int flags) { @@ -1280,30 +1253,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION); } - // Only ask IME to create inline suggestions request when - // 1. Autofill provider supports it or client enabled client suggestions. - // 2. The render service is available. - // 3. The view is focused. (The view may not be focused if the autofill is triggered - // manually.) + // Only ask IME to create inline suggestions request if Autofill provider supports it and + // the render service is available except the autofill is triggered manually and the view + // is also not focused. final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); - if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled) - && remoteRenderService != null - && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) { - final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer; - if (mSessionFlags.mClientSuggestionsEnabled) { - final int finalRequestId = requestId; - inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> { - // Using client suggestions - synchronized (mLock) { - onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest); - } - viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); - }; - } else { - inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked( - viewState, /* isInlineRequest= */ true); - } + if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null + && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = + mAssistReceiver.newAutofillRequestLocked(viewState, + /* isInlineRequest= */ true); if (inlineSuggestionsRequestConsumer != null) { final int requestIdCopy = requestId; final AutofillId focusedId = mCurrentViewId; @@ -1323,18 +1282,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState inlineSuggestionRendorInfoCallback); viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); } - } else if (mSessionFlags.mClientSuggestionsEnabled) { - // Request client suggestions for the dropdown mode - onClientFillRequestLocked(requestId, null); } else { mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false); } - if (mSessionFlags.mClientSuggestionsEnabled) { - // Using client suggestions, unnecessary request AssistStructure - return; - } - // Now request the assist structure data. requestAssistStructureLocked(requestId, flags); } @@ -1443,11 +1394,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags = new SessionFlags(); mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly; mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked(); - if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - == PackageManager.PERMISSION_GRANTED) { - mSessionFlags.mClientSuggestionsEnabled = - (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0; - } setClientLocked(client); } @@ -1599,15 +1545,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (requestLog != null) { requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1); } - processNullResponseOrFallbackLocked(requestId, requestFlags); + processNullResponseLocked(requestId, requestFlags); return; } // TODO: Check if this is required. We can still present datasets to the user even if // traditional field classification is disabled. fieldClassificationIds = response.getFieldClassificationIds(); - if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null - && !mService.isFieldClassificationEnabledLocked()) { + if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) { Slog.w(TAG, "Ignoring " + response + " because field detection is disabled"); processNullResponseLocked(requestId, requestFlags); return; @@ -1741,9 +1686,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) - && (ArrayUtils.isEmpty(response.getFieldClassificationIds()) - || (!mSessionFlags.mClientSuggestionsEnabled - && !mService.isFieldClassificationEnabledLocked()))); + && (ArrayUtils.isEmpty(response.getFieldClassificationIds()))); } } @@ -2190,40 +2133,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState fieldFilters.add(dataset.getFilter(index)); } - @GuardedBy("mLock") - private void processNullResponseOrFallbackLocked(int requestId, int flags) { - if (!mSessionFlags.mClientSuggestionsEnabled) { - processNullResponseLocked(requestId, flags); - return; - } - - // fallback to the default platform password manager - mSessionFlags.mClientSuggestionsEnabled = false; - mLastFillDialogTriggerIds = null; - // Log the existing FillResponse event. - mFillResponseEventLogger.logAndEndEvent(); - - final InlineSuggestionsRequest inlineRequest = - (mLastInlineSuggestionsRequest != null - && mLastInlineSuggestionsRequest.first == requestId) - ? mLastInlineSuggestionsRequest.second : null; - - // Start a new FillRequest logger for client suggestion fallback. - mFillRequestEventLogger.startLogForNewRequest(); - mRequestCount++; - mFillRequestEventLogger.maybeSetAppPackageUid(uid); - mFillRequestEventLogger.maybeSetFlags( - flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS); - mFillRequestEventLogger.maybeSetRequestTriggerReason( - TRIGGER_REASON_NORMAL_TRIGGER); - mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true); - - mAssistReceiver.newAutofillRequestLocked(inlineRequest); - requestAssistStructureLocked(requestId, - flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS); - return; - } - // FillServiceCallbacks @Override @SuppressWarnings("GuardedBy") @@ -2437,7 +2346,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + id + " destroyed"); return; } - fillInIntent = createAuthFillInIntentLocked(requestId, extras); + fillInIntent = createAuthFillInIntentLocked(requestId, extras, /* authExtras= */ null); if (fillInIntent == null) { forceRemoveFromServiceLocked(); return; @@ -4520,22 +4429,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState filterText = value.getTextValue().toString(); } - final CharSequence targetLabel; - final Drawable targetIcon; - synchronized (mLock) { - if (mSessionFlags.mClientSuggestionsEnabled) { - final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName, - mService.getUserId()); - targetLabel = ClientSuggestionsSession.getAppLabelLocked( - mService.getMaster().getContext(), appInfo); - targetIcon = ClientSuggestionsSession.getAppIconLocked( - mService.getMaster().getContext(), appInfo); - } else { - targetLabel = mService.getServiceLabelLocked(); - targetIcon = mService.getServiceIconLocked(); - } + final CharSequence serviceLabel; + final Drawable serviceIcon; + synchronized (this.mService.mLock) { + serviceLabel = mService.getServiceLabelLocked(); + serviceIcon = mService.getServiceIconLocked(); } - if (targetLabel == null || targetIcon == null) { + if (serviceLabel == null || serviceIcon == null) { wtf(null, "onFillReady(): no service label or icon"); return; } @@ -4596,7 +4496,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName, - targetLabel, targetIcon, this, mContext, id, mCompatMode, + serviceLabel, serviceIcon, this, mContext, id, mCompatMode, mService.getMaster().getMaxInputLengthForAutofill()); synchronized (mLock) { @@ -4799,17 +4699,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - final InlineSuggestionsRequest request = inlineSuggestionsRequest.get(); - if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported() - || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) { - if (sDebug) { - Slog.d(TAG, "Inline suggestions not supported for " - + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service") - + ". Falling back to dropdown."); - } - return false; - } - final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); if (remoteRenderService == null) { @@ -4824,7 +4713,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final InlineFillUi.InlineFillUiInfo inlineFillUiInfo = - new InlineFillUi.InlineFillUiInfo(request, focusedId, + new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId, filterText, remoteRenderService, userId, id); InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response, new InlineFillUi.InlineSuggestionUiCallback() { @@ -5558,7 +5447,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetAuthenticationType( AUTHENTICATION_TYPE_DATASET_AUTHENTICATION); setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false); - final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState); + final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState, + dataset.getAuthenticationExtras()); if (fillInIntent == null) { forceRemoveFromServiceLocked(); return; @@ -5574,7 +5464,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // TODO: this should never be null, but we got at least one occurrence, probably due to a race. @GuardedBy("mLock") @Nullable - private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) { + private Intent createAuthFillInIntentLocked(int requestId, Bundle extras, + @Nullable Bundle authExtras) { final Intent fillInIntent = new Intent(); final FillContext context = getFillContextByRequestIdLocked(requestId); @@ -5591,6 +5482,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure()); fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras); + if (authExtras != null) { + fillInIntent.putExtra(AutofillManager.EXTRA_AUTH_STATE, authExtras); + } return fillInIntent; } @@ -5636,26 +5530,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - @GuardedBy("mLock") - private void onClientFillRequestLocked(int requestId, - InlineSuggestionsRequest inlineSuggestionsRequest) { - if (mClientSuggestionsSession == null) { - mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler, - mComponentName, this); - } - - if (mContexts == null) { - mContexts = new ArrayList<>(1); - } - mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId)); - - if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) { - inlineSuggestionsRequest = null; - } - - mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags); - } - /** * The result of checking whether to show the save dialog, when session can be saved. * @@ -6137,9 +6011,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState continue; } final AutofillId viewId = dataset.getFieldIds().get(i); + final ViewState viewState = mViewStates.get(viewId); + if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly() + && viewState != null && viewState.id.getSessionId() != id) { + if (sVerbose) { + Slog.v(TAG, "Skipping filling view: " + + viewId + " as it isn't part of the current session: " + id); + } + continue; + } ids.add(viewId); values.add(dataset.getFieldValues().get(i)); - final ViewState viewState = mViewStates.get(viewId); if (viewState != null && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) { if (sVerbose) { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 8a2aa616f8e6..f5562d27f4e8 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -122,9 +122,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; /** - * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent - * with virtual displays created via {@link DisplayManager} and allow for the creation of - * private, auto-mirror, and fixed orientation displays since + * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent + * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow + * for the creation of private, auto-mirror, and fixed orientation displays since * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. * * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index cfe56e910b8a..5cb100a1bfa5 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -38,6 +38,7 @@ import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtualnative.IVirtualDeviceManagerNative; import android.content.AttributionSource; import android.content.Context; import android.content.Intent; @@ -88,8 +89,11 @@ public class VirtualDeviceManagerService extends SystemService { private static final String TAG = "VirtualDeviceManagerService"; + private static final String VIRTUAL_DEVICE_NATIVE_SERVICE = "virtualdevice_native"; + private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; + private final VirtualDeviceManagerNativeImpl mNativeImpl; private final VirtualDeviceManagerInternal mLocalService; private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext()); private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -125,6 +129,7 @@ public class VirtualDeviceManagerService extends SystemService { public VirtualDeviceManagerService(Context context) { super(context); mImpl = new VirtualDeviceManagerImpl(); + mNativeImpl = Flags.enableNativeVdm() ? new VirtualDeviceManagerNativeImpl() : null; mLocalService = new LocalService(); } @@ -155,6 +160,9 @@ public class VirtualDeviceManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); + if (Flags.enableNativeVdm()) { + publishBinderService(VIRTUAL_DEVICE_NATIVE_SERVICE, mNativeImpl); + } publishLocalService(VirtualDeviceManagerInternal.class, mLocalService); ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService( ActivityTaskManagerInternal.class); @@ -590,6 +598,19 @@ public class VirtualDeviceManagerService extends SystemService { } } + final class VirtualDeviceManagerNativeImpl extends IVirtualDeviceManagerNative.Stub { + @Override // Binder call + public int[] getDeviceIdsForUid(int uid) { + return mLocalService + .getDeviceIdsForUid(uid).stream().mapToInt(Integer::intValue).toArray(); + } + + @Override // Binder call + public int getDevicePolicy(int deviceId, int policyType) { + return mImpl.getDevicePolicy(deviceId, policyType); + } + } + private final class LocalService extends VirtualDeviceManagerInternal { @GuardedBy("mVirtualDeviceManagerLock") private final ArrayList<VirtualDisplayListener> diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index f59417046c85..1a8dd3a7316e 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; import static android.service.contentcapture.ContentCaptureService.setClientState; import static android.view.contentcapture.ContentCaptureHelper.toList; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; @@ -112,6 +115,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -203,6 +207,17 @@ public class ContentCaptureManagerService extends @GuardedBy("mLock") int mDevCfgContentProtectionBufferSize; + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionRequiredGroups; + + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionOptionalGroups; + + @GuardedBy("mLock") + int mDevCfgContentProtectionOptionalGroupsThreshold; + private final Executor mDataShareExecutor = Executors.newCachedThreadPool(); private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -226,6 +241,11 @@ public class ContentCaptureManagerService extends com.android.internal.R.string.config_defaultContentCaptureService), UserManager.DISALLOW_CONTENT_CAPTURE, /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH); + + mDevCfgContentProtectionRequiredGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS; + mDevCfgContentProtectionOptionalGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS; DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, ActivityThread.currentApplication().getMainExecutor(), (properties) -> onDeviceConfigChange(properties)); @@ -422,6 +442,9 @@ public class ContentCaptureManagerService extends case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: case ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD: setFineTuneParamsFromDeviceConfig(); return; default: @@ -433,6 +456,8 @@ public class ContentCaptureManagerService extends /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) protected void setFineTuneParamsFromDeviceConfig() { + String contentProtectionRequiredGroupsConfig; + String contentProtectionOptionalGroupsConfig; synchronized (mLock) { mDevCfgMaxBufferSize = DeviceConfig.getInt( @@ -486,6 +511,24 @@ public class ContentCaptureManagerService extends ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE); + contentProtectionRequiredGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG); + contentProtectionOptionalGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG); + mDevCfgContentProtectionOptionalGroupsThreshold = + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); if (verbose) { Slog.v( TAG, @@ -507,9 +550,24 @@ public class ContentCaptureManagerService extends + ", contentProtectionAppsBlocklistSize=" + mDevCfgContentProtectionAppsBlocklistSize + ", contentProtectionBufferSize=" - + mDevCfgContentProtectionBufferSize); + + mDevCfgContentProtectionBufferSize + + ", contentProtectionRequiredGroupsConfig=" + + contentProtectionRequiredGroupsConfig + + ", contentProtectionOptionalGroupsConfig=" + + contentProtectionOptionalGroupsConfig + + ", contentProtectionOptionalGroupsThreshold=" + + mDevCfgContentProtectionOptionalGroupsThreshold); } } + + List<List<String>> contentProtectionRequiredGroups = + parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig); + List<List<String>> contentProtectionOptionalGroups = + parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig); + synchronized (mLock) { + mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups; + mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups; + } } private void setLoggingLevelFromDeviceConfig() { @@ -786,6 +844,15 @@ public class ContentCaptureManagerService extends pw.print(prefix2); pw.print("contentProtectionBufferSize: "); pw.println(mDevCfgContentProtectionBufferSize); + pw.print(prefix2); + pw.print("contentProtectionRequiredGroupsSize: "); + pw.println(mDevCfgContentProtectionRequiredGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsSize: "); + pw.println(mDevCfgContentProtectionOptionalGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsThreshold: "); + pw.println(mDevCfgContentProtectionOptionalGroupsThreshold); pw.print(prefix); pw.println("Global Options:"); mGlobalContentCaptureOptions.dump(prefix2, pw); @@ -890,6 +957,16 @@ public class ContentCaptureManagerService extends return mContentCaptureManagerServiceStub; } + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @NonNull + protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) { + if (verbose) { + Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config); + } + return Collections.emptyList(); + } + final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { @Override @@ -1277,7 +1354,10 @@ public class ContentCaptureManagerService extends isContentCaptureReceiverEnabled || whitelistedComponents != null, new ContentCaptureOptions.ContentProtectionOptions( isContentProtectionReceiverEnabled, - mDevCfgContentProtectionBufferSize), + mDevCfgContentProtectionBufferSize, + mDevCfgContentProtectionRequiredGroups, + mDevCfgContentProtectionOptionalGroups, + mDevCfgContentProtectionOptionalGroupsThreshold), whitelistedComponents); if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options); return options; diff --git a/services/core/Android.bp b/services/core/Android.bp index 6521fabe5b7c..e5225f65f22a 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -183,6 +183,7 @@ java_library_static { "cbor-java", "display_flags_lib", "icu4j_calendar_astronomer", + "android.security.aaid_aidl-java", "netd-client", "overlayable_policy_aidl-java", "SurfaceFlingerProperties", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index cd879083927f..8df54569cccd 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1428,6 +1428,12 @@ public abstract class PackageManagerInternal { @UserIdInt int userId); /** + * Sends the PACKAGE_RESTARTED broadcast on the package manager handler thread. + */ + public abstract void sendPackageRestartedBroadcast(@NonNull String packageName, + int uid, @Intent.Flags int flags); + + /** * Return a list of all historical install sessions for the given user. */ public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions( diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 556eba6ced76..e9d4d76c6131 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.os.Flags.stateOfHealthPublic; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static com.android.server.health.Utils.copyV1Battery; @@ -27,7 +28,6 @@ import android.app.BroadcastOptions; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.health.HealthInfo; import android.hardware.health.V2_1.BatteryCapacityLevel; @@ -1333,10 +1333,14 @@ public final class BatteryService extends SystemService { @Override public int getProperty(int id, final BatteryProperty prop) throws RemoteException { switch (id) { + case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH: + if (stateOfHealthPublic()) { + break; + } + case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE: case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE: case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY: - case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH: mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null); break; diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index d47a3991a966..db89cac2a943 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -87,7 +87,7 @@ option java_package com.android.server # replaces 27510 with a row per notification 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1) # a notification emited noise, vibration, or light -27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1) +27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1) # a notification was added to a autogroup 27533 notification_autogrouped (key|3) # notification was removed from an autogroup diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 93dca2f4f192..57ed5a298d9f 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -41,6 +41,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.Message; import android.os.RemoteException; @@ -153,6 +154,9 @@ public final class PinnerService extends SystemService { @GuardedBy("this") private ArraySet<Integer> mPinKeys; + private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size"; + private static final long DEFAULT_ANON_SIZE = + SystemProperties.getLong("pinner.pin_shared_anon_size", 0); private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB private long mPinAnonSize; private long mPinAnonAddress; @@ -180,6 +184,17 @@ public final class PinnerService extends SystemService { } }; + private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener = + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT.equals(properties.getNamespace()) + && properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) { + refreshPinAnonConfig(); + } + } + }; + public PinnerService(Context context) { super(context); @@ -206,6 +221,11 @@ public final class PinnerService extends SystemService { registerUidListener(); registerUserSetupCompleteListener(); + + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + new HandlerExecutor(mPinnerHandler), + mDeviceConfigListener); } @Override @@ -344,6 +364,8 @@ public final class PinnerService extends SystemService { } } } + + refreshPinAnonConfig(); } /** @@ -560,11 +582,6 @@ public final class PinnerService extends SystemService { pinKeys.add(KEY_ASSISTANT); } - mPinAnonSize = DeviceConfig.getLong(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, - "pin_anon_size", - SystemProperties.getLong("pinner.pin_anon_size", 0)); - mPinAnonSize = Math.max(0, Math.min(mPinAnonSize, MAX_ANON_SIZE)); - return pinKeys; } @@ -604,7 +621,6 @@ public final class PinnerService extends SystemService { int key = currentPinKeys.valueAt(i); pinApp(key, userHandle, true /* force */); } - pinAnonRegion(); } /** @@ -689,10 +705,28 @@ public final class PinnerService extends SystemService { } /** + * Handle any changes in the anon region pinner config. + */ + private void refreshPinAnonConfig() { + long newPinAnonSize = + DeviceConfig.getLong( + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + DEVICE_CONFIG_KEY_ANON_SIZE, + DEFAULT_ANON_SIZE); + newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE)); + if (newPinAnonSize != mPinAnonSize) { + mPinAnonSize = newPinAnonSize; + pinAnonRegion(); + } + } + + /** * Pin an empty anonymous region. This should only be used for ablation experiments. */ private void pinAnonRegion() { if (mPinAnonSize == 0) { + Slog.d(TAG, "pinAnonRegion: releasing pinned region"); + unpinAnonRegion(); return; } long alignedPinSize = mPinAnonSize; @@ -700,15 +734,21 @@ public final class PinnerService extends SystemService { alignedPinSize -= alignedPinSize % PAGE_SIZE; Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize); } - if (mPinAnonAddress != 0 - && mCurrentlyPinnedAnonSize != alignedPinSize) { + if (mPinAnonAddress != 0) { + if (mCurrentlyPinnedAnonSize == alignedPinSize) { + Slog.d(TAG, "pinAnonRegion: already pinned region of size " + alignedPinSize); + return; + } + Slog.d(TAG, "pinAnonRegion: resetting pinned region for new size " + alignedPinSize); unpinAnonRegion(); } long address = 0; try { + // Map as SHARED to avoid changing rss.anon for system_server (per /proc/*/status). + // The mapping is visible in other rss metrics, and as private dirty in smaps/meminfo. address = Os.mmap(0, alignedPinSize, OsConstants.PROT_READ | OsConstants.PROT_WRITE, - OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS, + OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS, new FileDescriptor(), /*offset=*/0); Unsafe tempUnsafe = null; @@ -729,7 +769,7 @@ public final class PinnerService extends SystemService { mCurrentlyPinnedAnonSize = alignedPinSize; mPinAnonAddress = address; address = -1; - Slog.e(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize); + Slog.w(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize); } catch (Exception ex) { Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex); return; @@ -744,6 +784,8 @@ public final class PinnerService extends SystemService { if (mPinAnonAddress != 0) { safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize); } + mPinAnonAddress = 0; + mCurrentlyPinnedAnonSize = 0; } /** diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 962f38f10b5d..beea063221fb 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -214,9 +214,12 @@ class StorageManagerService extends IStorageManager.Stub // external storage service. public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10; - /** Extended timeout for the system server watchdog. */ + /** Extended timeout for the system server watchdog. */ private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000; + /** Extended timeout for the system server watchdog for vold#partition operation. */ + private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000; + @GuardedBy("mLock") private final Set<Integer> mFuseMountedUser = new ArraySet<>(); @@ -2338,6 +2341,8 @@ class StorageManagerService extends IStorageManager.Stub try { // TODO(b/135341433): Remove cautious logging when FUSE is stable Slog.i(TAG, "Mounting volume " + vol); + Watchdog.getInstance().setOneOffTimeoutForMonitors( + SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow"); mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() { @Override public boolean onVolumeChecking(FileDescriptor fd, String path, @@ -2463,10 +2468,12 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) @Override public void partitionPublic(String diskId) { - super.partitionPublic_enforcePermission(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + + Watchdog.getInstance().setOneOffTimeoutForMonitors( + PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1); waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2483,6 +2490,9 @@ class StorageManagerService extends IStorageManager.Stub enforceAdminUser(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + + Watchdog.getInstance().setOneOffTimeoutForMonitors( + PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1); waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2499,6 +2509,9 @@ class StorageManagerService extends IStorageManager.Stub enforceAdminUser(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + + Watchdog.getInstance().setOneOffTimeoutForMonitors( + PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio); waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 330742a2d748..5fb889a23fc5 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5023,7 +5023,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle(); - Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class); if (intent != null && intent.getClass() != Intent.class) { return false; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 553b08501925..0956c6ded013 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -409,6 +409,13 @@ public final class ActiveServices { AppWidgetManagerInternal mAppWidgetManagerInternal; + /** + * The available ANR timers. + */ + private final ProcessAnrTimer mActiveServiceAnrTimer; + private final ServiceAnrTimer mShortFGSAnrTimer; + private final ServiceAnrTimer mServiceFGAnrTimer; + // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); @@ -663,6 +670,15 @@ public final class ActiveServices { final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); this.mFGSLogger = new ForegroundServiceTypeLoggerModule(); + this.mActiveServiceAnrTimer = new ProcessAnrTimer(service, + ActivityManagerService.SERVICE_TIMEOUT_MSG, + "SERVICE_TIMEOUT"); + this.mShortFGSAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, + "FGS_TIMEOUT"); + this.mServiceFGAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, + "SERVICE_FOREGROUND_TIMEOUT"); } void systemServicesReady() { @@ -2083,8 +2099,7 @@ public final class ActiveServices { r.fgRequired = false; r.fgWaiting = false; alreadyStartedOp = stopProcStatsOp = true; - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); } final ProcessServiceRecord psr = r.app.mServices; @@ -3313,7 +3328,7 @@ public final class ActiveServices { } void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) { - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); + mShortFGSAnrTimer.cancel(sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG, sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr); @@ -3387,9 +3402,11 @@ public final class ActiveServices { Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr + " " + sr.getShortFgsTimedEventDescription(nowUptime)); } + mShortFGSAnrTimer.discard(sr); return; } Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr); + mShortFGSAnrTimer.accept(sr); traceInstant("short FGS timeout: ", sr); logFGSStateChangeLocked(sr, @@ -3413,11 +3430,10 @@ public final class ActiveServices { msg, sr.getShortFgsInfo().getProcStateDemoteTime()); } - { - final Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); - mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime()); - } + // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now". + // Compute the time from "now" when starting the anr timer. + mShortFGSAnrTimer.start(sr, + sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis()); } } @@ -4847,8 +4863,7 @@ public final class ActiveServices { // a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT // again. if (r.fgRequired && r.fgWaiting) { - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); r.fgWaiting = false; } @@ -5691,8 +5706,7 @@ public final class ActiveServices { } mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); @@ -6128,7 +6142,7 @@ public final class ActiveServices { if (psr.numberOfExecutingServices() == 0) { if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, "No more executingServices of " + r.shortInstanceName); - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); + if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app); } else if (r.executeFg) { // Need to re-evaluate whether the app still needs to be in the foreground. for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) { @@ -6816,13 +6830,16 @@ public final class ActiveServices { synchronized (mAm) { if (proc.isDebugging()) { // The app's being debugged, ignore timeout. + mActiveServiceAnrTimer.discard(proc); return; } final ProcessServiceRecord psr = proc.mServices; if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null || proc.isKilled()) { + mActiveServiceAnrTimer.discard(proc); return; } + mActiveServiceAnrTimer.accept(proc); final long now = SystemClock.uptimeMillis(); final long maxTime = now - (psr.shouldExecServicesFg() @@ -6855,12 +6872,11 @@ public final class ActiveServices { timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName, waitedMillis); } else { - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg() - ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : - (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)); + final long delay = psr.shouldExecServicesFg() + ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : + (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT) + - SystemClock.uptimeMillis(); + mActiveServiceAnrTimer.start(proc, delay); } } @@ -6886,12 +6902,15 @@ public final class ActiveServices { synchronized (mAm) { timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded(); if (!r.fgRequired || !r.fgWaiting || r.destroying) { + mServiceFGAnrTimer.discard(r); return; } + mServiceFGAnrTimer.accept(r); app = r.app; if (app != null && app.isDebugging()) { // The app's being debugged; let it ride + mServiceFGAnrTimer.discard(r); return; } @@ -6948,26 +6967,46 @@ public final class ActiveServices { ForegroundServiceDidNotStartInTimeException.createExtrasForService(service)); } + private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> { + + ProcessAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ProcessRecord proc, long millis) { + start(proc, proc.getPid(), proc.uid, millis); + } + } + + private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> { + + ServiceAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ServiceRecord service, long millis) { + start(service, + (service.app != null) ? service.app.getPid() : 0, + service.appInfo.uid, + millis); + } + } + void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg() - ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT); + final long delay = proc.mServices.shouldExecServicesFg() + ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT; + mActiveServiceAnrTimer.start(proc, delay); } void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) { if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG); - msg.obj = r; r.fgWaiting = true; - mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs); + mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs); } final class ServiceDumper { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d3ce47c56e49..b43b986064fe 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -377,6 +377,7 @@ import android.util.FeatureFlagUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -562,7 +563,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait for a launched process to complete its app startup before we ANR. - static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + static final int BIND_APPLICATION_TIMEOUT = 15 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait to kill an application zygote, after the last process using // it has gone away. @@ -1531,6 +1532,11 @@ public class ActivityManagerService extends IActivityManager.Stub */ int mBootPhase; + /** + * The time stamp that all apps have received BOOT_COMPLETED. + */ + volatile long mBootCompletedTimestamp; + @GuardedBy("this") boolean mDeterministicUidIdle = false; @@ -1630,7 +1636,8 @@ public class ActivityManagerService extends IActivityManager.Stub static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79; static final int ADD_UID_TO_OBSERVER_MSG = 80; static final int REMOVE_UID_FROM_OBSERVER_MSG = 81; - static final int BIND_APPLICATION_TIMEOUT_MSG = 82; + static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82; + static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1983,15 +1990,11 @@ public class ActivityManagerService extends IActivityManager.Stub case UPDATE_CACHED_APP_HIGH_WATERMARK: { mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj); } break; - case BIND_APPLICATION_TIMEOUT_MSG: { - ProcessRecord app = (ProcessRecord) msg.obj; - - final String anrMessage; - synchronized (app) { - anrMessage = "Process " + app + " failed to complete startup"; - } - - mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage)); + case BIND_APPLICATION_TIMEOUT_SOFT_MSG: { + handleBindApplicationTimeoutSoft((ProcessRecord) msg.obj, msg.arg1); + } break; + case BIND_APPLICATION_TIMEOUT_HARD_MSG: { + handleBindApplicationTimeoutHard((ProcessRecord) msg.obj); } break; } } @@ -4160,26 +4163,34 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") private void finishForceStopPackageLocked(final String packageName, int uid) { - Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, - Uri.fromParts("package", packageName, null)); + int flags = 0; if (!mProcessesReady) { - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY - | Intent.FLAG_RECEIVER_FOREGROUND); - } - final int userId = UserHandle.getUserId(uid); - final int[] broadcastAllowList = - getPackageManagerInternal().getVisibilityAllowList(packageName, userId); - intent.putExtra(Intent.EXTRA_UID, uid); - intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - broadcastIntentLocked(null /* callerApp */, null /* callerPackage */, - null /* callerFeatureId */, intent, null /* resolvedType */, - null /* resultToApp */, null /* resultTo */, - 0 /* resultCode */, null /* resultData */, null /* resultExtras */, - null /* requiredPermissions */, null /* excludedPermissions */, - null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */, - false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(), - Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE, - broadcastAllowList, null /* filterExtrasForReceiver */); + flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND; + } + if (android.content.pm.Flags.stayStopped()) { + // Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED + mPackageManagerInt.sendPackageRestartedBroadcast(packageName, + uid, flags); + } else { + Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, + Uri.fromParts("package", packageName, null)); + intent.addFlags(flags); + final int userId = UserHandle.getUserId(uid); + final int[] broadcastAllowList = + getPackageManagerInternal().getVisibilityAllowList(packageName, userId); + intent.putExtra(Intent.EXTRA_UID, uid); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + broadcastIntentLocked(null /* callerApp */, null /* callerPackage */, + null /* callerFeatureId */, intent, null /* resolvedType */, + null /* resultToApp */, null /* resultTo */, + 0 /* resultCode */, null /* resultData */, null /* resultExtras */, + null /* requiredPermissions */, null /* excludedPermissions */, + null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */, + false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(), + Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE, + broadcastAllowList, null /* filterExtrasForReceiver */); + } } private void cleanupDisabledPackageComponentsLocked( @@ -4749,6 +4760,7 @@ public class ActivityManagerService extends IActivityManager.Stub mPlatformCompat.resetReporting(app.info); } final ProviderInfoList providerList = ProviderInfoList.fromList(providers); + app.mProfile.mLastCpuDelayTime.set(app.getCpuDelayTime()); if (app.getIsolatedEntryPoint() != null) { // This is an isolated process which should just call an entry point instead of // being bound to an application. @@ -4786,9 +4798,10 @@ public class ActivityManagerService extends IActivityManager.Stub app.getStartElapsedTime(), app.getStartUptime()); } - Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG); + Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG); msg.obj = app; - mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT); + msg.arg1 = BIND_APPLICATION_TIMEOUT; + mHandler.sendMessageDelayed(msg, msg.arg1 /* BIND_APPLICATION_TIMEOUT */); mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); if (profilerInfo != null) { @@ -4865,7 +4878,8 @@ public class ActivityManagerService extends IActivityManager.Stub } if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) { - mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app); + mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_SOFT_MSG, app); + mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_HARD_MSG, app); } else { Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid + ". Uid: " + uid); @@ -5002,6 +5016,35 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) { + // Similar logic as the broadcast delivery timeout: + // instead of immediately triggering an ANR, extend the timeout by + // the amount of time the process was runnable-but-waiting; we're + // only willing to do this once before triggering an hard ANR. + final long cpuDelayTime = app.getCpuDelayTime() - app.mProfile.mLastCpuDelayTime.get(); + final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis); + + if (hardTimeoutMillis == 0) { + handleBindApplicationTimeoutHard(app); + return; + } + + Slog.i(TAG, "Extending process start timeout by " + hardTimeoutMillis + "ms for " + app); + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplicationTimeSoft " + + app.processName + "(" + app.getPid() + ")"); + final Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_HARD_MSG, app); + mHandler.sendMessageDelayed(msg, hardTimeoutMillis); + } + + private void handleBindApplicationTimeoutHard(ProcessRecord app) { + final String anrMessage; + synchronized (app) { + anrMessage = "Process " + app + " failed to complete startup"; + } + + mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage)); + } + /** * @return The last part of the string of an intent's action. */ @@ -5126,10 +5169,14 @@ public class ActivityManagerService extends IActivityManager.Stub public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { - synchronized (mProcLock) { - mAppProfiler.requestPssAllProcsLPr( - SystemClock.uptimeMillis(), true, false); - } + mBootCompletedTimestamp = SystemClock.uptimeMillis(); + // Defer the full Pss collection as the system is really busy now. + mHandler.postDelayed(() -> { + synchronized (mProcLock) { + mAppProfiler.requestPssAllProcsLPr( + SystemClock.uptimeMillis(), true, false); + } + }, mConstants.FULL_PSS_MIN_INTERVAL); } }); maybeLogUserspaceRebootEvent(); diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java index 9ba49ce35dad..3e17930e3cb9 100644 --- a/services/core/java/com/android/server/am/AnrTimer.java +++ b/services/core/java/com/android/server/am/AnrTimer.java @@ -28,6 +28,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; +import android.text.format.TimeMigrationUtils; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; @@ -44,7 +45,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -150,7 +150,7 @@ class AnrTimer<V> { /** A partial stack that localizes the caller of the operation. */ final StackTraceElement[] stack; /** The date, in local time, the error was created. */ - final String date; + final long timestamp; Error(@NonNull String issue, @NonNull String operation, @NonNull String tag, @NonNull StackTraceElement[] stack, @NonNull String arg) { @@ -159,7 +159,7 @@ class AnrTimer<V> { this.tag = tag; this.stack = stack; this.arg = arg; - this.date = new Date().toString(); + this.timestamp = SystemClock.elapsedRealtime(); } } @@ -347,20 +347,23 @@ class AnrTimer<V> { * main Looper. */ @NonNull - Handler getHandler(@NonNull Handler.Callback callback) { + Handler newHandler(@NonNull Handler.Callback callback) { Looper looper = mReferenceHandler.getLooper(); if (looper == null) looper = Looper.getMainLooper(); return new Handler(looper, callback); - }; + } - /** Return a CpuTracker. */ + /** + * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes + * for unit tests. + **/ @NonNull - CpuTracker getTracker() { + CpuTracker newTracker() { return new CpuTracker(); } /** Return true if the feature is enabled. */ - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return anrTimerServiceEnabled(); } } @@ -401,8 +404,8 @@ class AnrTimer<V> { /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */ @VisibleForTesting HandlerTimerService(@NonNull Injector injector) { - mHandler = injector.getHandler(this::expires); - mCpu = injector.getTracker(); + mHandler = injector.newHandler(this::expires); + mCpu = injector.newTracker(); } /** Post a message with the specified timeout. The timer is not modified. */ @@ -513,7 +516,26 @@ class AnrTimer<V> { private final FeatureSwitch mFeature; /** - * The common constructor. A null injector results in a normal, production timer. + * Create one AnrTimer instance. The instance is given a handler and a "what". Individual + * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent + * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set + * to the timer key. + * + * AnrTimer instances have a label, which must be unique. The label is used for reporting and + * debug. + * + * If an individual timer expires internally, and the "extend" parameter is true, then the + * AnrTimer may extend the individual timer rather than immediately delivering the timeout to + * the client. The extension policy is not part of the instance. + * + * This method accepts an {@link #Injector} to tune behavior for testing. This method should + * not be called directly by regular clients. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. + * @param injector An {@link #Injector} to tune behavior for testing. */ @VisibleForTesting AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend, @@ -522,7 +544,7 @@ class AnrTimer<V> { mWhat = what; mLabel = label; mExtend = extend; - boolean enabled = injector.getFeatureEnabled(); + boolean enabled = injector.isFeatureEnabled(); if (!enabled) { mFeature = new FeatureDisabled(); mTimerService = null; @@ -538,14 +560,25 @@ class AnrTimer<V> { } /** - * Create one timer instance for production. The client can ask for extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler, + * int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) { this(handler, what, label, extend, new Injector(handler)); } /** - * Create one timer instance for production. There are no extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled. + * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label) { this(handler, what, label, false); @@ -555,6 +588,8 @@ class AnrTimer<V> { * Return true if the service is enabled on this instance. Clients should use this method to * decide if the feature is enabled, and not read the flags directly. This method should be * deleted if and when the feature is enabled permanently. + * + * @return true if the service is flag-enabled. */ boolean serviceEnabled() { return mFeature.enabled(); @@ -642,7 +677,7 @@ class AnrTimer<V> { } /** - * Report something about a timer. + * Generate a log message for a timer. */ private void report(@NonNull Timer timer, @NonNull String msg) { Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg)); @@ -654,9 +689,13 @@ class AnrTimer<V> { */ private abstract class FeatureSwitch { abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs); + abstract boolean cancel(@NonNull V arg); + abstract boolean accept(@NonNull V arg); + abstract boolean discard(@NonNull V arg); + abstract boolean enabled(); } @@ -666,6 +705,7 @@ class AnrTimer<V> { */ private class FeatureDisabled extends FeatureSwitch { /** Start a timer by sending a message to the client's handler. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Message msg = mHandler.obtainMessage(mWhat, arg); mHandler.sendMessageDelayed(msg, timeoutMs); @@ -673,22 +713,26 @@ class AnrTimer<V> { } /** Cancel a timer by removing the message from the client's handler. */ + @Override boolean cancel(@NonNull V arg) { mHandler.removeMessages(mWhat, arg); return true; } /** accept() is a no-op when the feature is disabled. */ + @Override boolean accept(@NonNull V arg) { return true; } /** discard() is a no-op when the feature is disabled. */ + @Override boolean discard(@NonNull V arg) { return true; } /** The feature is not enabled. */ + @Override boolean enabled() { return false; } @@ -703,16 +747,17 @@ class AnrTimer<V> { /** * Start a timer. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this); synchronized (mLock) { Timer old = mTimerMap.get(arg); + // There is an existing timer. If the timer was running, then cancel the running + // timer and restart it. If the timer was expired record a protocol error and + // discard the expired timer. if (old != null) { - // There is an existing timer. This is a protocol error in the client. - // Record the error and then clean up by canceling running timers and - // discarding expired timers. - restartedLocked(old.status, arg); if (old.status == TIMER_EXPIRED) { + restartedLocked(old.status, arg); discard(arg); } else { cancel(arg); @@ -735,6 +780,7 @@ class AnrTimer<V> { /** * Cancel a timer. Return false if the timer was not found. */ + @Override boolean cancel(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -755,6 +801,7 @@ class AnrTimer<V> { * Accept a timer in the framework-level handler. The timeout has been accepted and the * timeout handler is executing. Return false if the timer was not found. */ + @Override boolean accept(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -775,6 +822,7 @@ class AnrTimer<V> { * longer interesting. No statistics are collected. Return false if the time was not * found. */ + @Override boolean discard(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -791,40 +839,58 @@ class AnrTimer<V> { } /** The feature is enabled. */ + @Override boolean enabled() { return true; } } /** - * Start a timer associated with arg. If a timer already exists with the same arg, then that - * timer is canceled and a new timer is created. This returns false if the timer cannot be - * created. + * Start a timer associated with arg. The same object must be used to cancel, accept, or + * discard a timer later. If a timer already exists with the same arg, then the existing timer + * is canceled and a new timer is created. + * + * @param arg The key by which the timer is known. This is never examined or modified. + * @param pid The Linux process ID of the target being timed. + * @param uid The Linux user ID of the target being timed. + * @param timeoutMs The timer timeout, in milliseconds. + * @return true if the timer was successfully created. */ boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { return mFeature.start(arg, pid, uid, timeoutMs); } /** - * Cancel a running timer and remove it from any list. This returns true if the timer was - * found and false otherwise. It is not an error to cancel a non-existent timer. It is also - * not an error to cancel an expired timer. + * Cancel the running timer associated with arg. The timer is forgotten. If the timer has + * expired, the call is treated as a discard. No errors are reported if the timer does not + * exist or if the timer has expired. + * + * @return true if the timer was found and was running. */ boolean cancel(@NonNull V arg) { return mFeature.cancel(arg); } /** - * Accept an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Accept the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is + * an error to accept a running timer, however the running timer will be canceled. + * + * @return true if the timer was found and was expired. */ boolean accept(@NonNull V arg) { return mFeature.accept(arg); } /** - * Discard an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Discard the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a false ANR. ((See {@link #accept} for an alternate response.) One + * reason to discard an expired timer is if the process being timed was also being debugged: + * such a process could be stopped at a breakpoint and its failure to respond would not be an + * error. It is an error to discard a running timer, however the running timer will be + * canceled. + * + * @return true if the timer was found and was expired. */ boolean discard(@NonNull V arg) { return mFeature.discard(arg); @@ -913,7 +979,10 @@ class AnrTimer<V> { private static void dump(IndentingPrintWriter ipw, int seq, Error err) { ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag, err.issue, err.arg); - ipw.format(" date:%s\n", err.date); + + final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + final long etime = offset + err.timestamp; + ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); ipw.increaseIndent(); for (int i = 0; i < err.stack.length; i++) { ipw.println(" " + err.stack[i].toString()); diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 928b5d8f3ca7..3cf4332349d7 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1205,6 +1205,8 @@ public class AppProfiler { trackerMemFactor = mService.mProcessStats.getMemFactorLocked(); } + mLastMemoryLevel = memFactor; + mLastNumProcesses = mService.mProcessList.getLruSizeLOSP(); if (mService.mConstants.USE_MODERN_TRIM) { // Modern trim is not sent based on lowmem state // Dispatch UI_HIDDEN to processes that need it @@ -1235,8 +1237,6 @@ public class AppProfiler { return false; } - mLastMemoryLevel = memFactor; - mLastNumProcesses = mService.mProcessList.getLruSizeLOSP(); if (memFactor != ADJ_MEM_FACTOR_NORMAL) { if (mLowRamStartTime == 0) { mLowRamStartTime = now; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a42890707368..d19eae5b0709 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -258,7 +258,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6; private static final int MSG_UID_STATE_CHANGED = 7; - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This constant should be deleted if and + // when the flag is fused on. private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8; private void enqueueUpdateRunningList() { @@ -274,7 +275,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { updateRunningList(); return true; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This case should be deleted if + // and when the flag is fused on. case MSG_DELIVERY_TIMEOUT_SOFT: { synchronized (mService) { deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1); @@ -1169,7 +1171,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultTo = null; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.start()} if and when the flag is fused on. private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (mAnrTimer.serviceEnabled()) { @@ -1181,7 +1184,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on. private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) { mAnrTimer.cancel(queue); if (!mAnrTimer.serviceEnabled()) { @@ -1189,7 +1193,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be deleted entirely + // if and when the flag is fused on. private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (queue.app != null) { diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 4b622f589adb..903cb7bcfaed 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -175,13 +175,15 @@ final class CoreSettingsObserver extends ContentObserver { TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT)); // Register all text aconfig flags. - for (String flag : TextFlags.TEXT_ACONFIGS_FLAGS) { + for (int i = 0; i < TextFlags.TEXT_ACONFIGS_FLAGS.length; i++) { + final String flag = TextFlags.TEXT_ACONFIGS_FLAGS[i]; + final boolean defaultValue = TextFlags.TEXT_ACONFIG_DEFAULT_VALUE[i]; sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>( TextFlags.NAMESPACE, flag, TextFlags.getKeyForFlag(flag), boolean.class, - false)); // All aconfig flags are false by default. + defaultValue)); } // add other device configs here... } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4572766371ec..e0e6cade5f27 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1439,7 +1439,7 @@ public final class ProcessList { } public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, - boolean sleeping, long now) { + boolean sleeping, long now, long earliest) { boolean first; float scalingFactor; final int memState = sProcStateToProcMem[procState]; @@ -1470,7 +1470,7 @@ public final class ProcessList { if (delay > PSS_MAX_INTERVAL) { delay = PSS_MAX_INTERVAL; } - return now + delay; + return Math.max(now + delay, earliest); } long getMemLevel(int adjustment) { diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index db74f1aed422..940c58b7a5f0 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -142,6 +142,11 @@ final class ProcessProfileRecord { final AtomicLong mCurCpuTime = new AtomicLong(0); /** + * How long the process has spent on waiting in the runqueue since fork. + */ + final AtomicLong mLastCpuDelayTime = new AtomicLong(0); + + /** * Last selected memory trimming level. */ @CompositeRWLock({"mService", "mProcLock"}) @@ -570,7 +575,11 @@ final class ProcessProfileRecord { @GuardedBy("mProfilerLock") long computeNextPssTime(int procState, boolean test, boolean sleeping, long now) { - return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now); + return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now, + // Cap the Pss time to make sure no Pss is collected during the very few + // minutes after the system is boot, given the system is already busy. + Math.max(mService.mBootCompletedTimestamp, mService.mLastIdleTime) + + mService.mConstants.FULL_PSS_MIN_INTERVAL); } private static void commitNextPssTime(ProcStateMemTracker tracker) { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index d8a269598bdc..3771c05a294e 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1255,11 +1255,10 @@ class ProcessRecord implements WindowProcessListener { killProcessGroup = true; } if (killProcessGroup) { - if (async) { - ProcessList.killProcessGroup(uid, mPid); - } else { + if (!async) { Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL); } + ProcessList.killProcessGroup(uid, mPid); } } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 1ba1f55af71b..16e3fdf2a6ab 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -163,9 +163,12 @@ public class SettingsToPropertiesMapper { "wear_system_health", "wear_systems", "window_surfaces", - "windowing_frontend" + "windowing_frontend", }; + public static final String NAMESPACE_REBOOT_STAGING = "staged"; + public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; + private final String[] mGlobalSettings; private final String[] mDeviceConfigScopes; @@ -261,6 +264,22 @@ public class SettingsToPropertiesMapper { } }); } + + // add sys prop sync callback for staged flag values + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_REBOOT_STAGING, + AsyncTask.THREAD_POOL_EXECUTOR, + (DeviceConfig.Properties properties) -> { + String scope = properties.getNamespace(); + for (String key : properties.getKeyset()) { + String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key); + if (aconfigPropertyName == null) { + log("unable to construct system property for " + scope + "/" + key); + return; + } + setProperty(aconfigPropertyName, properties.getString(key, null)); + } + }); } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -332,6 +351,35 @@ public class SettingsToPropertiesMapper { } /** + * system property name constructing rule for staged aconfig flags, the flag name + * is in the form of [namespace]*[actual flag name], we should push the following + * to system properties + * "next_boot.[actual sys prop name]". + * If the name contains invalid characters or substrings for system property name, + * will return null. + * @param flagName + * @return + */ + @VisibleForTesting + static String makeAconfigFlagStagedPropertyName(String flagName) { + int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); + if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { + log("invalid staged flag: " + flagName); + return null; + } + + String propertyName = "next_boot." + makeAconfigFlagPropertyName( + flagName.substring(0, idx), flagName.substring(idx+1)); + + if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) + || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { + return null; + } + + return propertyName; + } + + /** * system property name constructing rule for aconfig flags: * "persist.device_config.aconfig_flags.[category_name].[flag_name]". * If the name contains invalid characters or substrings for system property name, diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java index 241abafe6179..85acf707677a 100644 --- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java +++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java @@ -47,6 +47,12 @@ class AudioManagerShellCommand extends ShellCommand { return setEncodedSurroundMode(); case "get-encoded-surround-mode": return getEncodedSurroundMode(); + case "set-sound-dose-value": + return setSoundDoseValue(); + case "get-sound-dose-value": + return getSoundDoseValue(); + case "reset-sound-dose-timeout": + return resetSoundDoseTimeout(); } return 0; } @@ -66,6 +72,12 @@ class AudioManagerShellCommand extends ShellCommand { pw.println(" Sets the encoded surround sound mode to SURROUND_SOUND_MODE"); pw.println(" get-encoded-surround-mode"); pw.println(" Returns the encoded surround sound mode"); + pw.println(" set-sound-dose-value"); + pw.println(" Sets the current sound dose value"); + pw.println(" get-sound-dose-value"); + pw.println(" Returns the current sound dose value"); + pw.println(" reset-sound-dose-timeout"); + pw.println(" Resets the sound dose timeout used for momentary exposure"); } private int setSurroundFormatEnabled() { @@ -162,4 +174,46 @@ class AudioManagerShellCommand extends ShellCommand { getOutPrintWriter().println("Encoded surround mode: " + am.getEncodedSurroundMode()); return 0; } + + private int setSoundDoseValue() { + String soundDoseValueText = getNextArg(); + + if (soundDoseValueText == null) { + getErrPrintWriter().println("Error: no sound dose value specified"); + return 1; + } + + float soundDoseValue = 0.f; + try { + soundDoseValue = Float.parseFloat(soundDoseValueText); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: wrong format specified for sound dose"); + return 1; + } + + if (soundDoseValue < 0) { + getErrPrintWriter().println("Error: invalid value of sound dose"); + return 1; + } + + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + am.setCsd(soundDoseValue); + return 0; + } + + private int getSoundDoseValue() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + getOutPrintWriter().println("Sound dose value: " + am.getCsd()); + return 0; + } + + private int resetSoundDoseTimeout() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + am.setCsd(-1.f); + getOutPrintWriter().println("Reset sound dose momentary exposure timeout"); + return 0; + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 0aa9cc11c432..3243385c3b18 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -36,7 +36,6 @@ import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; - import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -73,7 +72,6 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -267,6 +265,8 @@ public class AudioService extends IAudioService.Stub private final SettingsAdapter mSettings; private final AudioPolicyFacade mAudioPolicy; + private final MusicFxHelper mMusicFxHelper; + /** Debug audio mode */ protected static final boolean DEBUG_MODE = false; @@ -407,9 +407,15 @@ public class AudioService extends IAudioService.Stub private static final int MSG_CONFIGURATION_CHANGED = 54; private static final int MSG_BROADCAST_MASTER_MUTE = 55; - /** Messages handled by the {@link SoundDoseHelper}. */ + /** + * Messages handled by the {@link SoundDoseHelper}, do not exceed + * {@link MUSICFX_HELPER_MSG_START}. + */ /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000; + /** Messages handled by the {@link MusicFxHelper}. */ + /*package*/ static final int MUSICFX_HELPER_MSG_START = 1100; + // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -1304,6 +1310,8 @@ public class AudioService extends IAudioService.Stub 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); mDisplayManager = context.getSystemService(DisplayManager.class); + + mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); } private void initVolumeStreamStates() { @@ -9456,11 +9464,21 @@ public class AudioService extends IAudioService.Stub onConfigurationChanged(); break; + case MusicFxHelper.MSG_EFFECT_CLIENT_GONE: + mMusicFxHelper.handleMessage(msg); + break; + + case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA: + case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA_FORCED: + case SoundDoseHelper.MSG_PERSIST_SAFE_VOLUME_STATE: + case SoundDoseHelper.MSG_PERSIST_MUSIC_ACTIVE_MS: + case SoundDoseHelper.MSG_PERSIST_CSD_VALUES: + case SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION: + mSoundDoseHelper.handleMessage(msg); + break; + default: - if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) { - // msg could be for the SoundDoseHelper - mSoundDoseHelper.handleMessage(msg); - } + Log.e(TAG, "Unsupported msgId " + msg.what); } } } @@ -9695,7 +9713,7 @@ public class AudioService extends IAudioService.Stub } } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) || action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) { - handleAudioEffectBroadcast(context, intent); + mMusicFxHelper.handleAudioEffectBroadcast(context, intent); } else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) { final int[] suspendedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); final String[] suspendedPackages = @@ -9750,27 +9768,6 @@ public class AudioService extends IAudioService.Stub } } // end class AudioServiceUserRestrictionsListener - private void handleAudioEffectBroadcast(Context context, Intent intent) { - String target = intent.getPackage(); - if (target != null) { - Log.w(TAG, "effect broadcast already targeted to " + target); - return; - } - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - // TODO this should target a user-selected panel - List<ResolveInfo> ril = context.getPackageManager().queryBroadcastReceivers( - intent, 0 /* flags */); - if (ril != null && ril.size() != 0) { - ResolveInfo ri = ril.get(0); - if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { - intent.setPackage(ri.activityInfo.packageName); - context.sendBroadcastAsUser(intent, UserHandle.ALL); - return; - } - } - Log.w(TAG, "couldn't find receiver package for effect intent"); - } - private void killBackgroundUserProcessesWithRecordAudioPermission(UserInfo oldUser) { PackageManager pm = mContext.getPackageManager(); // Find the home activity of the user. It should not be killed to avoid expensive restart, diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java new file mode 100644 index 000000000000..6c0fef5f628d --- /dev/null +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -0,0 +1,295 @@ +/* + * 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.audio; + +import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.IUidObserver; +import android.app.UidObserver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.media.AudioManager; +import android.media.audiofx.AudioEffect; +import android.os.Binder; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.audio.AudioService.AudioHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * MusicFx management. + * . + */ +public class MusicFxHelper { + private static final String TAG = "AS.MusicFxHelper"; + + @NonNull private final Context mContext; + + @NonNull private final AudioHandler mAudioHandler; + + // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver. + private final Object mClientUidMapLock = new Object(); + + // The binder token identifying the UidObserver registration. + private IBinder mUidObserverToken = null; + + // Hashmap of UID and list of open sessions for this UID. + @GuardedBy("mClientUidMapLock") + private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>(); + + /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1; + + // UID observer for effect MusicFx clients + private final IUidObserver mEffectUidObserver = new UidObserver() { + @Override public void onUidGone(int uid, boolean disabled) { + Log.w(TAG, " send MSG_EFFECT_CLIENT_GONE"); + mAudioHandler.sendMessageAtTime( + mAudioHandler.obtainMessage(MSG_EFFECT_CLIENT_GONE, + uid /* arg1 */, 0 /* arg2 */, + null /* obj */), 0 /* delay */); + } + }; + + // BindService connection implementation, we don't need any implementation now + private ServiceConnection mMusicFxBindConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.d(TAG, " service connected to " + name); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.d(TAG, " service disconnected from " + name); + } + }; + + MusicFxHelper(@NonNull Context context, @NonNull AudioHandler audioHandler) { + mContext = context; + mAudioHandler = audioHandler; + } + + /** + * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and + * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents. + * + * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}: + * - If the MusicFx process is not running, call bindService with AUTO_CREATE to create. + * - If this is the first audio session in MusicFx, call set foreground service delegate. + * - If this is the first audio session for a given UID, add the UID into observer. + * + * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}: + * - MusicFx will not be foreground delegated anymore. + * - The KeepAliveService of MusicFx will be unbound. + * - The UidObserver will be removed. + */ + public void handleAudioEffectBroadcast(Context context, Intent intent) { + String target = intent.getPackage(); + if (target != null) { + Log.w(TAG, "effect broadcast already targeted to " + target); + return; + } + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + final PackageManager pm = context.getPackageManager(); + // TODO this should target a user-selected panel + List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */); + if (ril != null && ril.size() != 0) { + ResolveInfo ri = ril.get(0); + final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME); + try { + final int senderUid = pm.getPackageUidAsUser(senderPackageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); + if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { + intent.setPackage(ri.activityInfo.packageName); + synchronized (mClientUidMapLock) { + setMusicFxServiceWithObserver(context, intent, senderUid); + } + context.sendBroadcastAsUser(intent, UserHandle.ALL); + return; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Not able to find UID from package: " + senderPackageName + " error: " + + e); + } + } + Log.w(TAG, "couldn't find receiver package for effect intent"); + } + + /** + * Handle the UidObserver onUidGone callback of MusicFx clients. + * All open audio sessions of this UID will be closed. + * If this is the last UID for MusicFx: + * - MusicFx will not be foreground delegated anymore. + * - The KeepAliveService of MusicFx will be unbound. + * - The UidObserver will be removed. + */ + public void handleEffectClientUidGone(int uid) { + synchronized (mClientUidMapLock) { + Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE"); + // Once the uid is no longer running, close all remain audio session(s) for this UID + if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) { + final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid)); + Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions"); + for (Integer session : sessions) { + Intent intent = new Intent( + AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session); + setMusicFxServiceWithObserver(mContext, intent, uid); + Log.i(TAG, "Close session " + session + " of UID " + uid); + } + mClientUidSessionMap.remove(Integer.valueOf(uid)); + } + } + } + + @GuardedBy("mClientUidMapLock") + private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) { + PackageManager pm = context.getPackageManager(); + try { + final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, + AudioManager.AUDIO_SESSION_ID_GENERATE); + if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) { + Log.e(TAG, "Intent missing audio session: " + audioSession); + return; + } + + // only apply to com.android.musicfx and KeepAliveService for now + final String musicFxPackageName = "com.android.musicfx"; + final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService"; + final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); + + if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) { + List<Integer> sessions = new ArrayList<>(); + Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession); + // start foreground service delegate and register UID observer with the first + // session of first UID open + if (0 == mClientUidSessionMap.size()) { + final int procState = ActivityManager.getService().getPackageProcessState( + musicFxPackageName, this.getClass().getPackage().getName()); + // if musicfx process not in binding state, call bindService with AUTO_CREATE + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + Intent bindIntent = new Intent().setClassName(musicFxPackageName, + musicFxKeepAliveService); + context.bindServiceAsUser( + bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE, + UserHandle.of(getCurrentUserId())); + Log.i(TAG, "bindService to " + musicFxPackageName); + } + + Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid + + " procState " + procState); + } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) { + sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); + if (sessions.contains(audioSession)) { + Log.e(TAG, "Audio session " + audioSession + " already exist for UID " + + senderUid + ", abort"); + return; + } + } + // first session of this UID + if (sessions.size() == 0) { + // call registerUidObserverForUids with the first UID and first session + if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) { + mUidObserverToken = ActivityManager.getService().registerUidObserverForUids( + mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid}); + Log.i(TAG, "UID " + senderUid + " registered to observer"); + } else { + // add UID to observer for each new UID + ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG, + senderUid); + Log.i(TAG, "UID " + senderUid + " addeded to observer"); + } + } + + sessions.add(Integer.valueOf(audioSession)); + mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions); + } else { + if (mClientUidSessionMap.get(senderUid) != null) { + Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession); + List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); + sessions.remove(Integer.valueOf(audioSession)); + if (0 == sessions.size()) { + mClientUidSessionMap.remove(Integer.valueOf(senderUid)); + } else { + mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions); + } + + // stop foreground service delegate and unregister UID observer with the + // last session of last UID close + if (0 == mClientUidSessionMap.size()) { + ActivityManager.getService().unregisterUidObserver(mEffectUidObserver); + mClientUidSessionMap.clear(); + context.unbindService(mMusicFxBindConnection); + Log.i(TAG, " remove all sessions, unregister UID observer, and unbind " + + musicFxPackageName); + } + } else { + // if the audio session already closed, print an error + Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession + + " which does not exist"); + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Not able to find UID from package: " + e); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException " + e + " with handling intent"); + } + } + + private int getCurrentUserId() { + final long ident = Binder.clearCallingIdentity(); + try { + UserInfo currentUser = ActivityManager.getService().getCurrentUser(); + return currentUser.id; + } catch (RemoteException e) { + // Activity manager not running, nothing we can do assume user 0. + } finally { + Binder.restoreCallingIdentity(ident); + } + return UserHandle.USER_SYSTEM; + } + + /*package*/ void handleMessage(Message msg) { + switch (msg.what) { + case MSG_EFFECT_CLIENT_GONE: + Log.w(TAG, " handle MSG_EFFECT_CLIENT_GONE"); + handleEffectClientUidGone(msg.arg1 /* uid */); + break; + default: + Log.e(TAG, "Unexpected msg to handle in MusicFxHelper: " + msg.what); + break; + } + } + +} diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 81365bfbf2a6..d65c7c2c526d 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -108,11 +108,11 @@ public class SoundDoseHelper { private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed - private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1; - private static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2; - private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3; - private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4; - private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5; + /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1; + /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2; + /*package*/ static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3; + /*package*/ static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4; + /*package*/ static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5; /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 6; private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours @@ -189,6 +189,8 @@ public class SoundDoseHelper { private final AtomicBoolean mEnableCsd = new AtomicBoolean(false); + private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false); + private final Object mCsdAsAFeatureLock = new Object(); @GuardedBy("mCsdAsAFeatureLock") @@ -375,9 +377,21 @@ public class SoundDoseHelper { } } + private boolean updateCsdForTestApi() { + if (mForceCsdProperty.get() != SystemProperties.getBoolean( + SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) { + updateCsdEnabled("SystemPropertiesChangeCallback"); + } + + return mEnableCsd.get(); + } + float getCsd() { if (!mEnableCsd.get()) { - return -1.f; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return -1.f; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -396,7 +410,10 @@ public class SoundDoseHelper { void setCsd(float csd) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } SoundDoseRecord[] doseRecordsArray; @@ -430,7 +447,10 @@ public class SoundDoseHelper { void resetCsdTimeouts() { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } synchronized (mCsdStateLock) { @@ -440,7 +460,10 @@ public class SoundDoseHelper { void forceUseFrameworkMel(boolean useFrameworkMel) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -458,7 +481,10 @@ public class SoundDoseHelper { void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -488,7 +514,7 @@ public class SoundDoseHelper { try { return soundDose.isSoundDoseHalSupported(); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing CSD computation on all devices", e); + Log.e(TAG, "Exception while querying the csd enabled status", e); } return false; } @@ -544,7 +570,7 @@ public class SoundDoseHelper { audioDeviceCategory.csdCompatible = isHeadphone; soundDose.setAudioDeviceCategory(audioDeviceCategory); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while setting the audio device category", e); } } @@ -894,7 +920,7 @@ public class SoundDoseHelper { mCachedAudioDeviceCategories.clear(); } } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while initializing the cached audio device categories", e); } synchronized (mCsdAsAFeatureLock) { @@ -991,19 +1017,20 @@ public class SoundDoseHelper { } private void updateCsdEnabled(String caller) { - boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false); + mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, + false)); // we are using the MCC overlaid legacy flag used for the safe volume enablement // to determine whether the MCC enforces any safe hearing standard. boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean( com.android.internal.R.bool.config_safe_media_volume_enabled); boolean csdEnable = mContext.getResources().getBoolean( R.bool.config_safe_sound_dosage_enabled); - boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce; + boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get(); synchronized (mCsdAsAFeatureLock) { if (!mccEnforcedSafeMedia && csdEnable) { mIsCsdAsAFeatureAvailable = true; - newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce; + newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get(); Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: " + newEnabledCsd); } else { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 4538cad513d6..1760bb3c7a6a 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; @@ -357,6 +358,18 @@ public class AuthService extends SystemService { } @Override + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener listener) throws RemoteException { + checkInternalPermission(); + final long identity = Binder.clearCallingIdentity(); + try { + mBiometricService.registerBiometricPromptStatusListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) throws RemoteException { checkInternalPermission(); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 1898b8015462..9569f23e8d49 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -88,6 +89,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; @@ -105,6 +107,8 @@ public class BiometricService extends SystemService { @VisibleForTesting final SettingObserver mSettingObserver; private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; + private final ConcurrentLinkedQueue<BiometricPromptStatusListener> + mBiometricPromptStatusListeners; private final Random mRandom = new Random(); @NonNull private final Supplier<Long> mRequestCounter; @NonNull private final BiometricContext mBiometricContext; @@ -425,6 +429,42 @@ public class BiometricService extends SystemService { } } + final class BiometricPromptStatusListener implements IBinder.DeathRecipient { + private final IBiometricPromptStatusListener mBiometricPromptStatusListener; + + BiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + mBiometricPromptStatusListener = callback; + } + + void notifyBiometricPromptShowing() { + try { + mBiometricPromptStatusListener.onBiometricPromptShowing(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyHandleAuthenticate", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyHandleAuthenticate", e); + } + } + + void notifyBiometricPromptIdle() { + try { + mBiometricPromptStatusListener.onBiometricPromptIdle(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyDialogDismissed", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyDialogDismissed", e); + } + } + + @Override + public void binderDied() { + Slog.e(TAG, "Biometric prompt callback binder died"); + mBiometricPromptStatusListeners.remove(this); + } + } + // Receives events from individual biometric sensors. private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) { return new IBiometricSensorReceiver.Stub() { @@ -705,6 +745,22 @@ public class BiometricService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call + public void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + super.registerBiometricPromptStatusListener_enforcePermission(); + + BiometricPromptStatusListener biometricPromptStatusListener = + new BiometricPromptStatusListener(callback); + mBiometricPromptStatusListeners.add(biometricPromptStatusListener); + + if (mAuthSession != null) { + biometricPromptStatusListener.notifyBiometricPromptShowing(); + } else { + biometricPromptStatusListener.notifyBiometricPromptIdle(); + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override // Binder call public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) { @@ -1044,6 +1100,7 @@ public class BiometricService extends SystemService { mDevicePolicyManager = mInjector.getDevicePolicyManager(context); mImpl = new BiometricServiceWrapper(); mEnabledOnKeyguardCallbacks = new ArrayList<>(); + mBiometricPromptStatusListeners = new ConcurrentLinkedQueue<>(); mSettingObserver = mInjector.getSettingObserver(context, mHandler, mEnabledOnKeyguardCallbacks); mRequestCounter = mInjector.getRequestGenerator(); @@ -1158,6 +1215,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleOnError: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); @@ -1186,6 +1244,7 @@ public class BiometricService extends SystemService { session.onDialogDismissed(reason, credentialAttestation); mAuthSession = null; + notifyAuthSessionChanged(); } private void handleOnTryAgainPressed(long requestId) { @@ -1235,6 +1294,7 @@ public class BiometricService extends SystemService { final boolean finished = session.onClientDied(); if (finished) { mAuthSession = null; + notifyAuthSessionChanged(); } } @@ -1349,6 +1409,16 @@ public class BiometricService extends SystemService { }); } + private void notifyAuthSessionChanged() { + for (BiometricPromptStatusListener listener : mBiometricPromptStatusListeners) { + if (mAuthSession == null) { + listener.notifyBiometricPromptIdle(); + } else { + listener.notifyBiometricPromptShowing(); + } + } + } + /** * handleAuthenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be @@ -1386,6 +1456,7 @@ public class BiometricService extends SystemService { } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } + notifyAuthSessionChanged(); } private void handleCancelAuthentication(long requestId) { @@ -1400,6 +1471,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleCancelAuthentication: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index b537e0eae548..b12d831ffe24 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -6,3 +6,10 @@ flag { description: "This flag controls tunscany virtual HAL feature" bug: "294254230" } + +flag { + name: "de_hidl" + namespace: "biometrics_framework" + description: "feature flag for biometrics de-hidl" + bug: "287332354" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 78c95ad4576b..a47135fd032f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -35,6 +35,7 @@ import android.util.EventLog; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -116,7 +117,24 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> @LockoutTracker.LockoutMode public int handleFailedAttempt(int userId) { - return LockoutTracker.LOCKOUT_NONE; + if (Flags.deHidl()) { + if (mLockoutTracker != null) { + mLockoutTracker.addFailedAttemptForUser(getTargetUserId()); + } + @LockoutTracker.LockoutMode final int lockoutMode = + getLockoutTracker().getLockoutModeForUser(userId); + final PerformanceTracker performanceTracker = + PerformanceTracker.getInstanceForSensorId(getSensorId()); + if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) { + performanceTracker.incrementPermanentLockoutForUser(userId); + } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) { + performanceTracker.incrementTimedLockoutForUser(userId); + } + + return lockoutMode; + } else { + return LockoutTracker.LOCKOUT_NONE; + } } protected long getStartTimeMs() { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 8a54ae5a6fea..037ea38a2d17 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -586,4 +586,13 @@ public class BiometricScheduler { } }, 10000); } + + /** + * Handle stop user client when user switching occurs. + */ + public void onUserStopped() {} + + public Handler getHandler() { + return mHandler; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java index 95c49032c029..35e9bcbbf085 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java @@ -32,6 +32,7 @@ public class LockoutCache implements LockoutTracker { mUserLockoutStates = new SparseIntArray(); } + @Override public void setLockoutModeForUser(int userId, @LockoutMode int mode) { Slog.d(TAG, "Lockout for user: " + userId + " is " + mode); synchronized (this) { diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java index 4a59c9df9bef..8271380010e2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java @@ -35,4 +35,7 @@ public interface LockoutTracker { @interface LockoutMode {} @LockoutMode int getLockoutModeForUser(int userId); + void setLockoutModeForUser(int userId, @LockoutMode int mode); + default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {} + default void addFailedAttemptForUser(int userId) {} } diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index 694dfd28d0cc..80754702415a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -165,6 +165,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } } + @Override public void onUserStopped() { if (mStopUserClient == null) { Slog.e(getTag(), "Unexpected onUserStopped"); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java index cc0022703745..e5d4a635876d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java @@ -31,6 +31,11 @@ public class LockoutHalImpl implements LockoutTracker { return mCurrentUserLockoutMode; } + @Override + public void setLockoutModeForUser(int userId, @LockoutMode int mode) { + setCurrentUserLockoutMode(mode); + } + public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) { mCurrentUserLockoutMode = lockoutMode; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java index 573c20fe041b..d149f5215a7a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java @@ -38,7 +38,7 @@ import android.util.Slog; /** * Utilities for converting from hardware to framework-defined AIDL models. */ -final class AidlConversionUtils { +public final class AidlConversionUtils { private static final String TAG = "AidlConversionUtils"; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..57f5b34c197a --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java @@ -0,0 +1,319 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.EnrollmentFrame; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.face.ISessionCallback; +import android.hardware.face.Face; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.face.FaceUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { + /** + * Interface to send results to the AidlResponseHandler's owner. + */ + public interface HardwareUnavailableCallback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } + + private static final String TAG = "AidlResponseHandler"; + + @NonNull + private final Context mContext; + @NonNull + private final BiometricScheduler mScheduler; + private final int mSensorId; + private final int mUserId; + @NonNull + private final LockoutTracker mLockoutCache; + @NonNull + private final LockoutResetDispatcher mLockoutResetDispatcher; + + @NonNull + private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final HardwareUnavailableCallback mHardwareUnavailableCallback; + + public AidlResponseHandler(@NonNull Context context, + @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull LockoutTracker lockoutTracker, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull AuthSessionCoordinator authSessionCoordinator, + @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { + mContext = context; + mScheduler = scheduler; + mSensorId = sensorId; + mUserId = userId; + mLockoutCache = lockoutTracker; + mLockoutResetDispatcher = lockoutResetDispatcher; + mAuthSessionCoordinator = authSessionCoordinator; + mHardwareUnavailableCallback = hardwareUnavailableCallback; + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + + @Override + public void onChallengeGenerated(long challenge) { + handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId, + mUserId, challenge), null); + } + + @Override + public void onChallengeRevoked(long challenge) { + handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId, + mUserId, challenge), null); + } + + @Override + public void onAuthenticationFrame(AuthenticationFrame frame) { + handleResponse(FaceAuthenticationClient.class, (c) -> { + if (frame == null) { + Slog.e(TAG, "Received null enrollment frame for face authentication client."); + return; + } + c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); + }, null); + } + + @Override + public void onEnrollmentFrame(EnrollmentFrame frame) { + handleResponse(FaceEnrollClient.class, (c) -> { + if (frame == null) { + Slog.e(TAG, "Received null enrollment frame for face enroll client."); + return; + } + c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); + }, null); + } + + @Override + public void onError(byte error, int vendorCode) { + onError(AidlConversionUtils.toFrameworkError(error), vendorCode); + } + + /** + * Handle error messages from the HAL. + */ + public void onError(int error, int vendorCode) { + handleResponse(ErrorConsumer.class, (c) -> { + c.onError(error, vendorCode); + if (error == Error.HW_UNAVAILABLE) { + mHardwareUnavailableCallback.onHardwareUnavailable(); + } + }, null); + } + + @Override + public void onEnrollmentProgress(int enrollmentId, int remaining) { + BaseClientMonitor client = mScheduler.getCurrentClient(); + final int currentUserId; + if (client == null) { + return; + } else { + currentUserId = client.getTargetUserId(); + } + final CharSequence name = FaceUtils.getInstance(mSensorId) + .getUniqueName(mContext, currentUserId); + final Face face = new Face(name, enrollmentId, mSensorId); + + handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null); + } + + @Override + public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { + final Face face = new Face("" /* name */, enrollmentId, mSensorId); + final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); + final ArrayList<Byte> byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, + true /* authenticated */, byteList), null); + } + + @Override + public void onAuthenticationFailed() { + final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, + false /* authenticated */, null /* hat */), null); + } + + @Override + public void onLockoutTimed(long durationMillis) { + handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null); + } + + @Override + public void onLockoutPermanent() { + handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); + } + + @Override + public void onLockoutCleared() { + handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared, + (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + Utils.getCurrentStrength(mSensorId), -1 /* requestId */)); + } + + @Override + public void onInteractionDetected() { + handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null); + } + + @Override + public void onEnrollmentsEnumerated(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; ++i) { + final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); + final int finalI = i; + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult( + null /* identifier */, 0 /* remaining */), null); + } + } + + @Override + public void onFeaturesRetrieved(byte[] features) { + handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */, + features), null); + } + + @Override + public void onFeatureSet(byte feature) { + handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null); + } + + @Override + public void onEnrollmentsRemoved(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); + final int finalI = i; + handleResponse(RemovalConsumer.class, + (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1), + null); + } + } else { + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */, + 0 /* remaining */), null); + } + } + + @Override + public void onAuthenticatorIdRetrieved(long authenticatorId) { + handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved( + authenticatorId), null); + } + + @Override + public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { + handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( + newAuthenticatorId), null); + } + + /** + * Handles acquired messages sent by the HAL (specifically for HIDL HAL). + */ + public void onAcquired(int acquiredInfo, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), + null); + } + + /** + * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL). + */ + public void onLockoutChanged(long duration) { + mScheduler.getHandler().post(() -> { + @LockoutTracker.LockoutMode final int lockoutMode; + if (duration == 0) { + lockoutMode = LockoutTracker.LOCKOUT_NONE; + } else if (duration == -1 || duration == Long.MAX_VALUE) { + lockoutMode = LockoutTracker.LOCKOUT_PERMANENT; + } else { + lockoutMode = LockoutTracker.LOCKOUT_TIMED; + } + + mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode); + + if (duration == 0) { + mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId); + } + }); + } + + private <T> void handleResponse(@NonNull Class<T> className, + @NonNull Consumer<T> actionIfClassMatchesClient, + @Nullable Consumer<BaseClientMonitor> alternateAction) { + mScheduler.getHandler().post(() -> { + final BaseClientMonitor client = mScheduler.getCurrentClient(); + if (className.isInstance(client)) { + actionIfClassMatchesClient.accept((T) client); + } else { + Slog.d(TAG, "Current client is not an instance of " + className.getName()); + if (alternateAction != null) { + alternateAction.accept(client); + } + } + }); + } + + @Override + public void onSessionClosed() { + mScheduler.getHandler().post(mScheduler::onUserStopped); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java index 29eee6b5bb06..858bb864d4db 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java @@ -16,10 +16,14 @@ package com.android.server.biometrics.sensors.face.aidl; -import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback; - import android.annotation.NonNull; +import android.content.Context; import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; + +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier; /** * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -31,14 +35,22 @@ public class AidlSession { @NonNull private final ISession mSession; private final int mUserId; - @NonNull private final HalSessionCallback mHalSessionCallback; + @NonNull private final AidlResponseHandler mAidlResponseHandler; public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, - HalSessionCallback halSessionCallback) { + AidlResponseHandler aidlResponseHandler) { mHalInterfaceVersion = halInterfaceVersion; mSession = session; mUserId = userId; - mHalSessionCallback = halSessionCallback; + mAidlResponseHandler = aidlResponseHandler; + } + + public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId, + AidlResponseHandler aidlResponseHandler) { + mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler); + mHalInterfaceVersion = 0; + mUserId = userId; + mAidlResponseHandler = aidlResponseHandler; } /** The underlying {@link ISession}. */ @@ -52,8 +64,8 @@ public class AidlSession { } /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ - HalSessionCallback getHalSessionCallback() { - return mHalSessionCallback; + AidlResponseHandler getHalSessionCallback() { + return mAidlResponseHandler; } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 35fc43ae5f74..470dc4b7172c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -46,8 +46,8 @@ import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.face.UsageStats; @@ -57,7 +57,8 @@ import java.util.function.Supplier; /** * Face-specific authentication client for the {@link IFace} AIDL HAL interface. */ -class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> +public class FaceAuthenticationClient + extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> implements LockoutConsumer { private static final String TAG = "FaceAuthenticationClient"; @@ -74,11 +75,11 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut @Nullable private ICancellationSignal mCancellationSignal; @Nullable - private SensorPrivacyManager mSensorPrivacyManager; + private final SensorPrivacyManager mSensorPrivacyManager; @FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN; - FaceAuthenticationClient(@NonNull Context context, + public FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, long operationId, @@ -86,7 +87,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, - @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, + @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication, @Authenticators.Types int sensorStrength) { this(context, lazyDaemon, token, requestId, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, @@ -103,12 +104,12 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, - @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, + @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication, SensorPrivacyManager sensorPrivacyManager, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, token, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, - isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */, + isStrongBiometric, null /* taskStackListener */, lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, biometricStrength); setRequestId(requestId); @@ -263,8 +264,13 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut mLastAcquire = acquireInfo; final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode); onAcquiredInternal(acquireInfo, vendorCode, shouldSend); - PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); - pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); + + //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL + if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser( + getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) { + PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); + pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); + } } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index f55cf0549382..dbed1f7a8f9d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -85,7 +85,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { } }; - FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java index 165c3a241043..e404bd2be31e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java @@ -36,7 +36,7 @@ import java.util.function.Supplier; public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { private static final String TAG = "FaceGenerateChallengeClient"; - FaceGenerateChallengeClient(@NonNull Context context, + public FaceGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java index ef3b345402bf..c15049b48bb2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession; import android.os.IBinder; import android.os.RemoteException; import android.provider.Settings; @@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter; import java.util.HashMap; import java.util.Map; @@ -46,14 +48,16 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen private static final String TAG = "FaceGetFeatureClient"; private final int mUserId; + private final int mFeature; - FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, - @NonNull BiometricContext biometricContext) { + @NonNull BiometricContext biometricContext, int feature) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, logger, biometricContext); mUserId = userId; + mFeature = feature; } @Override @@ -70,7 +74,11 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen @Override protected void startHalOperation() { try { - getFreshDaemon().getSession().getFeatures(); + ISession session = getFreshDaemon().getSession(); + if (session instanceof AidlToHidlAdapter) { + ((AidlToHidlAdapter) session).setFeature(mFeature); + } + session.getFeatures(); } catch (RemoteException e) { Slog.e(TAG, "Unable to getFeature", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index f09d192966f1..e75c6aba1489 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -38,9 +38,9 @@ import java.util.function.Supplier; /** * Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface. */ -class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { +public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { - FaceInternalCleanupClient(@NonNull Context context, + public FaceInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index cc3118cc3433..dd9c6d50ae9e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -493,7 +493,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, - mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(), + mUsageStats, null /* lockoutTracker */, allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId)); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override @@ -619,7 +619,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mFaceSensors.get(sensorId).getLazySession(), token, callback, userId, mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext), - mBiometricContext); + mBiometricContext, feature); scheduleForSensor(sensorId, client); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java index 0512017394af..079388822def 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java @@ -36,12 +36,12 @@ import java.util.function.Supplier; /** * Face-specific removal client for the {@link IFace} AIDL HAL interface. */ -class FaceRemovalClient extends RemovalClient<Face, AidlSession> { +public class FaceRemovalClient extends RemovalClient<Face, AidlSession> { private static final String TAG = "FaceRemovalClient"; final int[] mBiometricIds; - FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 1a12fcdf5010..77b5592c5064 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -48,14 +47,14 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem private static final String TAG = "FaceResetLockoutClient"; private final HardwareAuthToken mHardwareAuthToken; - private final LockoutCache mLockoutCache; + private final LockoutTracker mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; private final int mBiometricStrength; - FaceResetLockoutClient(@NonNull Context context, + public FaceResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, + @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,7 +106,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem * be used instead. */ static void resetLocalLockoutStateToNone(int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, + @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, @Authenticators.Types int biometricStrength, long requestId) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java index 8838345de4d6..0d6143a7d0f0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java @@ -38,7 +38,7 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession private final long mChallenge; - FaceRevokeChallengeClient(@NonNull Context context, + public FaceRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java index 6c143872ff8c..f6da8726564f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java @@ -46,7 +46,7 @@ public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implemen private final boolean mEnabled; private final HardwareAuthToken mHardwareAuthToken; - FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 2ad41c2a7a02..54e66eb4cca4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -23,16 +23,10 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.face.AuthenticationFrame; -import android.hardware.biometrics.face.EnrollmentFrame; -import android.hardware.biometrics.face.Error; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; -import android.hardware.biometrics.face.ISessionCallback; -import android.hardware.face.Face; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -44,29 +38,22 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; -import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.face.FaceUtils; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -91,397 +78,6 @@ public class Sensor { @NonNull private final Supplier<AidlSession> mLazySession; @Nullable AidlSession mCurrentSession; - @VisibleForTesting - public static class HalSessionCallback extends ISessionCallback.Stub { - /** - * Interface to sends results to the HalSessionCallback's owner. - */ - public interface Callback { - /** - * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. - */ - void onHardwareUnavailable(); - } - - @NonNull - private final Context mContext; - @NonNull - private final Handler mHandler; - @NonNull - private final String mTag; - @NonNull - private final UserAwareBiometricScheduler mScheduler; - private final int mSensorId; - private final int mUserId; - @NonNull - private final LockoutCache mLockoutCache; - @NonNull - private final LockoutResetDispatcher mLockoutResetDispatcher; - - @NonNull - private AuthSessionCoordinator mAuthSessionCoordinator; - @NonNull - private final Callback mCallback; - - HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull Callback callback) { - mContext = context; - mHandler = handler; - mTag = tag; - mScheduler = scheduler; - mSensorId = sensorId; - mUserId = userId; - mLockoutCache = lockoutTracker; - mLockoutResetDispatcher = lockoutResetDispatcher; - mAuthSessionCoordinator = authSessionCoordinator; - mCallback = callback; - } - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() { - return this.HASH; - } - - @Override - public void onChallengeGenerated(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGenerateChallengeClient)) { - Slog.e(mTag, "onChallengeGenerated for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceGenerateChallengeClient generateChallengeClient = - (FaceGenerateChallengeClient) client; - generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onChallengeRevoked(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceRevokeChallengeClient)) { - Slog.e(mTag, "onChallengeRevoked for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceRevokeChallengeClient revokeChallengeClient = - (FaceRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onAuthenticationFrame(AuthenticationFrame frame) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceAuthenticationClient)) { - Slog.e(mTag, "onAuthenticationFrame for incompatible client: " - + Utils.getClientName(client)); - return; - - } - if (frame == null) { - Slog.e(mTag, "Received null authentication frame for client: " - + Utils.getClientName(client)); - return; - } - ((FaceAuthenticationClient) client).onAuthenticationFrame( - AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); - }); - } - - @Override - public void onEnrollmentFrame(EnrollmentFrame frame) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceEnrollClient)) { - Slog.e(mTag, "onEnrollmentFrame for incompatible client: " - + Utils.getClientName(client)); - return; - } - if (frame == null) { - Slog.e(mTag, "Received null enrollment frame for client: " - + Utils.getClientName(client)); - return; - } - ((FaceEnrollClient) client).onEnrollmentFrame( - AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); - }); - } - - @Override - public void onError(byte error, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - Slog.d(mTag, "onError" - + ", client: " + Utils.getClientName(client) - + ", error: " + error - + ", vendorCode: " + vendorCode); - if (!(client instanceof ErrorConsumer)) { - Slog.e(mTag, "onError for non-error consumer: " - + Utils.getClientName(client)); - return; - } - - final ErrorConsumer errorConsumer = (ErrorConsumer) client; - errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - - if (error == Error.HW_UNAVAILABLE) { - mCallback.onHardwareUnavailable(); - } - }); - } - - @Override - public void onEnrollmentProgress(int enrollmentId, int remaining) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceEnrollClient)) { - Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " - + Utils.getClientName(client)); - return; - } - - final int currentUserId = client.getTargetUserId(); - final CharSequence name = FaceUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); - final Face face = new Face(name, enrollmentId, mSensorId); - - final FaceEnrollClient enrollClient = (FaceEnrollClient) client; - enrollClient.onEnrollResult(face, remaining); - }); - } - - @Override - public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Face face = new Face("" /* name */, enrollmentId, mSensorId); - final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); - final ArrayList<Byte> byteList = new ArrayList<>(); - for (byte b : byteArray) { - byteList.add(b); - } - authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList); - }); - } - - @Override - public void onAuthenticationFailed() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); - authenticationConsumer.onAuthenticated(face, false /* authenticated */, - null /* hat */); - }); - } - - @Override - public void onLockoutTimed(long durationMillis) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } - - @Override - public void onLockoutPermanent() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutPermanent(); - }); - } - - @Override - public void onLockoutCleared() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceResetLockoutClient)) { - Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); - // Given that onLockoutCleared() can happen at any time, and is not necessarily - // coming from a specific client, set this to -1 to indicate it wasn't for a - // specific request. - FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, - mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - Utils.getCurrentStrength(mSensorId), -1 /* requestId */); - } else { - Slog.d(mTag, "onLockoutCleared after resetLockout"); - final FaceResetLockoutClient resetLockoutClient = - (FaceResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - } - }); - } - - @Override - public void onInteractionDetected() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceDetectClient)) { - Slog.e(mTag, "onInteractionDetected for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FaceDetectClient detectClient = (FaceDetectClient) client; - detectClient.onInteractionDetected(); - }); - } - - @Override - public void onEnrollmentsEnumerated(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof EnumerateConsumer)) { - Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " - + Utils.getClientName(client)); - return; - } - - final EnumerateConsumer enumerateConsumer = - (EnumerateConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; ++i) { - final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); - enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1); - } - } else { - enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */); - } - }); - } - - @Override - public void onFeaturesRetrieved(byte[] features) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGetFeatureClient)) { - Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: " - + Utils.getClientName(client)); - return; - } - final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client; - faceGetFeatureClient.onFeatureGet(true /* success */, features); - }); - - } - - @Override - public void onFeatureSet(byte feature) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceSetFeatureClient)) { - Slog.e(mTag, "onFeatureSet for non-set consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client; - faceSetFeatureClient.onFeatureSet(true /* success */); - }); - } - - @Override - public void onEnrollmentsRemoved(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof RemovalConsumer)) { - Slog.e(mTag, "onRemoved for non-removal consumer: " - + Utils.getClientName(client)); - return; - } - - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); - removalConsumer.onRemoved(face, enrollmentIds.length - i - 1); - } - } else { - removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */); - } - }); - } - - @Override - public void onAuthenticatorIdRetrieved(long authenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceGetAuthenticatorIdClient)) { - Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceGetAuthenticatorIdClient getAuthenticatorIdClient = - (FaceGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } - - @Override - public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FaceInvalidationClient)) { - Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client; - invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); - }); - } - - @Override - public void onSessionClosed() { - mHandler.post(mScheduler::onUserStopped); - } - } Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, @@ -511,9 +107,9 @@ public class Sensor { public StartUserClient<?, ?> getStartUserClient(int newUserId) { final int sensorId = mSensorProperties.sensorId; - final HalSessionCallback resultController = new HalSessionCallback(mContext, - mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, - lockoutResetDispatcher, + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutCache, lockoutResetDispatcher, biometricContext.getAuthSessionCoordinator(), () -> { Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..eecf44b92918 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java @@ -0,0 +1,347 @@ +/* + * 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.biometrics.sensors.face.hidl; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.face.EnrollmentStageConfig; +import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.common.NativeHandle; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { + + private final String TAG = "AidlToHidlAdapter"; + private static final int CHALLENGE_TIMEOUT_SEC = 600; + @DurationMillisLong + private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000; + @DurationMillisLong + private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000; + private static final int INVALID_VALUE = -1; + private final Clock mClock; + private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); + @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75; + private long mGenerateChallengeCreatedAt = INVALID_VALUE; + private long mGenerateChallengeResult = INVALID_VALUE; + @NonNull private Supplier<IBiometricsFace> mSession; + private final int mUserId; + private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; + private final Context mContext; + private int mFeature = INVALID_VALUE; + + public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, + int userId, AidlResponseHandler aidlResponseHandler) { + this(context, session, userId, aidlResponseHandler, Clock.systemUTC()); + } + + AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId, + AidlResponseHandler aidlResponseHandler, Clock clock) { + mSession = session; + mUserId = userId; + mContext = context; + mClock = clock; + setCallback(aidlResponseHandler); + } + + private void setCallback(AidlResponseHandler aidlResponseHandler) { + mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); + try { + mSession.get().setCallback(mHidlToAidlCallbackConverter); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to set callback"); + } + } + + @Override + public IBinder asBinder() { + return null; + } + + private boolean isGeneratedChallengeCacheValid() { + return mGenerateChallengeCreatedAt != INVALID_VALUE + && mGenerateChallengeResult != INVALID_VALUE + && mClock.millis() - mGenerateChallengeCreatedAt + < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS; + } + + private void incrementChallengeCount() { + mGeneratedChallengeCount.add(0, mClock.millis()); + } + + private int decrementChallengeCount() { + final long now = mClock.millis(); + // ignore values that are old in case generate/revoke calls are not matched + // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing + mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS); + if (!mGeneratedChallengeCount.isEmpty()) { + mGeneratedChallengeCount.remove(0); + } + return mGeneratedChallengeCount.size(); + } + + @Override + public void generateChallenge() throws RemoteException { + incrementChallengeCount(); + if (isGeneratedChallengeCacheValid()) { + Slog.d(TAG, "Current challenge is cached and will be reused"); + mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); + return; + } + mGenerateChallengeCreatedAt = mClock.millis(); + mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value; + mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); + } + + @Override + public void revokeChallenge(long challenge) throws RemoteException { + final boolean shouldRevoke = decrementChallengeCount() == 0; + if (!shouldRevoke) { + Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " + + mGeneratedChallengeCount); + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); + return; + } + mGenerateChallengeCreatedAt = INVALID_VALUE; + mGenerateChallengeResult = INVALID_VALUE; + mSession.get().revokeChallenge(); + mHidlToAidlCallbackConverter.onChallengeRevoked(0L); + } + + @Override + public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException { + //unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features, + NativeHandle previewSurface) throws RemoteException { + final ArrayList<Byte> token = new ArrayList<>(); + final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat); + for (byte b : hardwareAuthTokenArray) { + token.add(b); + } + final ArrayList<Integer> disabledFeatures = new ArrayList<>(); + for (byte b: features) { + disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b)); + } + mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures); + return new Cancellation(); + } + + @Override + public ICancellationSignal authenticate(long operationId) throws RemoteException { + mSession.get().authenticate(operationId); + return new Cancellation(); + } + + @Override + public ICancellationSignal detectInteraction() throws RemoteException { + mSession.get().authenticate(0); + return new Cancellation(); + } + + @Override + public void enumerateEnrollments() throws RemoteException { + mSession.get().enumerate(); + } + + @Override + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + mSession.get().remove(enrollmentIds[0]); + } + + /** + * Needs to be called before getFeatures is invoked. + */ + public void setFeature(int feature) { + mFeature = feature; + } + + @Override + public void getFeatures() throws RemoteException { + final int faceId = getFaceId(); + if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) { + return; + } + + final OptionalBool result = mSession.get() + .getFeature(mFeature, faceId); + + if (result.status == Status.OK && result.value) { + mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils + .convertFrameworkToAidlFeature(mFeature)}); + } else if (result.status == Status.OK) { + mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{}); + } else { + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); + } + + mFeature = INVALID_VALUE; + } + + @Override + public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled) + throws RemoteException { + final int faceId = getFaceId(); + if (faceId == INVALID_VALUE) { + return; + } + ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>(); + for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthTokenList.add(b); + } + final int result = mSession.get().setFeature( + AidlConversionUtils.convertAidlToFrameworkFeature(feature), + enabled, hardwareAuthTokenList, faceId); + if (result == Status.OK) { + mHidlToAidlCallbackConverter.onFeatureSet(feature); + } else { + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); + } + } + + private int getFaceId() { + FaceManager faceManager = mContext.getSystemService(FaceManager.class); + List<Face> faces = faceManager.getEnrolledFaces(mUserId); + if (faces.isEmpty()) { + Slog.d(TAG, "No faces to get feature from."); + mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, + BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */); + return INVALID_VALUE; + } + + return faces.get(0).getBiometricId(); + } + + @Override + public void getAuthenticatorId() throws RemoteException { + long authenticatorId = mSession.get().getAuthenticatorId().value; + mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId); + } + + @Override + public void invalidateAuthenticatorId() throws RemoteException { + //unsupported in HIDL + } + + @Override + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); + for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthToken.add(b); + } + mSession.get().resetLockout(hardwareAuthToken); + } + + @Override + public void close() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features, + NativeHandle previewSurface, OperationContext context) throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public void onContextChanged(OperationContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public int getInterfaceVersion() throws RemoteException { + //Unsupported in HIDL + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + //Unsupported in HIDL + return null; + } + + /** + * Cancellation in HIDL occurs for the entire session, instead of a specific client. + */ + private class Cancellation extends ICancellationSignal.Stub { + + Cancellation() {} + @Override + public void cancel() throws RemoteException { + try { + mSession.get().cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting cancel", e); + } + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 1499317478aa..46ce0b62e6d5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; @@ -61,6 +62,7 @@ import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; @@ -78,6 +80,8 @@ import com.android.server.biometrics.sensors.face.FaceUtils; import com.android.server.biometrics.sensors.face.LockoutHalImpl; import com.android.server.biometrics.sensors.face.ServiceProvider; import com.android.server.biometrics.sensors.face.UsageStats; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.face.aidl.AidlSession; import org.json.JSONArray; import org.json.JSONException; @@ -131,7 +135,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private int mCurrentUserId = UserHandle.USER_NULL; private final int mSensorId; private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); + private final LockoutResetDispatcher mLockoutResetDispatcher; private FaceGenerateChallengeClient mGeneratedChallengeCache = null; + private AidlSession mSession; private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override @@ -361,6 +367,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mLockoutTracker = new LockoutHalImpl(); mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler, mScheduler, mLockoutTracker, lockoutResetDispatcher); + mLockoutResetDispatcher = lockoutResetDispatcher; mHalResultController.setCallback(() -> { mDaemon = null; mCurrentUserId = UserHandle.USER_NULL; @@ -420,6 +427,24 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { }); } + public int getCurrentUserId() { + return mCurrentUserId; + } + + synchronized AidlSession getSession() { + if (mDaemon != null && mSession != null) { + return mSession; + } else { + return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId, + new AidlResponseHandler(mContext, mScheduler, mSensorId, + mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher, + new AuthSessionCoordinator(), () -> { + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + })); + } + } + private synchronized IBiometricsFace getDaemon() { if (mTestHalEnabled) { final TestHal testHal = new TestHal(mContext, mSensorId); @@ -551,32 +576,63 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - incrementChallengeCount(); + scheduleUpdateActiveUserWithoutHandler(userId); - if (isGeneratedChallengeCacheValid()) { - Slog.d(TAG, "Current challenge is cached and will be reused"); - mGeneratedChallengeCache.reuseResult(receiver); - return; + if (Flags.deHidl()) { + scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); + } else { + scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleGenerateChallenge onClientStarted, mismatched client." + + " Expecting: " + client + ", received: " + + clientMonitor); + } + } + }); + } - final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, sSystemClock.millis()); - mGeneratedChallengeCache = client; - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - if (client != clientMonitor) { - Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client." - + " Expecting: " + client + ", received: " + clientMonitor); - } + private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + incrementChallengeCount(); + if (isGeneratedChallengeCacheValid()) { + Slog.d(TAG, "Current challenge is cached and will be reused"); + mGeneratedChallengeCache.reuseResult(receiver); + return; + } + + final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, + opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, sSystemClock.millis()); + mGeneratedChallengeCache = client; + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleGenerateChallenge onClientStarted, mismatched client." + + " Expecting: " + client + ", received: " + + clientMonitor); } - }); + } }); } @@ -584,31 +640,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final boolean shouldRevoke = decrementChallengeCount() == 0; - if (!shouldRevoke) { - Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " - + mGeneratedChallengeCount); - return; + if (Flags.deHidl()) { + scheduleRevokeChallengeAidl(userId, token, opPackageName); + } else { + scheduleRevokeChallengeHidl(userId, token, opPackageName); + } + }); + } + + private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient( + mContext, this::getSession, token, userId, opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, 0L); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleRevokeChallenge, mismatched client." + "Expecting: " + + client + ", received: " + clientMonitor); + } } + }); + } - Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); - mGeneratedChallengeCache = null; + private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final boolean shouldRevoke = decrementChallengeCount() == 0; + if (!shouldRevoke) { + Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " + + mGeneratedChallengeCount); + return; + } - final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, - mLazyDaemon, token, userId, opPackageName, mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - if (client != clientMonitor) { - Slog.e(TAG, "scheduleRevokeChallenge, mismatched client." - + "Expecting: " + client + ", received: " + clientMonitor); - } + Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); + mGeneratedChallengeCache = null; + final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, + mLazyDaemon, token, userId, opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (client != clientMonitor) { + Slog.e(TAG, + "scheduleRevokeChallenge, mismatched client." + "Expecting: " + + client + ", received: " + clientMonitor); } - }); + } }); } @@ -620,7 +707,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); + if (Flags.deHidl()) { + scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, + opPackageName, disabledFeatures, previewSurface, id); + } else { + scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, + opPackageName, disabledFeatures, previewSurface, id); + } + }); + return id; + } + + private void scheduleEnrollAidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName, @NonNull int[] disabledFeatures, + @Nullable Surface previewSurface, long id) { + final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, + hardwareAuthToken, opPackageName, id, + FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, + ENROLL_TIMEOUT_SEC, previewSurface, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, + mContext.getResources().getInteger( + com.android.internal.R.integer.config_faceMaxTemplatesPerUser), + false); + + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId()); + } + } + }); + } + + private void scheduleEnrollHidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, + @NonNull String opPackageName, @NonNull int[] disabledFeatures, + @Nullable Surface previewSurface, long id) { final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, @@ -628,7 +770,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -650,8 +791,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } } }); - }); - return id; } @Override @@ -683,18 +822,46 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { scheduleUpdateActiveUserWithoutHandler(userId); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId); - final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, - mLazyDaemon, token, requestId, receiver, operationId, restricted, - options, cookie, false /* requireConfirmation */, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, isStrongBiometric, mLockoutTracker, - mUsageStats, allowBackgroundAuthentication, - Utils.getCurrentStrength(mSensorId)); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } else { + scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } }); } + private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter receiver, + @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, + int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient( + mContext, this::getSession, token, requestId, receiver, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), mBiometricContext, + isStrongBiometric, mUsageStats, mLockoutTracker, + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter receiver, + @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, + int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, + mLazyDaemon, token, requestId, receiver, operationId, restricted, options, + cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), mBiometricContext, + isStrongBiometric, mLockoutTracker, mUsageStats, + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client); + } + @Override public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter receiver, @@ -719,13 +886,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId); + } else { + scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId); + } }); } @@ -736,17 +901,39 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { scheduleUpdateActiveUserWithoutHandler(userId); // For IBiometricsFace@1.0, remove(0) means remove all enrollments - final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId, - opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, userId, receiver, opPackageName, 0); + } else { + scheduleRemoveHidl(token, userId, receiver, opPackageName, 0); + } }); } + private void scheduleRemoveAidl(@NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { + final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId, + opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleRemoveHidl(@NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { + final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), faceId, userId, + opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) { mHandler.post(() -> { @@ -756,89 +943,180 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } scheduleUpdateActiveUserWithoutHandler(userId); - - final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, - mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, hardwareAuthToken); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleResetLockoutAidl(userId, hardwareAuthToken); + } else { + scheduleResetLockoutHidl(userId, hardwareAuthToken); + } }); } + private void scheduleResetLockoutAidl(int userId, + @NonNull byte[] hardwareAuthToken) { + final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, mSensorProperties.sensorStrength); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleResetLockoutHidl(int userId, + @NonNull byte[] hardwareAuthToken) { + final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, + mLazyDaemon, + userId, mContext.getOpPackageName(), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final List<Face> faces = getEnrolledFaces(sensorId, userId); - if (faces.isEmpty()) { - Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); - return; + scheduleUpdateActiveUserWithoutHandler(userId); + if (Flags.deHidl()) { + scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, + receiver, opPackageName); + } else { + scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, + receiver, opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, + int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final List<Face> faces = getEnrolledFaces(sensorId, userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); + return; + } + final int faceId = faces.get(0).getBiometricId(); + final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon, + token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, + enabled, hardwareAuthToken, faceId); + mScheduler.scheduleClientMonitor(client); + } - final int faceId = faces.get(0).getBiometricId(); - final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext), - mBiometricContext, - feature, enabled, hardwareAuthToken, faceId); - mScheduler.scheduleClientMonitor(client); - }); + private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId, + int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature, enabled, hardwareAuthToken); + mScheduler.scheduleClientMonitor(client); } + @Override public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) { mHandler.post(() -> { - final List<Face> faces = getEnrolledFaces(sensorId, userId); - if (faces.isEmpty()) { - Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); - return; + scheduleUpdateActiveUserWithoutHandler(userId); + + if (Flags.deHidl()) { + scheduleGetFeatureAidl(token, userId, feature, listener, + opPackageName); + } else { + scheduleGetFeatureHidl(sensorId, token, userId, feature, listener, + opPackageName); } + }); + } - scheduleUpdateActiveUserWithoutHandler(userId); + private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, + int feature, @Nullable ClientMonitorCallbackConverter listener, + @NonNull String opPackageName) { + final List<Face> faces = getEnrolledFaces(sensorId, userId); + if (faces.isEmpty()) { + Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); + return; + } - final int faceId = faces.get(0).getBiometricId(); - final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, - token, listener, userId, opPackageName, mSensorId, - BiometricLogger.ofUnknown(mContext), mBiometricContext, - feature, faceId); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientFinished( - @NonNull BaseClientMonitor clientMonitor, boolean success) { - if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { - final int settingsValue = client.getValue() ? 1 : 0; - Slog.d(TAG, "Updating attention value for user: " + userId - + " to value: " + settingsValue); - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, - settingsValue, userId); - } + final int faceId = faces.get(0).getBiometricId(); + final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, + token, listener, userId, opPackageName, mSensorId, + BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (success + && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { + final int settingsValue = client.getValue() ? 1 : 0; + Slog.d(TAG, + "Updating attention value for user: " + userId + " to value: " + + settingsValue); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue, + userId); } - }); + } }); } + private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId, + int feature, @Nullable ClientMonitorCallbackConverter listener, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client = + new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient( + mContext, this::getSession, token, listener, userId, opPackageName, + mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature); + mScheduler.scheduleClientMonitor(client); + } + private void scheduleInternalCleanup(int userId, @Nullable ClientMonitorCallback callback) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - - final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, - mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, - createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, - FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback, - mBiometricStateCallback)); + if (Flags.deHidl()) { + scheduleInternalCleanupAidl(userId, callback); + } else { + scheduleInternalCleanupHidl(userId, callback); + } }); } + private void scheduleInternalCleanupHidl(int userId, + @Nullable ClientMonitorCallback callback) { + final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, + mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, + new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); + } + + private void scheduleInternalCleanupAidl(int userId, + @Nullable ClientMonitorCallback callback) { + final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient + client = + new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), + mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, + new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); + } + @Override public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback) { @@ -970,6 +1248,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { + if (mCurrentUserId != targetUserId) { + // Create new session with updated user ID + mSession = null; + } mCurrentUserId = targetUserId; } else { Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..36a9790d2d4b --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,115 @@ +/* + * 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.biometrics.sensors.face.hidl; + +import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub { + + private final AidlResponseHandler mAidlResponseHandler; + + public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) { + mAidlResponseHandler = aidlResponseHandler; + } + + @Override + public void onEnrollResult( + long deviceId, int faceId, int userId, int remaining) { + mAidlResponseHandler.onEnrollmentProgress(faceId, remaining); + } + + @Override + public void onAuthenticated(long deviceId, int faceId, int userId, + ArrayList<Byte> token) { + final boolean authenticated = faceId != 0; + byte[] hardwareAuthToken = new byte[token.size()]; + + for (int i = 0; i < token.size(); i++) { + hardwareAuthToken[i] = token.get(i); + } + + if (authenticated) { + mAidlResponseHandler.onAuthenticationSucceeded(faceId, + HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); + } else { + mAidlResponseHandler.onAuthenticationFailed(); + } + } + + @Override + public void onAcquired(long deviceId, int userId, int acquiredInfo, + int vendorCode) { + mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); + } + + @Override + public void onError(long deviceId, int userId, int error, int vendorCode) { + mAidlResponseHandler.onError(error, vendorCode); + } + + @Override + public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) { + int[] enrollmentIds = new int[removed.size()]; + for (int i = 0; i < removed.size(); i++) { + enrollmentIds[i] = removed.get(i); + } + mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds); + } + + @Override + public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) { + int[] enrollmentIds = new int[faceIds.size()]; + for (int i = 0; i < faceIds.size(); i++) { + enrollmentIds[i] = faceIds.get(i); + } + mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds); + } + + @Override + public void onLockoutChanged(long duration) { + mAidlResponseHandler.onLockoutChanged(duration); + } + + void onChallengeGenerated(long challenge) { + mAidlResponseHandler.onChallengeGenerated(challenge); + } + + void onChallengeRevoked(long challenge) { + mAidlResponseHandler.onChallengeRevoked(challenge); + } + + void onFeatureGet(byte[] features) { + mAidlResponseHandler.onFeaturesRetrieved(features); + } + + void onFeatureSet(byte feature) { + mAidlResponseHandler.onFeatureSet(feature); + } + + void onAuthenticatorIdRetrieved(long authenticatorId) { + mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..4a019436cf6f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java @@ -0,0 +1,274 @@ +/* + * 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.biometrics.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { + + /** + * Interface to send results to the AidlResponseHandler's owner. + */ + public interface HardwareUnavailableCallback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } + + private static final String TAG = "AidlResponseHandler"; + + @NonNull + private final Context mContext; + @NonNull + private final BiometricScheduler mScheduler; + private final int mSensorId; + private final int mUserId; + @NonNull + private final LockoutCache mLockoutCache; + @NonNull + private final LockoutResetDispatcher mLockoutResetDispatcher; + @NonNull + private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final HardwareUnavailableCallback mHardwareUnavailableCallback; + + public AidlResponseHandler(@NonNull Context context, + @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull LockoutCache lockoutTracker, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull AuthSessionCoordinator authSessionCoordinator, + @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { + mContext = context; + mScheduler = scheduler; + mSensorId = sensorId; + mUserId = userId; + mLockoutCache = lockoutTracker; + mLockoutResetDispatcher = lockoutResetDispatcher; + mAuthSessionCoordinator = authSessionCoordinator; + mHardwareUnavailableCallback = hardwareUnavailableCallback; + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + + @Override + public void onChallengeGenerated(long challenge) { + handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated( + mSensorId, mUserId, challenge), null); + } + + @Override + public void onChallengeRevoked(long challenge) { + handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked( + challenge), null); + } + + /** + * Handles acquired messages sent by the HAL (specifically for HIDL HAL). + */ + public void onAcquired(int acquiredInfo, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), + null); + } + + @Override + public void onAcquired(byte info, int vendorCode) { + handleResponse(AcquisitionClient.class, (c) -> c.onAcquired( + AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null); + } + + /** + * Handle error messages from the HAL. + */ + public void onError(int error, int vendorCode) { + handleResponse(ErrorConsumer.class, (c) -> { + c.onError(error, vendorCode); + if (error == Error.HW_UNAVAILABLE) { + mHardwareUnavailableCallback.onHardwareUnavailable(); + } + }, null); + } + + @Override + public void onError(byte error, int vendorCode) { + onError(AidlConversionUtils.toFrameworkError(error), vendorCode); + } + + @Override + public void onEnrollmentProgress(int enrollmentId, int remaining) { + BaseClientMonitor client = mScheduler.getCurrentClient(); + final int currentUserId; + if (client == null) { + return; + } else { + currentUserId = client.getTargetUserId(); + } + final CharSequence name = FingerprintUtils.getInstance(mSensorId) + .getUniqueName(mContext, currentUserId); + final Fingerprint fingerprint = new Fingerprint(name, currentUserId, + enrollmentId, mSensorId); + handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint, + remaining), null); + } + + @Override + public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { + final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); + final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); + final ArrayList<Byte> byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, + true /* authenticated */, byteList), (c) -> onInteractionDetected()); + } + + @Override + public void onAuthenticationFailed() { + final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); + handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, + false /* authenticated */, null /* hardwareAuthToken */), + (c) -> onInteractionDetected()); + } + + @Override + public void onLockoutTimed(long durationMillis) { + handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), + null); + } + + @Override + public void onLockoutPermanent() { + handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); + } + + @Override + public void onLockoutCleared() { + handleResponse(FingerprintResetLockoutClient.class, + FingerprintResetLockoutClient::onLockoutCleared, + (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone( + mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher, + mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId), + -1 /* requestId */)); + } + + @Override + public void onInteractionDetected() { + handleResponse(FingerprintDetectClient.class, + FingerprintDetectClient::onInteractionDetected, null); + } + + @Override + public void onEnrollmentsEnumerated(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + int finalI = i; + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null, + 0), null); + } + } + + @Override + public void onEnrollmentsRemoved(int[] enrollmentIds) { + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + int finalI = i; + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, + enrollmentIds.length - finalI - 1), null); + } + } else { + handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0), + null); + } + } + + @Override + public void onAuthenticatorIdRetrieved(long authenticatorId) { + handleResponse(FingerprintGetAuthenticatorIdClient.class, + (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null); + } + + @Override + public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { + handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( + newAuthenticatorId), null); + } + + private <T> void handleResponse(@NonNull Class<T> className, + @NonNull Consumer<T> action, + @Nullable Consumer<BaseClientMonitor> alternateAction) { + mScheduler.getHandler().post(() -> { + final BaseClientMonitor client = mScheduler.getCurrentClient(); + if (className.isInstance(client)) { + action.accept((T) client); + } else { + Slog.e(TAG, "Client monitor is not an instance of " + className.getName()); + if (alternateAction != null) { + alternateAction.accept(client); + } + } + }); + } + + @Override + public void onSessionClosed() { + mScheduler.getHandler().post(mScheduler::onUserStopped); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java index 55861bb4cc6f..299a310caee9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java @@ -16,10 +16,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; -import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback; - import android.annotation.NonNull; import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; + +import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier; /** * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -30,14 +33,22 @@ public class AidlSession { private final int mHalInterfaceVersion; @NonNull private final ISession mSession; private final int mUserId; - @NonNull private final HalSessionCallback mHalSessionCallback; + @NonNull private final AidlResponseHandler mAidlResponseHandler; public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, - HalSessionCallback halSessionCallback) { + AidlResponseHandler aidlResponseHandler) { mHalInterfaceVersion = halInterfaceVersion; mSession = session; mUserId = userId; - mHalSessionCallback = halSessionCallback; + mAidlResponseHandler = aidlResponseHandler; + } + + public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session, + int userId, AidlResponseHandler aidlResponseHandler) { + mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler); + mHalInterfaceVersion = 0; + mUserId = userId; + mAidlResponseHandler = aidlResponseHandler; } /** The underlying {@link ISession}. */ @@ -51,8 +62,8 @@ public class AidlSession { } /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ - HalSessionCallback getHalSessionCallback() { - return mHalSessionCallback; + AidlResponseHandler getHalSessionCallback() { + return mAidlResponseHandler; } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 54d1faa39be0..337c3c299e54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.TaskStackListener; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; import android.hardware.biometrics.BiometricManager.Authenticators; @@ -51,8 +52,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; @@ -66,7 +67,7 @@ import java.util.function.Supplier; * Fingerprint-specific authentication client supporting the {@link * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintAuthenticationClient +public class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions> implements Udfps, LockoutConsumer, PowerPressHandler { private static final String TAG = "FingerprintAuthenticationClient"; @@ -93,7 +94,7 @@ class FingerprintAuthenticationClient private Runnable mAuthSuccessRunnable; private final Clock mClock; - FingerprintAuthenticationClient( + public FingerprintAuthenticationClient( @NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @@ -108,14 +109,14 @@ class FingerprintAuthenticationClient @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, - @NonNull LockoutCache lockoutCache, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull Handler handler, @Authenticators.Types int biometricStrength, - @NonNull Clock clock) { + @NonNull Clock clock, + @Nullable LockoutTracker lockoutTracker) { super( context, lazyDaemon, @@ -130,7 +131,7 @@ class FingerprintAuthenticationClient biometricContext, isStrongBiometric, taskStackListener, - null /* lockoutCache */, + lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, biometricStrength); @@ -211,6 +212,7 @@ class FingerprintAuthenticationClient boolean authenticated, ArrayList<Byte> token) { super.onAuthenticated(identifier, authenticated, token); + handleLockout(authenticated); if (authenticated) { mState = STATE_STOPPED; mSensorOverlays.hide(getSensorId()); @@ -219,6 +221,32 @@ class FingerprintAuthenticationClient } } + private void handleLockout(boolean authenticated) { + if (getLockoutTracker() == null) { + Slog.d(TAG, "Lockout is implemented by the HAL"); + return; + } + if (authenticated) { + getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */, + getTargetUserId()); + } else { + @LockoutTracker.LockoutMode final int lockoutMode = + getLockoutTracker().getLockoutModeForUser(getTargetUserId()); + if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { + Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")"); + final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED + ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT + : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; + // Send the error, but do not invoke the FinishCallback yet. Since lockout is not + // controlled by the HAL, the framework must stop the sensor before finishing the + // client. + mSensorOverlays.hide(getSensorId()); + onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); + cancel(); + } + } + } + @Override public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 4502e5d0c4b6..e2413ee1c016 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -43,7 +43,8 @@ import java.util.function.Supplier; * Performs fingerprint detection without exposing any matching information (e.g. accept/reject * have the same haptic, lockout counter is not increased). */ -class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer { +public class FingerprintDetectClient extends AcquisitionClient<AidlSession> + implements DetectionConsumer { private static final String TAG = "FingerprintDetectClient"; @@ -52,7 +53,8 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements @NonNull private final SensorOverlays mSensorOverlays; @Nullable private ICancellationSignal mCancellationSignal; - FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FingerprintDetectClient(@NonNull Context context, + @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, @NonNull FingerprintAuthenticateOptions options, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 46ff6b4fab1a..06550d8b4fce 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -49,14 +49,13 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.SensorOverlays; -import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; import java.util.function.Supplier; -class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, +public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, PowerPressHandler { private static final String TAG = "FingerprintEnrollClient"; @@ -72,12 +71,16 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps private static boolean shouldVibrateFor(Context context, FingerprintSensorPropertiesInternal sensorProps) { - final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); - final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); - return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; + if (sensorProps != null) { + final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); + final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); + return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; + } else { + return true; + } } - FingerprintEnrollClient(@NonNull Context context, + public FingerprintEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @@ -89,8 +92,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, - 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger, - biometricContext); + 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), + logger, biometricContext); setRequestId(requestId); mSensorProps = sensorProps; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); @@ -136,7 +139,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; // For UDFPS, notify SysUI that the illumination can be turned off. // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE - if (mSensorProps.isAnyUdfpsType()) { + if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) { if (acquiredGood && mShouldVibrate) { vibrateSuccess(); } @@ -162,8 +165,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps @Override protected boolean hasReachedEnrollmentLimit() { - return FingerprintUtils.getInstance(getSensorId()) - .getBiometricsForUser(getContext(), getTargetUserId()).size() + return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size() >= mMaxTemplatesPerUser; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java index ddae8bedf7c1..ce693ff58e19 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java @@ -33,10 +33,10 @@ import java.util.function.Supplier; /** * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface. */ -class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { +public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { private static final String TAG = "FingerprintGenerateChallengeClient"; - FingerprintGenerateChallengeClient(@NonNull Context context, + public FingerprintGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index ff9127f516af..5edc2ca080ad 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -39,9 +39,10 @@ import java.util.function.Supplier; * Fingerprint-specific internal cleanup client supporting the * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> { +public class FingerprintInternalCleanupClient + extends InternalCleanupClient<Fingerprint, AidlSession> { - FingerprintInternalCleanupClient(@NonNull Context context, + public FingerprintInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index f74b45cbdb0e..9985b06833c9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -428,8 +428,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENROLL, + opPackageName, FingerprintUtils.getInstance(sensorId), + sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, mFingerprintSensors.get(sensorId).getSensorProperties(), @@ -496,12 +496,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, - mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(), + mTaskStackListener, mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler, Utils.getCurrentStrength(sensorId), - SystemClock.elapsedRealtimeClock()); + SystemClock.elapsedRealtimeClock(), + null /* lockoutTracker */); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override @@ -875,6 +876,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void simulateVhalFingerDown(int userId, int sensorId) { Slog.d(getTag(), "Simulate virtual HAL finger down event"); final AidlSession session = mFingerprintSensors.get(sensorId).getSessionForUser(userId); + if (session == null) { + Slog.e(getTag(), "no existing hal session found - aborting"); + return; + } final PointerContext pc = new PointerContext(); try { session.getSession().onPointerDownWithContext(pc); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java index d559bb1d72f1..4f08f6fbde54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java @@ -37,12 +37,12 @@ import java.util.function.Supplier; * Fingerprint-specific removal client supporting the * {@link android.hardware.biometrics.fingerprint.IFingerprint} interface. */ -class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { +public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { private static final String TAG = "FingerprintRemovalClient"; private final int[] mBiometricIds; - FingerprintRemovalClient(@NonNull Context context, + public FingerprintRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index 7a620349075c..ec225a60d54b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -43,19 +42,20 @@ import java.util.function.Supplier; * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is * cleared. */ -class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer { +public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> + implements ErrorConsumer { private static final String TAG = "FingerprintResetLockoutClient"; private final HardwareAuthToken mHardwareAuthToken; - private final LockoutCache mLockoutCache; + private final LockoutTracker mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; private final int mBiometricStrength; - FingerprintResetLockoutClient(@NonNull Context context, + public FingerprintResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, - @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, + @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,10 +107,11 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem * be used instead. */ static void resetLocalLockoutStateToNone(int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, + @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, @Authenticators.Types int biometricStrength, long requestId) { + lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId); lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE); lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId); authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java index afa62e2e3173..23d8e1e63f63 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java @@ -32,13 +32,13 @@ import java.util.function.Supplier; /** * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface. */ -class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { +public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { - private static final String TAG = "FingerpirntRevokeChallengeClient"; + private static final String TAG = "FingerprintRevokeChallengeClient"; private final long mChallenge; - FingerprintRevokeChallengeClient(@NonNull Context context, + public FingerprintRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @@ -57,7 +57,7 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession } } - void onChallengeRevoked(int sensorId, int userId, long challenge) { + void onChallengeRevoked(long challenge) { final boolean success = challenge == mChallenge; mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 56b85ceb8e6b..893cb8f9b4fc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -23,13 +23,9 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.fingerprint.Error; import android.hardware.biometrics.fingerprint.ISession; -import android.hardware.biometrics.fingerprint.ISessionCallback; -import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -39,34 +35,25 @@ import android.os.UserManager; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AcquisitionClient; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; -import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -93,348 +80,6 @@ public class Sensor { @Nullable AidlSession mCurrentSession; @NonNull private final Supplier<AidlSession> mLazySession; - @VisibleForTesting - public static class HalSessionCallback extends ISessionCallback.Stub { - - /** - * Interface to sends results to the HalSessionCallback's owner. - */ - public interface Callback { - /** - * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. - */ - void onHardwareUnavailable(); - } - - @NonNull - private final Context mContext; - @NonNull - private final Handler mHandler; - @NonNull - private final String mTag; - @NonNull - private final UserAwareBiometricScheduler mScheduler; - private final int mSensorId; - private final int mUserId; - @NonNull - private final LockoutCache mLockoutCache; - @NonNull - private final LockoutResetDispatcher mLockoutResetDispatcher; - @NonNull - private AuthSessionCoordinator mAuthSessionCoordinator; - @NonNull - private final Callback mCallback; - - HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, - @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull Callback callback) { - mContext = context; - mHandler = handler; - mTag = tag; - mScheduler = scheduler; - mSensorId = sensorId; - mUserId = userId; - mLockoutCache = lockoutTracker; - mLockoutResetDispatcher = lockoutResetDispatcher; - mAuthSessionCoordinator = authSessionCoordinator; - mCallback = callback; - } - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() { - return this.HASH; - } - - @Override - public void onChallengeGenerated(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGenerateChallengeClient)) { - Slog.e(mTag, "onChallengeGenerated for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintGenerateChallengeClient generateChallengeClient = - (FingerprintGenerateChallengeClient) client; - generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onChallengeRevoked(long challenge) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintRevokeChallengeClient)) { - Slog.e(mTag, "onChallengeRevoked for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintRevokeChallengeClient revokeChallengeClient = - (FingerprintRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); - }); - } - - @Override - public void onAcquired(byte info, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AcquisitionClient)) { - Slog.e(mTag, "onAcquired for non-acquisition client: " - + Utils.getClientName(client)); - return; - } - - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info), - vendorCode); - }); - } - - @Override - public void onError(byte error, int vendorCode) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - Slog.d(mTag, "onError" - + ", client: " + Utils.getClientName(client) - + ", error: " + error - + ", vendorCode: " + vendorCode); - if (!(client instanceof ErrorConsumer)) { - Slog.e(mTag, "onError for non-error consumer: " - + Utils.getClientName(client)); - return; - } - - final ErrorConsumer errorConsumer = (ErrorConsumer) client; - errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - - if (error == Error.HW_UNAVAILABLE) { - mCallback.onHardwareUnavailable(); - } - }); - } - - @Override - public void onEnrollmentProgress(int enrollmentId, int remaining) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintEnrollClient)) { - Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " - + Utils.getClientName(client)); - return; - } - - final int currentUserId = client.getTargetUserId(); - final CharSequence name = FingerprintUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); - final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId); - - final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; - enrollClient.onEnrollResult(fingerprint, remaining); - }); - } - - @Override - public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); - final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); - final ArrayList<Byte> byteList = new ArrayList<>(); - for (byte b : byteArray) { - byteList.add(b); - } - - authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList); - }); - } - - @Override - public void onAuthenticationFailed() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } - - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); - authenticationConsumer - .onAuthenticated(fp, false /* authenticated */, null /* hat */); - }); - } - - @Override - public void onLockoutTimed(long durationMillis) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } - - @Override - public void onLockoutPermanent() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } - - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutPermanent(); - }); - } - - @Override - public void onLockoutCleared() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintResetLockoutClient)) { - Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); - // Given that onLockoutCleared() can happen at any time, and is not necessarily - // coming from a specific client, set this to -1 to indicate it wasn't for a - // specific request. - FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, - mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - Utils.getCurrentStrength(mSensorId), -1 /* requestId */); - } else { - Slog.d(mTag, "onLockoutCleared after resetLockout"); - final FingerprintResetLockoutClient resetLockoutClient = - (FingerprintResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - } - }); - } - - @Override - public void onInteractionDetected() { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintDetectClient)) { - Slog.e(mTag, "onInteractionDetected for non-detect client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintDetectClient fingerprintDetectClient = - (FingerprintDetectClient) client; - fingerprintDetectClient.onInteractionDetected(); - }); - } - - @Override - public void onEnrollmentsEnumerated(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof EnumerateConsumer)) { - Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " - + Utils.getClientName(client)); - return; - } - - final EnumerateConsumer enumerateConsumer = - (EnumerateConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); - enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1); - } - } else { - enumerateConsumer.onEnumerationResult(null /* identifier */, 0); - } - }); - } - - @Override - public void onEnrollmentsRemoved(int[] enrollmentIds) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof RemovalConsumer)) { - Slog.e(mTag, "onRemoved for non-removal consumer: " - + Utils.getClientName(client)); - return; - } - - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); - removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1); - } - } else { - removalConsumer.onRemoved(null, 0); - } - }); - } - - @Override - public void onAuthenticatorIdRetrieved(long authenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGetAuthenticatorIdClient)) { - Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient = - (FingerprintGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } - - @Override - public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { - mHandler.post(() -> { - final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintInvalidationClient)) { - Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " - + Utils.getClientName(client)); - return; - } - - final FingerprintInvalidationClient invalidationClient = - (FingerprintInvalidationClient) client; - invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); - }); - } - - @Override - public void onSessionClosed() { - mHandler.post(mScheduler::onUserStopped); - } - } - Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @@ -466,9 +111,9 @@ public class Sensor { public StartUserClient<?, ?> getStartUserClient(int newUserId) { final int sensorId = mSensorProperties.sensorId; - final HalSessionCallback resultController = new HalSessionCallback(mContext, - mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, - lockoutResetDispatcher, + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutCache, lockoutResetDispatcher, biometricContext.getAuthSessionCoordinator(), () -> { Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..b48d232e65af --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java @@ -0,0 +1,231 @@ +/* + * 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.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { + private final String TAG = "AidlToHidlAdapter"; + @VisibleForTesting + static final int ENROLL_TIMEOUT_SEC = 60; + @NonNull + private final Supplier<IBiometricsFingerprint> mSession; + private final int mUserId; + private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; + + public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId, + AidlResponseHandler aidlResponseHandler) { + mSession = session; + mUserId = userId; + setCallback(aidlResponseHandler); + } + + private void setCallback(AidlResponseHandler aidlResponseHandler) { + mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); + try { + mSession.get().setNotify(mHidlToAidlCallbackConverter); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to set callback"); + } + } + + @Override + public IBinder asBinder() { + return null; + } + + @Override + public void generateChallenge() throws RemoteException { + long challenge = mSession.get().preEnroll(); + mHidlToAidlCallbackConverter.onChallengeGenerated(challenge); + } + + @Override + public void revokeChallenge(long challenge) throws RemoteException { + mSession.get().postEnroll(); + mHidlToAidlCallbackConverter.onChallengeRevoked(0L); + } + + @Override + public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException { + mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId, + ENROLL_TIMEOUT_SEC); + return new Cancellation(); + } + + @Override + public ICancellationSignal authenticate(long operationId) throws RemoteException { + mSession.get().authenticate(operationId, mUserId); + return new Cancellation(); + } + + @Override + public ICancellationSignal detectInteraction() throws RemoteException { + mSession.get().authenticate(0, mUserId); + return new Cancellation(); + } + + @Override + public void enumerateEnrollments() throws RemoteException { + mSession.get().enumerate(); + } + + @Override + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + if (enrollmentIds.length > 1) { + mSession.get().remove(mUserId, 0); + } else { + mSession.get().remove(mUserId, enrollmentIds[0]); + } + } + + @Override + public void onPointerDown(int pointerId, int x, int y, float minor, float major) + throws RemoteException { + UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major); + } + + @Override + public void onPointerUp(int pointerId) throws RemoteException { + UdfpsHelper.onFingerUp(mSession.get()); + } + + @Override + public void getAuthenticatorId() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void invalidateAuthenticatorId() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + mHidlToAidlCallbackConverter.onResetLockout(); + } + + @Override + public void close() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onUiReady() throws RemoteException { + //Unsupported in HIDL + } + + @Override + public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public ICancellationSignal detectInteractionWithContext(OperationContext context) + throws RemoteException { + //Unsupported in HIDL + return null; + } + + @Override + public void onPointerDownWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onPointerUpWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onContextChanged(OperationContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void onPointerCancelWithContext(PointerContext context) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException { + //Unsupported in HIDL + } + + @Override + public int getInterfaceVersion() throws RemoteException { + //Unsupported in HIDL + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + //Unsupported in HIDL + return null; + } + + private class Cancellation extends ICancellationSignal.Stub { + + Cancellation() {} + @Override + public void cancel() throws RemoteException { + try { + mSession.get().cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting cancel", e); + } + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index a655f3601a07..8bfa560b8031 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; @@ -64,6 +65,7 @@ import com.android.server.biometrics.fingerprint.PerformanceStatsProto; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; @@ -74,6 +76,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; @@ -82,6 +85,8 @@ import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import com.android.server.biometrics.sensors.fingerprint.ServiceProvider; import com.android.server.biometrics.sensors.fingerprint.Udfps; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession; import org.json.JSONArray; import org.json.JSONException; @@ -132,6 +137,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private final boolean mIsUdfps; private final int mSensorId; private final boolean mIsPowerbuttonFps; + private AidlSession mSession; private final class BiometricTaskStackListener extends TaskStackListener { @Override @@ -413,6 +419,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider }); } + synchronized AidlSession getSession() { + if (mDaemon != null && mSession != null) { + return mSession; + } else { + return mSession = new AidlSession(this::getDaemon, + mCurrentUserId, new AidlResponseHandler(mContext, + mScheduler, mSensorId, mCurrentUserId, new LockoutCache(), + mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> { + mDaemon = null; + mCurrentUserId = UserHandle.USER_NULL; + })); + } + } + @VisibleForTesting synchronized IBiometricsFingerprint getDaemon() { if (mTestHalEnabled) { @@ -518,6 +538,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { if (success) { + if (mCurrentUserId != targetUserId) { + // Create new session with updated user ID + mSession = null; + } mCurrentUserId = targetUserId; } else { Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); @@ -554,47 +578,116 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler // thread. mHandler.post(() -> { - final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, - userId, mContext.getOpPackageName(), sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext, mLockoutTracker); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken); + } else { + scheduleResetLockoutHidl(sensorId, userId); + } }); } + private void scheduleResetLockoutAidl(int sensorId, int userId, + @Nullable byte[] hardwareAuthToken) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, hardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, + Utils.getCurrentStrength(sensorId)); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleResetLockoutHidl(int sensorId, int userId) { + final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, + userId, mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, mLockoutTracker); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final FingerprintGenerateChallengeClient client = - new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, - new ClientMonitorCallbackConverter(receiver), userId, opPackageName, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); + } else { + scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName); + } }); } + private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { + final FingerprintGenerateChallengeClient client = + new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + @Override public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( - mContext, mLazyDaemon, token, userId, opPackageName, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext); - mScheduler.scheduleClientMonitor(client); + if (Flags.deHidl()) { + scheduleRevokeChallengeAidl(userId, token, opPackageName); + } else { + scheduleRevokeChallengeHidl(userId, token, opPackageName); + } }); } + private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient( + mContext, this::getSession, + token, userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, 0L); + mScheduler.scheduleClientMonitor(client); + } + + private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, + @NonNull String opPackageName) { + final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( + mContext, mLazyDaemon, token, userId, opPackageName, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext); + mScheduler.scheduleClientMonitor(client); + } + @Override public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @@ -604,38 +697,96 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), - userId, hardwareAuthToken, opPackageName, - FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENROLL, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); - mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - mBiometricStateCallback.onClientStarted(clientMonitor); - } + if (Flags.deHidl()) { + scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, + opPackageName, enrollReason, id); + } else { + scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, + opPackageName, enrollReason, id); + } + }); + return id; + } - @Override - public void onBiometricAction(int action) { - mBiometricStateCallback.onBiometricAction(action); + private void scheduleEnrollHidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + @FingerprintManager.EnrollReason int enrollReason, long id) { + final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, + mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), + true /* force */); } + } + }); + } - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - mBiometricStateCallback.onClientFinished(clientMonitor, success); - if (success) { - // Update authenticatorIds - scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), - true /* force */); - } + private void scheduleEnrollAidl(@NonNull IBinder token, + @NonNull byte[] hardwareAuthToken, int userId, + @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, + @FingerprintManager.EnrollReason int enrollReason, long id) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient( + mContext, + this::getSession, token, id, + new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, null /* sensorProps */, + mUdfpsOverlayController, mSidefpsController, + mContext.getResources().getInteger( + com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser), + enrollReason); + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + mBiometricStateCallback.onClientStarted(clientMonitor); + } + + @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mBiometricStateCallback.onClientFinished(clientMonitor, success); + if (success) { + // Update authenticatorIds + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), + true /* force */); } - }); + } }); - return id; } @Override @@ -653,17 +804,46 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); - final FingerprintDetectClient client = new FingerprintDetectClient(mContext, - mLazyDaemon, token, id, listener, options, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, mUdfpsOverlayController, isStrongBiometric); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + + if (Flags.deHidl()) { + scheduleFingerDetectAidl(token, listener, options, statsClient, id, + isStrongBiometric); + } else { + scheduleFingerDetectHidl(token, listener, options, statsClient, id, + isStrongBiometric); + } }); return id; } + private void scheduleFingerDetectHidl(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + int statsClient, long id, boolean isStrongBiometric) { + final FingerprintDetectClient client = new FingerprintDetectClient(mContext, + mLazyDaemon, token, id, listener, options, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, isStrongBiometric); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleFingerDetectAidl(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + int statsClient, long id, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient( + mContext, + this::getSession, token, id, listener, options, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, mUdfpsOverlayController, isStrongBiometric); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -674,20 +854,55 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); - final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( - mContext, mLazyDaemon, token, requestId, listener, operationId, - restricted, options, cookie, false /* requireConfirmation */, - createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, - mAuthenticationStatsCollector), - mBiometricContext, isStrongBiometric, - mTaskStackListener, mLockoutTracker, - mUdfpsOverlayController, mSidefpsController, - allowBackgroundAuthentication, mSensorProperties, - Utils.getCurrentStrength(mSensorId)); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + + if (Flags.deHidl()) { + scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } else { + scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId, + restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); + } }); } + private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, + boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient( + mContext, this::getSession, token, requestId, listener, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, isStrongBiometric, + mTaskStackListener, + mUdfpsOverlayController, mSidefpsController, + allowBackgroundAuthentication, mSensorProperties, mHandler, + Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, + boolean allowBackgroundAuthentication, boolean isStrongBiometric) { + final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( + mContext, mLazyDaemon, token, requestId, listener, operationId, + restricted, options, cookie, false /* requireConfirmation */, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, + mAuthenticationStatsCollector), + mBiometricContext, isStrongBiometric, + mTaskStackListener, mLockoutTracker, + mUdfpsOverlayController, mSidefpsController, + allowBackgroundAuthentication, mSensorProperties, + Utils.getCurrentStrength(mSensorId)); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -719,17 +934,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, - userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName); + } else { + scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName); + } }); } + private void scheduleRemoveHidl(@NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, + @NonNull String opPackageName) { + final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, + userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + + private void scheduleRemoveAidl(@NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, + @NonNull String opPackageName) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient( + mContext, this::getSession, token, + new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId, + opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + } + @Override public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, int userId, @@ -738,15 +977,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider scheduleUpdateActiveUserWithoutHandler(userId); // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments - final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), - 0 /* fingerprintId */, userId, opPackageName, - FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + if (Flags.deHidl()) { + scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName); + } else { + scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName); + } }); } @@ -755,17 +990,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( - mContext, mLazyDaemon, userId, mContext.getOpPackageName(), - mSensorProperties.sensorId, - createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, - BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), - mBiometricContext, - FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, callback); + if (Flags.deHidl()) { + scheduleInternalCleanupAidl(userId, callback); + } else { + scheduleInternalCleanupHidl(userId, callback); + } }); } + private void scheduleInternalCleanupHidl(int userId, + @Nullable ClientMonitorCallback callback) { + final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( + mContext, mLazyDaemon, userId, mContext.getOpPackageName(), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), + mBiometricContext, + FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, callback); + } + + private void scheduleInternalCleanupAidl(int userId, + @Nullable ClientMonitorCallback callback) { + final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient + client = + new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient( + mContext, this::getSession, userId, mContext.getOpPackageName(), + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), + mBiometricContext, + FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client, callback); + } + @Override public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..c3e5cbe7daf4 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,95 @@ +/* + * 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.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub { + + final AidlResponseHandler mAidlResponseHandler; + + public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) { + mAidlResponseHandler = aidlResponseHandler; + } + + @Override + public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining); + } + + @Override + public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) { + onAcquired_2_2(deviceId, acquiredInfo, vendorCode); + } + + @Override + public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) { + mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); + } + + @Override + public void onAuthenticated(long deviceId, int fingerId, int groupId, + ArrayList<Byte> token) { + if (fingerId != 0) { + byte[] hardwareAuthToken = new byte[token.size()]; + for (int i = 0; i < token.size(); i++) { + hardwareAuthToken[i] = token.get(i); + } + mAidlResponseHandler.onAuthenticationSucceeded(fingerId, + HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); + } else { + mAidlResponseHandler.onAuthenticationFailed(); + } + } + + @Override + public void onError(long deviceId, int error, int vendorCode) { + mAidlResponseHandler.onError(error, vendorCode); + } + + @Override + public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId}); + } + + @Override + public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) { + mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId}); + } + + void onChallengeGenerated(long challenge) { + mAidlResponseHandler.onChallengeGenerated(challenge); + } + + void onChallengeRevoked(long challenge) { + mAidlResponseHandler.onChallengeRevoked(challenge); + } + + void onResetLockout() { + mAidlResponseHandler.onLockoutCleared(); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java index 36d56c8a1544..0730c672acd9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java @@ -89,7 +89,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { // Attempt counter should only be cleared when Keyguard goes away or when // a biometric is successfully authenticated. Lockout should eventually be done below the HAL. // See AuthenticationClient#shouldFrameworkHandleLockout(). - void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { + @Override + public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { if (getLockoutModeForUser(userId) != LOCKOUT_NONE) { Slog.v(TAG, "Reset biometric lockout for user: " + userId + ", clearAttemptCounter: " + clearAttemptCounter); @@ -104,7 +105,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { mLockoutResetCallback.onLockoutReset(userId); } - void addFailedAttemptForUser(int userId) { + @Override + public void addFailedAttemptForUser(int userId) { mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1); mTimedLockoutCleared.put(userId, false); @@ -114,7 +116,8 @@ public class LockoutFrameworkImpl implements LockoutTracker { } @Override - public @LockoutMode int getLockoutModeForUser(int userId) { + @LockoutMode + public int getLockoutModeForUser(int userId) { final int failedAttempts = mFailedAttempts.get(userId, 0); if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) { return LOCKOUT_PERMANENT; @@ -126,6 +129,19 @@ public class LockoutFrameworkImpl implements LockoutTracker { return LOCKOUT_NONE; } + /** + * Clears lockout for Fingerprint HIDL HAL + */ + @Override + public void setLockoutModeForUser(int userId, int mode) { + mFailedAttempts.put(userId, 0); + mTimedLockoutCleared.put(userId, true); + // If we're asked to reset failed attempts externally (i.e. from Keyguard), + // the alarm might still be pending; remove it. + cancelLockoutResetForUser(userId); + mLockoutResetCallback.onLockoutReset(userId); + } + private void cancelLockoutResetForUser(int userId) { mAlarmManager.cancel(getLockoutResetIntentForUser(userId)); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d372f3031b81..0689478ded1e 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -160,6 +160,7 @@ import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.display.utils.SensorUtils; import com.android.server.input.InputManagerInternal; import com.android.server.utils.FoldSettingProvider; @@ -522,6 +523,8 @@ public final class DisplayManagerService extends SystemService { private final DisplayManagerFlags mFlags; + private final DisplayNotificationManager mDisplayNotificationManager; + /** * Applications use {@link android.view.Display#getRefreshRate} and * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate. @@ -555,6 +558,7 @@ public final class DisplayManagerService extends SystemService { mInjector = injector; mContext = context; mFlags = injector.getFlags(); + mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext); mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); @@ -650,6 +654,7 @@ public final class DisplayManagerService extends SystemService { } mDisplayModeDirector.onBootCompleted(); mLogicalDisplayMapper.onBootCompleted(); + mDisplayNotificationManager.onBootCompleted(); } } @@ -784,6 +789,10 @@ public final class DisplayManagerService extends SystemService { } } + DisplayNotificationManager getDisplayNotificationManager() { + return mDisplayNotificationManager; + } + private void loadStableDisplayValuesLocked() { final Point size = mPersistentDataStore.getStableDisplaySize(); if (size.x > 0 && size.y > 0) { @@ -1776,7 +1785,8 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { // main display adapter registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext, - mHandler, mDisplayDeviceRepo, mFlags)); + mHandler, mDisplayDeviceRepo, mFlags, + mDisplayNotificationManager)); // Standalone VR devices rely on a virtual display as their primary display for // 2D UI. We register virtual display adapter along side the main display adapter @@ -3191,9 +3201,10 @@ public final class DisplayManagerService extends SystemService { LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener, - flags); + flags, displayNotificationManager); } long getDefaultDisplayDelayTimeout() { diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 9b022d8b6662..d97c8e71c73c 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -30,6 +30,8 @@ import java.util.Arrays; class DisplayManagerShellCommand extends ShellCommand { private static final String TAG = "DisplayManagerShellCommand"; + private static final String NOTIFICATION_TYPES = + "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable"; private final DisplayManagerService mService; private final DisplayManagerFlags mFlags; @@ -46,6 +48,10 @@ class DisplayManagerShellCommand extends ShellCommand { } final PrintWriter pw = getOutPrintWriter(); switch(cmd) { + case "show-notification": + return showNotification(); + case "cancel-notifications": + return cancelNotifications(); case "set-brightness": return setBrightness(); case "reset-brightness-configuration": @@ -102,6 +108,10 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(); + pw.println(" show-notification NOTIFICATION_TYPE"); + pw.println(" Show notification for one of the following types: " + NOTIFICATION_TYPES); + pw.println(" cancel-notifications"); + pw.println(" Cancel notifications."); pw.println(" set-brightness BRIGHTNESS"); pw.println(" Sets the current brightness to BRIGHTNESS (a number between 0 and 1)."); pw.println(" reset-brightness-configuration"); @@ -172,6 +182,39 @@ class DisplayManagerShellCommand extends ShellCommand { return 0; } + private int showNotification() { + final String notificationType = getNextArg(); + if (notificationType == null) { + getErrPrintWriter().println("Error: no notificationType specified, use one of: " + + NOTIFICATION_TYPES); + return 1; + } + + switch(notificationType) { + case "on-hotplug-error": + mService.getDisplayNotificationManager().onHotplugConnectionError(); + break; + case "on-link-training-failure": + mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure(); + break; + case "on-cable-dp-incapable": + mService.getDisplayNotificationManager().onCableNotCapableDisplayPort(); + break; + default: + getErrPrintWriter().println( + "Error: unexpected notification type=" + notificationType + ", use one of: " + + NOTIFICATION_TYPES); + return 1; + } + + return 0; + } + + private int cancelNotifications() { + mService.getDisplayNotificationManager().cancelNotifications(); + return 0; + } + private int setBrightness() { String brightnessText = getNextArg(); if (brightnessText == null) { diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 0a1f316ac059..e5d38cb669d4 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -22,9 +22,8 @@ import static android.view.Display.Mode.INVALID_MODE_ID; import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; -import android.hardware.display.DisplayManagerInternal; -import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; +import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.sidekick.SidekickInternal; import android.os.Build; import android.os.Handler; @@ -52,6 +51,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -86,18 +86,25 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final DisplayManagerFlags mFlags; + private final DisplayNotificationManager mDisplayNotificationManager; + private Context mOverlayContext; // Called with SyncRoot lock held. LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, - Handler handler, Listener listener, DisplayManagerFlags flags) { - this(syncRoot, context, handler, listener, flags, new Injector()); + Handler handler, Listener listener, DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { + this(syncRoot, context, handler, listener, flags, displayNotificationManager, + new Injector()); } @VisibleForTesting LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, - Listener listener, DisplayManagerFlags flags, Injector injector) { + Listener listener, DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager, + Injector injector) { super(syncRoot, context, handler, listener, TAG); + mDisplayNotificationManager = displayNotificationManager; mInjector = injector; mSurfaceControlProxy = mInjector.getSurfaceControlProxy(); mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport(); @@ -1454,6 +1461,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { + "timestampNanos=" + timestampNanos + ", connectionError=" + connectionError + ")"); } + + mDisplayNotificationManager.onHotplugConnectionError(); } @Override diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index d8831fac5c79..03b0cfc3d844 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -78,6 +78,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; @@ -148,10 +149,12 @@ public final class ColorDisplayService extends SystemService { 1f, 1f, 1f, 1f }; + private final DisplayManagerFlags mDisplayManagerFlags = new DisplayManagerFlags(); + @VisibleForTesting final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController( - LocalServices.getService(DisplayManagerInternal.class)); + LocalServices.getService(DisplayManagerInternal.class), mDisplayManagerFlags); private final NightDisplayTintController mNightDisplayTintController = new NightDisplayTintController(); private final TintController mGlobalSaturationTintController = @@ -716,15 +719,16 @@ public final class ColorDisplayService extends SystemService { tintController.computeMatrixForCct(to)); tintController.setAppliedCct(to); } else { + final long duration = tintController.getTransitionDurationMilliseconds(to > from); Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct=" - + to + " fromCct=" + from); + + to + " fromCct=" + from + " with duration=" + duration); ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to); tintController.setAnimator(valueAnimator); final CctEvaluator evaluator = tintController.getEvaluator(); if (evaluator != null) { valueAnimator.setEvaluator(evaluator); } - valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds()); + valueAnimator.setDuration(duration); valueAnimator.setInterpolator(AnimationUtils.loadInterpolator( getContext(), android.R.interpolator.linear)); valueAnimator.addUpdateListener((ValueAnimator animator) -> { diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java index bf0139f347f8..b9fd1d070cbf 100644 --- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java +++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java @@ -34,6 +34,7 @@ import android.view.SurfaceControl.DisplayPrimaries; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -65,6 +66,8 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro boolean mSetUp = false; private final float[] mMatrixDisplayWhiteBalance = new float[16]; private long mTransitionDuration; + private long mTransitionDurationIncrease; + private long mTransitionDurationDecrease; private Boolean mIsAvailable; // This feature becomes disallowed if the device is in an unsupported strong/light state. private boolean mIsAllowed = true; @@ -74,8 +77,12 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro private final DisplayManagerInternal mDisplayManagerInternal; - DisplayWhiteBalanceTintController(DisplayManagerInternal dm) { + private final DisplayManagerFlags mDisplayManagerFlags; + + DisplayWhiteBalanceTintController(DisplayManagerInternal dm, + DisplayManagerFlags displayManagerFlags) { mDisplayManagerInternal = dm; + mDisplayManagerFlags = displayManagerFlags; } @Override @@ -139,6 +146,16 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro mTransitionDuration = res.getInteger( R.integer.config_displayWhiteBalanceTransitionTime); + if (!mDisplayManagerFlags.isAdaptiveTone2Enabled()) { + mTransitionDurationDecrease = mTransitionDuration; + mTransitionDurationIncrease = mTransitionDuration; + } else { + mTransitionDurationIncrease = res.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeIncrease); + mTransitionDurationDecrease = res.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeDecrease); + } + int[] cctRangeMinimums = res.getIntArray( R.array.config_displayWhiteBalanceDisplayRangeMinimums); int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps); @@ -323,6 +340,11 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro } @Override + public long getTransitionDurationMilliseconds(boolean isIncreasing) { + return isIncreasing ? mTransitionDurationIncrease : mTransitionDurationDecrease; + } + + @Override public void dump(PrintWriter pw) { synchronized (mLock) { pw.println(" mSetUp = " + mSetUp); @@ -348,6 +370,9 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro pw.println(" mMatrixDisplayWhiteBalance = " + matrixToString(mMatrixDisplayWhiteBalance, 4)); pw.println(" mIsAllowed = " + mIsAllowed); + pw.println(" mTransitionDuration = " + mTransitionDuration); + pw.println(" mTransitionDurationIncrease = " + mTransitionDurationIncrease); + pw.println(" mTransitionDurationDecrease = " + mTransitionDurationDecrease); } } diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java index 384333a574b5..716661dd6c3c 100644 --- a/services/core/java/com/android/server/display/color/TintController.java +++ b/services/core/java/com/android/server/display/color/TintController.java @@ -75,6 +75,10 @@ abstract class TintController { return TRANSITION_DURATION; } + public long getTransitionDurationMilliseconds(boolean direction) { + return TRANSITION_DURATION; + } + /** * Dump debug information. */ diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index e66fa5b3d516..7050c5a4168f 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -47,6 +47,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1, Flags::enableAdaptiveToneImprovements1); + private final FlagState mAdaptiveToneImprovements2 = new FlagState( + Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_2, + Flags::enableAdaptiveToneImprovements2); + private final FlagState mDisplayOffloadFlagState = new FlagState( Flags.FLAG_ENABLE_DISPLAY_OFFLOAD, Flags::enableDisplayOffload); @@ -55,6 +59,14 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY, Flags::enableModeLimitForExternalDisplay); + private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState( + Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING, + Flags::enableConnectedDisplayErrorHandling); + + private final FlagState mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState = new FlagState( + Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE, + Flags::backUpSmoothDisplayAndForcePeakRefreshRate); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -76,6 +88,13 @@ public class DisplayManagerFlags { return mAdaptiveToneImprovements1.isEnabled(); } + /** + * Returns whether adaptive tone improvements are enabled + */ + public boolean isAdaptiveTone2Enabled() { + return mAdaptiveToneImprovements2.isEnabled(); + } + /** Returns whether resolution range voting feature is enabled or not. */ public boolean isDisplayResolutionRangeVotingEnabled() { return isExternalDisplayLimitModeEnabled(); @@ -108,6 +127,15 @@ public class DisplayManagerFlags { return mDisplayOffloadFlagState.isEnabled(); } + /** Returns whether error notifications for connected displays are enabled on not */ + public boolean isConnectedDisplayErrorHandlingEnabled() { + return mConnectedDisplayErrorHandlingFlagState.isEnabled(); + } + + public boolean isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled() { + return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled(); + } + private static class FlagState { private final String mName; @@ -121,7 +149,6 @@ public class DisplayManagerFlags { mFlagFunction = flagFunction; } - // TODO(b/297159910): Simplify using READ-ONLY flags when available. private boolean isEnabled() { if (mEnabledSet) { if (DEBUG) { @@ -138,14 +165,7 @@ public class DisplayManagerFlags { } private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) { - boolean flagValue = false; - try { - flagValue = flagFunction.get(); - } catch (Throwable ex) { - if (DEBUG) { - Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex); - } - } + boolean flagValue = flagFunction.get(); // TODO(b/299462337) Remove when the infrastructure is ready. if (Build.IS_ENG || Build.IS_USERDEBUG) { return SystemProperties.getBoolean("persist.sys." + flagName + "-override", diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 542f26cbec69..a85e10dcfe2e 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -35,6 +35,14 @@ flag { } flag { + name: "enable_adaptive_tone_improvements_2" + namespace: "display_manager" + description: "Feature flag for Further Adaptive Tone Improvements" + bug: "294762632" + is_fixed_read_only: true +} + +flag { name: "enable_display_resolution_range_voting" namespace: "display_manager" description: "Feature flag to enable voting for ranges of resolutions" @@ -72,3 +80,19 @@ flag { bug: "299521647" is_fixed_read_only: true } + +flag { + name: "enable_connected_display_error_handling" + namespace: "display_manager" + description: "Feature flag for connected display error handling" + bug: "283461472" + is_fixed_read_only: true +} + +flag { + name: "back_up_smooth_display_and_force_peak_refresh_rate" + namespace: "display_manager" + description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate" + bug: "211737588" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 71ea8cc30405..ca23844044ca 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -22,6 +22,7 @@ import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT; import static android.view.Display.Mode.INVALID_MODE_ID; import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; +import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay; import android.annotation.IntegerRes; import android.annotation.NonNull; @@ -176,6 +177,8 @@ public class DisplayModeDirector { private final boolean mIsDisplaysRefreshRatesSynchronizationEnabled; + private final boolean mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled; + public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, @NonNull DisplayManagerFlags displayManagerFlags) { this(context, handler, new RealInjector(context), displayManagerFlags); @@ -191,6 +194,8 @@ public class DisplayModeDirector { .isExternalDisplayLimitModeEnabled(); mIsDisplaysRefreshRatesSynchronizationEnabled = displayManagerFlags .isDisplaysRefreshRatesSynchronizationEnabled(); + mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags + .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled(); mContext = context; mHandler = new DisplayModeDirectorHandler(handler.getLooper()); mInjector = injector; @@ -1193,8 +1198,7 @@ public class DisplayModeDirector { public void observe() { final ContentResolver cr = mContext.getContentResolver(); mInjector.registerPeakRefreshRateObserver(cr, this); - cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this, - UserHandle.USER_SYSTEM); + mInjector.registerMinRefreshRateObserver(cr, this); cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM); cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, @@ -1292,10 +1296,34 @@ public class DisplayModeDirector { private void updateRefreshRateSettingLocked() { final ContentResolver cr = mContext.getContentResolver(); + float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext); + float minRefreshRate = Settings.System.getFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId()); + if (Float.isInfinite(minRefreshRate)) { + // Infinity means that we want the highest possible refresh rate + minRefreshRate = highestRefreshRate; + + if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) { + // The flag had been turned off, we need to restore the original value + Settings.System.putFloatForUser(cr, + Settings.System.MIN_REFRESH_RATE, minRefreshRate, cr.getUserId()); + } + } + float peakRefreshRate = Settings.System.getFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId()); + if (Float.isInfinite(peakRefreshRate)) { + // Infinity means that we want the highest possible refresh rate + peakRefreshRate = highestRefreshRate; + + if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) { + // The flag had been turned off, we need to restore the original value + Settings.System.putFloatForUser(cr, + Settings.System.PEAK_REFRESH_RATE, peakRefreshRate, cr.getUserId()); + } + } + updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate); } @@ -3082,6 +3110,7 @@ public class DisplayModeDirector { interface Injector { Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE); + Uri MIN_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE); @NonNull DeviceConfigInterface getDeviceConfig(); @@ -3089,6 +3118,9 @@ public class DisplayModeDirector { void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer); + void registerMinRefreshRateObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer); + void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, Handler handler); @@ -3140,6 +3172,13 @@ public class DisplayModeDirector { } @Override + public void registerMinRefreshRateObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + cr.registerContentObserver(MIN_REFRESH_RATE_URI, false /*notifyDescendants*/, + observer, UserHandle.USER_SYSTEM); + } + + @Override public void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) { getDisplayManager().registerDisplayListener(listener, handler); diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java new file mode 100644 index 000000000000..f683e8104889 --- /dev/null +++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java @@ -0,0 +1,132 @@ +/* + * 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.notifications; + +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED; +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE; +import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.usb.DisplayPortAltModeInfo; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; + +/** + * Detects usb issues related to an external display connected. + */ +public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener { + private static final String TAG = "ConnectedDisplayUsbErrorsDetector"; + + /** + * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}. + */ + public interface Injector { + + /** + * @return {@link UsbManager} service. + */ + UsbManager getUsbManager(); + } + + /** + * USB errors listener + */ + public interface Listener { + + /** + * Link training failure callback. + */ + void onDisplayPortLinkTrainingFailure(); + + /** + * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort. + */ + void onCableNotCapableDisplayPort(); + } + + private Listener mListener; + private final Injector mInjector; + private final Context mContext; + private final boolean mIsConnectedDisplayErrorHandlingEnabled; + + ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags, + @NonNull final Context context) { + this(flags, context, () -> context.getSystemService(UsbManager.class)); + } + + @VisibleForTesting + ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags, + @NonNull final Context context, @NonNull final Injector injector) { + mContext = context; + mInjector = injector; + mIsConnectedDisplayErrorHandlingEnabled = + flags.isConnectedDisplayErrorHandlingEnabled(); + } + + /** Register listener for usb error events. */ + @SuppressLint("AndroidFrameworkRequiresPermission") + void registerListener(final Listener listener) { + if (!mIsConnectedDisplayErrorHandlingEnabled) { + return; + } + + final var usbManager = mInjector.getUsbManager(); + if (usbManager == null) { + Slog.e(TAG, "UsbManager is null"); + return; + } + + mListener = listener; + + try { + usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this); + } catch (IllegalStateException e) { + Slog.e(TAG, "Failed to register listener", e); + } + } + + /** + * Callback upon changes in {@link DisplayPortAltModeInfo}. + * @param portId String describing the {@link android.hardware.usb.UsbPort} that was changed. + * @param info New {@link DisplayPortAltModeInfo} for the corresponding portId. + */ + @Override + public void onDisplayPortAltModeInfoChanged(@NonNull String portId, + @NonNull DisplayPortAltModeInfo info) { + if (mListener == null) { + return; + } + + if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus() + && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus() + ) { + mListener.onCableNotCapableDisplayPort(); + return; + } + + if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) { + mListener.onDisplayPortLinkTrainingFailure(); + return; + } + } +} diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java new file mode 100644 index 000000000000..5cdef38cd45c --- /dev/null +++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java @@ -0,0 +1,205 @@ +/* + * 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.notifications; + +import static android.app.Notification.COLOR_DEFAULT; +import static com.android.internal.notification.SystemNotificationChannels.ALERTS; + +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Resources; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; + +/** + * Manages notifications for {@link com.android.server.display.DisplayManagerService}. + */ +public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener { + /** Dependency injection interface for {@link DisplayNotificationManager} */ + public interface Injector { + /** Get {@link NotificationManager} service or null if not available. */ + @Nullable + NotificationManager getNotificationManager(); + + /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */ + @Nullable + ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector(); + } + + private static final String TAG = "DisplayNotificationManager"; + private static final String NOTIFICATION_GROUP_NAME = TAG; + private static final String DISPLAY_NOTIFICATION_TAG = TAG; + private static final int DISPLAY_NOTIFICATION_ID = 1; + private static final long NOTIFICATION_TIMEOUT_MILLISEC = 30000L; + + private final Injector mInjector; + private final Context mContext; + private final boolean mConnectedDisplayErrorHandlingEnabled; + private NotificationManager mNotificationManager; + private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector; + + public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) { + this(flags, context, new Injector() { + @Nullable + @Override + public NotificationManager getNotificationManager() { + return context.getSystemService(NotificationManager.class); + } + + @Nullable + @Override + public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() { + return new ConnectedDisplayUsbErrorsDetector(flags, context); + } + }); + } + + @VisibleForTesting + DisplayNotificationManager(final DisplayManagerFlags flags, final Context context, + final Injector injector) { + mConnectedDisplayErrorHandlingEnabled = flags.isConnectedDisplayErrorHandlingEnabled(); + mContext = context; + mInjector = injector; + } + + /** + * Initialize services, which may be not yet published during boot. + * see {@link android.os.ServiceManager.ServiceNotFoundException}. + */ + public void onBootCompleted() { + mNotificationManager = mInjector.getNotificationManager(); + if (mNotificationManager == null) { + Slog.e(TAG, "onBootCompleted: NotificationManager is null"); + return; + } + + mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector(); + if (mConnectedDisplayUsbErrorsDetector != null) { + mConnectedDisplayUsbErrorsDetector.registerListener(this); + } + } + + /** + * Display error notification upon DisplayPort link training failure. + */ + @Override + public void onDisplayPortLinkTrainingFailure() { + if (!mConnectedDisplayErrorHandlingEnabled) { + Slog.d(TAG, "onDisplayPortLinkTrainingFailure:" + + " mConnectedDisplayErrorHandlingEnabled is false"); + return; + } + + sendErrorNotification(createErrorNotification( + R.string.connected_display_unavailable_notification_title, + R.string.connected_display_unavailable_notification_content)); + } + + /** + * Display error notification upon cable not capable of DisplayPort connected to a device + * capable of DisplayPort. + */ + @Override + public void onCableNotCapableDisplayPort() { + if (!mConnectedDisplayErrorHandlingEnabled) { + Slog.d(TAG, "onCableNotCapableDisplayPort:" + + " mConnectedDisplayErrorHandlingEnabled is false"); + return; + } + + sendErrorNotification(createErrorNotification( + R.string.connected_display_cable_dont_support_displays_notification_title, + R.string.connected_display_cable_dont_support_displays_notification_content)); + } + + /** + * Send notification about hotplug connection error. + */ + public void onHotplugConnectionError() { + if (!mConnectedDisplayErrorHandlingEnabled) { + Slog.d(TAG, "onHotplugConnectionError:" + + " mConnectedDisplayErrorHandlingEnabled is false"); + return; + } + + sendErrorNotification(createErrorNotification( + R.string.connected_display_unavailable_notification_title, + R.string.connected_display_unavailable_notification_content)); + } + + /** + * Cancel sent notifications. + */ + public void cancelNotifications() { + if (mNotificationManager == null) { + Slog.e(TAG, "Can't cancelNotifications: NotificationManager is null"); + return; + } + + mNotificationManager.cancel(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID); + } + + /** + * Send generic error notification. + */ + @SuppressLint("AndroidFrameworkRequiresPermission") + private void sendErrorNotification(final Notification notification) { + if (mNotificationManager == null) { + Slog.e(TAG, "Can't sendErrorNotification: NotificationManager is null"); + return; + } + + mNotificationManager.notify(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID, + notification); + } + + /** + * @return a newly built notification about an issue with connected display. + */ + private Notification createErrorNotification(final int titleId, final int messageId) { + final Resources resources = mContext.getResources(); + final CharSequence title = resources.getText(titleId); + final CharSequence message = resources.getText(messageId); + + int color = COLOR_DEFAULT; + try (var attrs = mContext.obtainStyledAttributes(new int[]{R.attr.colorError})) { + color = attrs.getColor(0, color); + } catch (Resources.NotFoundException e) { + Slog.e(TAG, "colorError attribute is not found: " + e.getMessage()); + } + + return new Notification.Builder(mContext, ALERTS) + .setGroup(NOTIFICATION_GROUP_NAME) + .setSmallIcon(R.drawable.usb_cable_unknown_issue) + .setWhen(0) + .setTimeoutAfter(NOTIFICATION_TIMEOUT_MILLISEC) + .setOngoing(false) + .setTicker(title) + .setColor(color) + .setContentTitle(title) + .setContentText(message) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + } +} diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index c6a50ed2580d..7b844a099841 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -48,6 +48,7 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; +import com.android.text.flags.Flags; import java.io.File; import java.io.FileDescriptor; @@ -240,21 +241,35 @@ public final class FontManagerService extends IFontManager.Stub { mContext = context; mIsSafeMode = safeMode; - SystemServerInitThreadPool.submit(() -> { - initialize(); - - // Set system font map only if there is updatable font directory. - // If there is no updatable font directory, `initialize` will have already loaded the - // system font map, so there's no need to set the system font map again here. - if (mUpdatableFontDir != null) { - try { - Typeface.setSystemFontMap(getCurrentFontMap()); - } catch (IOException | ErrnoException e) { - Slog.w(TAG, "Failed to set system font map of system_server"); + if (Flags.useOptimizedBoottimeFontLoading()) { + Slog.i(TAG, "Using optimized boot-time font loading."); + SystemServerInitThreadPool.submit(() -> { + initialize(); + + // Set system font map only if there is updatable font directory. + // If there is no updatable font directory, `initialize` will have already loaded + // the system font map, so there's no need to set the system font map again here. + synchronized (mUpdatableFontDirLock) { + if (mUpdatableFontDir != null) { + setSystemFontMap(); + } } - } + serviceStarted.complete(null); + }, "FontManagerService_create"); + } else { + Slog.i(TAG, "Not using optimized boot-time font loading."); + initialize(); + setSystemFontMap(); serviceStarted.complete(null); - }, "FontManagerService_create"); + } + } + + private void setSystemFontMap() { + try { + Typeface.setSystemFontMap(getCurrentFontMap()); + } catch (IOException | ErrnoException e) { + Slog.w(TAG, "Failed to set system font map of system_server"); + } } @Nullable @@ -291,9 +306,11 @@ public final class FontManagerService extends IFontManager.Stub { synchronized (mUpdatableFontDirLock) { mUpdatableFontDir = createUpdatableFontDir(); if (mUpdatableFontDir == null) { - // If fs-verity is not supported, load preinstalled system font map and use it for - // all apps. - Typeface.loadPreinstalledSystemFontMap(); + if (Flags.useOptimizedBoottimeFontLoading()) { + // If fs-verity is not supported, load preinstalled system font map and use it + // for all apps. + Typeface.loadPreinstalledSystemFontMap(); + } setSerializedFontMap(serializeSystemServerFontMap()); return; } 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 cd3d2f031455..0f40ca082663 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -619,8 +619,8 @@ final class UpdatableFontDir { } return new FontConfig( - config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis, - mConfigVersion); + config.getFontFamilies(), config.getAliases(), mergedFamilies, + config.getLocaleFallbackCustomizations(), mLastModifiedMillis, mConfigVersion); } private PersistentSystemFontConfig.Config readPersistentConfig() { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 429db5eeed49..c28a68b70d1c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4951,6 +4951,11 @@ public class HdmiControlService extends SystemService { AudioDeviceAttributes attributes = new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "", new ArrayList<AudioProfile>(), audioDescriptors); + // Set SAM to ON whenever CEC is disabled. Failure to do so may result in the absence + // of sound when CEC is disabled and eARC is enabled due to SAM being in the off state. + if (!isCecControlEnabled()) { + setSystemAudioActivated(true); + } getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); } diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index bad6bf0f0141..8580b9664075 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -1246,11 +1246,17 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { isFirstConfiguration); for (int i = 0; i < imeInfoList.size(); i++) { KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i); - boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null; - configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, - noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor), - noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT - : layoutInfo.mSelectionCriteria); + String layoutName = null; + int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT; + if (layoutInfo != null && layoutInfo.mDescriptor != null) { + layoutSelectionCriteria = layoutInfo.mSelectionCriteria; + KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor); + if (d != null) { + layoutName = d.keyboardLayoutName; + } + } + configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, layoutName, + layoutSelectionCriteria); } KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build()); } diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java index 08e597701ea2..2dd2a16bed64 100644 --- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java +++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java @@ -491,7 +491,7 @@ public final class KeyboardMetricsCollector { private final InputDevice mInputDevice; private boolean mIsFirstConfiguration; private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>(); - private final List<KeyboardLayout> mSelectedLayoutList = new ArrayList<>(); + private final List<String> mSelectedLayoutList = new ArrayList<>(); private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>(); public Builder(@NonNull InputDevice inputDevice) { @@ -511,7 +511,7 @@ public final class KeyboardMetricsCollector { * Adds keyboard layout configuration info for a particular IME subtype language */ public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype, - @Nullable KeyboardLayout selectedLayout, + @Nullable String selectedLayout, @LayoutSelectionCriteria int layoutSelectionCriteria) { Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null"); if (!isValidSelectionCriteria(layoutSelectionCriteria)) { @@ -533,7 +533,6 @@ public final class KeyboardMetricsCollector { } List<LayoutConfiguration> configurationList = new ArrayList<>(); for (int i = 0; i < size; i++) { - KeyboardLayout selectedLayout = mSelectedLayoutList.get(i); @LayoutSelectionCriteria int layoutSelectionCriteria = mLayoutSelectionCriteriaList.get(i); InputMethodSubtype imeSubtype = mImeSubtypeList.get(i); @@ -552,9 +551,9 @@ public final class KeyboardMetricsCollector { imeSubtype.getPhysicalKeyboardHintLayoutType()); // Sanitize null values - String keyboardLayoutName = - selectedLayout == null ? DEFAULT_LAYOUT_NAME - : selectedLayout.getLabel(); + String keyboardLayoutName = mSelectedLayoutList.get(i) == null + ? DEFAULT_LAYOUT_NAME + : mSelectedLayoutList.get(i); configurationList.add( new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag, diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index d4578dc1f74a..4851a81d3b69 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -486,9 +486,12 @@ public class LocaleManagerService extends SystemService { Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (!TextUtils.isEmpty(currentInputMethod)) { - String inputMethodPkgName = ComponentName - .unflattenFromString(currentInputMethod) - .getPackageName(); + ComponentName componentName = ComponentName.unflattenFromString(currentInputMethod); + if (componentName == null) { + Slog.d(TAG, "inValid input method"); + return false; + } + String inputMethodPkgName = componentName.getPackageName(); int inputMethodUid = getPackageUid(inputMethodPkgName, userId); return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), inputMethodUid); diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java index b9c9bae8e62b..5d5d59bcb6ef 100644 --- a/services/core/java/com/android/server/media/AudioAttributesUtils.java +++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java @@ -48,6 +48,8 @@ import android.media.MediaRoute2Info; case AudioDeviceInfo.TYPE_DOCK_ANALOG: return MediaRoute2Info.TYPE_DOCK; case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_HDMI_EARC: return MediaRoute2Info.TYPE_HDMI; case AudioDeviceInfo.TYPE_USB_DEVICE: return MediaRoute2Info.TYPE_USB_DEVICE; @@ -81,6 +83,8 @@ import android.media.MediaRoute2Info; case AudioDeviceInfo.TYPE_DOCK: case AudioDeviceInfo.TYPE_DOCK_ANALOG: case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_HDMI_EARC: case AudioDeviceInfo.TYPE_USB_DEVICE: return true; default: diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index c9528d8257c4..9dec1dff4cf0 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -63,6 +63,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.media.flags.Flags; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; @@ -161,11 +162,13 @@ class MediaRouter2ServiceImpl { mPowerManager = mContext.getSystemService(PowerManager.class); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); - IntentFilter screenOnOffIntentFilter = new IntentFilter(); - screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON); - screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF); + if (!Flags.disableScreenOffBroadcastReceiver()) { + IntentFilter screenOnOffIntentFilter = new IntentFilter(); + screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON); + screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF); + mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter); + } - mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter); mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged); MediaFeatureFlagManager.getInstance() @@ -2779,7 +2782,8 @@ class MediaRouter2ServiceImpl { List<ManagerRecord> managerRecords = getManagerRecords(); boolean isManagerScanning = false; - if (service.mPowerManager.isInteractive()) { + if (Flags.disableScreenOffBroadcastReceiver() + || service.mPowerManager.isInteractive()) { isManagerScanning = managerRecords.stream().anyMatch(manager -> manager.mIsScanning && service.mActivityManager .getPackageImportance(manager.mOwnerPackageName) diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 95ca08cc7fe9..a158b18d91b4 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -97,20 +97,23 @@ import java.util.concurrent.CopyOnWriteArrayList; public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl { /** - * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link - * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid - * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and - * above. For apps targeting Android T and below, the request will be ignored. + * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver( + * android.content.ComponentName)} throws an {@link + * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName} + * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver} + * for apps targeting Android U and above. For apps targeting Android T and below, the request + * will be ignored. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L; /** - * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link - * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link - * android.app.Activity activity} for apps targeting Android V and above. For apps targeting - * Android U and below, the request will be ignored. + * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)} + * throws an {@link java.lang.IllegalArgumentException} if the provided + * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for + * apps targeting Android V and above. For apps targeting Android U and below, the request will + * be ignored. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) 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 13d166294603..8cbc368467bb 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -76,6 +76,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; @@ -134,6 +135,7 @@ public final class MediaProjectionManagerService extends SystemService private final MediaRouter mMediaRouter; private final MediaRouterCallback mMediaRouterCallback; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private MediaRouter.RouteInfo mMediaRouteInfo; @GuardedBy("mLock") @@ -160,6 +162,7 @@ public final class MediaProjectionManagerService extends SystemService mWmInternal = LocalServices.getService(WindowManagerInternal.class); mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); mMediaRouterCallback = new MediaRouterCallback(); + mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(); Watchdog.getInstance().addMonitor(this); } @@ -193,6 +196,10 @@ public final class MediaProjectionManagerService extends SystemService Looper createCallbackLooper() { return Looper.getMainLooper(); } + + MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { + return MediaProjectionMetricsLogger.getInstance(); + } } @Override @@ -372,6 +379,10 @@ public final class MediaProjectionManagerService extends SystemService if (mProjectionGrant != null) { // Cache the session details. mProjectionGrant.mSession = incomingSession; + mMediaProjectionMetricsLogger.notifyProjectionStateChange( + mProjectionGrant.uid, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession); } return true; @@ -818,6 +829,19 @@ public final class MediaProjectionManagerService extends SystemService } @Override // Binder call + @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public void notifyPermissionRequestStateChange(int hostUid, int state, + int sessionCreationSource) { + notifyPermissionRequestStateChange_enforcePermission(); + final long token = Binder.clearCallingIdentity(); + try { + mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java new file mode 100644 index 000000000000..f18ecad09c42 --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java @@ -0,0 +1,50 @@ +/* + * 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.media.projection; + + +import com.android.internal.util.FrameworkStatsLog; + +/** + * Class for emitting logs describing a MediaProjection session. + */ +public class MediaProjectionMetricsLogger { + private static MediaProjectionMetricsLogger sSingleton = null; + + public static MediaProjectionMetricsLogger getInstance() { + if (sSingleton == null) { + sSingleton = new MediaProjectionMetricsLogger(); + } + return sSingleton; + } + + void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) { + write(hostUid, state, sessionCreationSource); + } + + private void write(int hostUid, int state, int sessionCreationSource) { + FrameworkStatsLog.write( + /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, + /* session_id */ 123, + /* state */ state, + /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN, + /* host_uid */ hostUid, + /* target_uid */ -1, + /* time_since_last_active */ 0, + /* creation_source */ sessionCreationSource); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 75a0cf521a1d..6b7db2d8d071 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -38,6 +38,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioAttributes; @@ -48,6 +49,7 @@ import android.os.Binder; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.os.VibrationEffect; import android.provider.Settings; import android.telephony.PhoneStateListener; @@ -61,18 +63,21 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.EventLogTags; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; +import com.android.server.notification.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -87,15 +92,24 @@ public final class NotificationAttentionHelper { static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean( "debug.notification.interruptiveness", false); + private static final float DEFAULT_VOLUME = 1.0f; + // TODO (b/291899544): remove for release + private static final String POLITE_STRATEGY1 = "rule1"; + private static final String POLITE_STRATEGY2 = "rule2"; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0; + private final Context mContext; private final PackageManager mPackageManager; private final TelephonyManager mTelephonyManager; + private final UserManager mUm; private final NotificationManagerPrivate mNMP; private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; private AccessibilityManager mAccessibilityManager; private KeyguardManager mKeyguardManager; private AudioManager mAudioManager; - private final LightsManager mLightsManager; private final NotificationUsageStats mUsageStats; private final ZenModeHelper mZenModeHelper; @@ -126,17 +140,26 @@ public final class NotificationAttentionHelper { private final float mInCallNotificationVolume; private Binder mCallNotificationToken = null; + // Settings flags + private boolean mNotificationCooldownEnabled; + private boolean mNotificationCooldownForWorkEnabled; + private boolean mNotificationCooldownApplyToAll; + private boolean mNotificationCooldownVibrateUnlocked; + + private boolean mEnablePoliteNotificationsFeature; + private final PolitenessStrategy mStrategy; + private int mCurrentWorkProfileId = UserHandle.USER_NULL; public NotificationAttentionHelper(Context context, LightsManager lightsManager, AccessibilityManager accessibilityManager, PackageManager packageManager, - NotificationUsageStats usageStats, + UserManager userManager, NotificationUsageStats usageStats, NotificationManagerPrivate notificationManagerPrivate, ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) { mContext = context; mPackageManager = packageManager; mTelephonyManager = context.getSystemService(TelephonyManager.class); mAccessibilityManager = accessibilityManager; - mLightsManager = lightsManager; + mUm = userManager; mNMP = notificationManagerPrivate; mUsageStats = usageStats; mZenModeHelper = zenModeHelper; @@ -144,8 +167,8 @@ public final class NotificationAttentionHelper { mVibratorHelper = new VibratorHelper(context); - mNotificationLight = mLightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); - mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); + mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); + mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); Resources resources = context.getResources(); mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight); @@ -169,7 +192,39 @@ public final class NotificationAttentionHelper { .build(); mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume); + mEnablePoliteNotificationsFeature = Flags.politeNotifications(); + + if (mEnablePoliteNotificationsFeature) { + mStrategy = getPolitenessStrategy(); + } else { + mStrategy = null; + } + mSettingsObserver = new SettingsObserver(); + loadUserSettings(); + } + + private PolitenessStrategy getPolitenessStrategy() { + final String politenessStrategy = mFlagResolver.getStringValue( + NotificationFlags.NOTIF_COOLDOWN_RULE); + + if (POLITE_STRATEGY2.equals(politenessStrategy)) { + return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2)); + } else { + if (!POLITE_STRATEGY1.equals(politenessStrategy)) { + Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to " + + POLITE_STRATEGY1); + } + + return new Strategy1(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + } } public void onSystemReady() { @@ -202,11 +257,59 @@ public final class NotificationAttentionHelper { filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(Intent.ACTION_USER_SWITCHED); + filter.addAction(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); mContext.getContentResolver().registerContentObserver( SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver, UserHandle.USER_ALL); + if (mEnablePoliteNotificationsFeature) { + mContext.getContentResolver().registerContentObserver( + SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver, + UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + SettingsObserver.NOTIFICATION_COOLDOWN_ALL_URI, false, mSettingsObserver, + UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + SettingsObserver.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI, false, + mSettingsObserver, UserHandle.USER_ALL); + } + } + + private void loadUserSettings() { + if (mEnablePoliteNotificationsFeature) { + try { + mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser()); + + mNotificationCooldownEnabled = + Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED, UserHandle.USER_CURRENT) != 0; + if (mCurrentWorkProfileId != UserHandle.USER_NULL) { + mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK, mCurrentWorkProfileId) + != 0; + } else { + mNotificationCooldownForWorkEnabled = false; + } + mNotificationCooldownApplyToAll = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL, + UserHandle.USER_CURRENT) != 0; + mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + UserHandle.USER_CURRENT) != 0; + } catch (Exception e) { + Log.e(TAG, "Failed to read Settings: " + e); + } + } } @VisibleForTesting @@ -229,6 +332,10 @@ public final class NotificationAttentionHelper { Log.d(TAG, "buzzBeepBlinkLocked " + record); } + if (isPoliteNotificationFeatureEnabled(record)) { + mStrategy.onNotificationPosted(record); + } + // Should this notification make noise, vibe, or use the LED? final boolean aboveThreshold = mIsAutomotive @@ -269,6 +376,9 @@ public final class NotificationAttentionHelper { vibration = mVibratorHelper.createFallbackVibration(insistent); } hasValidVibrate = vibration != null; + // Vibration-only if unlocked and Settings flag set + boolean vibrateOnly = + hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent; boolean hasAudibleAlert = hasValidSound || hasValidVibrate; if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) { if (!sentAccessibilityEvent) { @@ -277,7 +387,7 @@ public final class NotificationAttentionHelper { } if (DEBUG) Slog.v(TAG, "Interrupting!"); boolean isInsistentUpdate = isInsistentUpdate(record); - if (hasValidSound) { + if (hasValidSound && !vibrateOnly) { if (isInsistentUpdate) { // don't reset insistent sound, it's jarring beep = true; @@ -301,7 +411,7 @@ public final class NotificationAttentionHelper { if (isInsistentUpdate) { buzz = true; } else { - buzz = playVibration(record, vibration, hasValidSound); + buzz = playVibration(record, vibration, hasValidSound && !vibrateOnly); if (buzz) { mVibrateNotificationKey = key; } @@ -341,9 +451,7 @@ public final class NotificationAttentionHelper { } else if (wasShowLights) { updateLightsLocked(); } - final int buzzBeepBlink = - (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0); - if (buzzBeepBlink > 0) { + if (buzz || beep || blink) { // Ignore summary updates because we don't display most of the information. if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) { if (DEBUG_INTERRUPTIVENESS) { @@ -362,15 +470,43 @@ public final class NotificationAttentionHelper { + record.getKey() + " is interruptive: alerted"); } } + } + final int buzzBeepBlinkLoggingCode = + (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record); + if (buzzBeepBlinkLoggingCode > 0) { MetricsLogger.action(record.getLogMaker() .setCategory(MetricsEvent.NOTIFICATION_ALERT) .setType(MetricsEvent.TYPE_OPEN) - .setSubtype(buzzBeepBlink)); - EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); + .setSubtype(buzzBeepBlinkLoggingCode)); + EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0, + getPolitenessState(record)); } record.setAudiblyAlerted(buzz || beep); + if (mEnablePoliteNotificationsFeature) { + // Update last alert time + if (buzz || beep) { + record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis()); + } + } + return buzzBeepBlinkLoggingCode; + } - return buzzBeepBlink; + private int getPoliteBit(final NotificationRecord record) { + switch (getPolitenessState(record)) { + case PolitenessStrategy.POLITE_STATE_POLITE: + return MetricsProto.MetricsEvent.ALERT_POLITE; + case PolitenessStrategy.POLITE_STATE_MUTED: + return MetricsProto.MetricsEvent.ALERT_MUTED; + default: + return 0; + } + } + + private int getPolitenessState(final NotificationRecord record) { + if (!isPoliteNotificationFeatureEnabled(record)) { + return PolitenessStrategy.POLITE_STATE_DEFAULT; + } + return mStrategy.getPolitenessState(record); } boolean isInsistentUpdate(final NotificationRecord record) { @@ -468,7 +604,7 @@ public final class NotificationAttentionHelper { + record.getAudioAttributes()); } player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes()); + record.getAudioAttributes(), getSoundVolume(record)); return true; } } catch (RemoteException e) { @@ -480,12 +616,56 @@ public final class NotificationAttentionHelper { return false; } + private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) { + // Check feature flag + if (!mEnablePoliteNotificationsFeature) { + return false; + } + + // The user can enable/disable notifications cooldown from the Settings app + if (!mNotificationCooldownEnabled) { + return false; + } + + // The user can enable/disable notifications cooldown for work profile from the Settings app + if (isNotificationForWorkProfile(record) && !mNotificationCooldownForWorkEnabled) { + return false; + } + + // The user can choose to apply cooldown for all apps/conversations only from the + // Settings app + if (!mNotificationCooldownApplyToAll && record.getChannel().getConversationId() == null) { + return false; + } + + return true; + } + + private float getSoundVolume(final NotificationRecord record) { + if (!isPoliteNotificationFeatureEnabled(record)) { + return DEFAULT_VOLUME; + } + + return mStrategy.getSoundVolume(record); + } + + private float getVibrationIntensity(final NotificationRecord record) { + if (!isPoliteNotificationFeatureEnabled(record)) { + return DEFAULT_VOLUME; + } + + return mStrategy.getVibrationIntensity(record); + } + private boolean playVibration(final NotificationRecord record, final VibrationEffect effect, boolean delayVibForSound) { // Escalate privileges so we can use the vibrator even if the // notifying app does not have the VIBRATE permission. final long identity = Binder.clearCallingIdentity(); try { + final float scale = getVibrationIntensity(record); + final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0 + ? mVibratorHelper.scale(effect, scale) : effect; if (delayVibForSound) { new Thread(() -> { // delay the vibration by the same amount as the notification sound @@ -503,7 +683,7 @@ public final class NotificationAttentionHelper { // so need to check that the notification is still valid for vibrate. if (mNMP.getNotificationByKey(record.getKey()) != null) { if (record.getKey().equals(mVibrateNotificationKey)) { - vibrate(record, effect, true); + vibrate(record, scaledEffect, true); } else { if (DEBUG) { Slog.v(TAG, "No vibration for notification " @@ -517,7 +697,7 @@ public final class NotificationAttentionHelper { } }).start(); } else { - vibrate(record, effect, false); + vibrate(record, scaledEffect, false); } return true; } finally { @@ -535,7 +715,7 @@ public final class NotificationAttentionHelper { } void playInCallNotification() { - // TODO: Should we apply politeness to mInCallNotificationVolume ? + // TODO b/270456865: Should we apply politeness to mInCallNotificationVolume ? final ContentResolver cr = mContext.getContentResolver(); if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL && Settings.Secure.getIntForUser(cr, @@ -760,6 +940,22 @@ public final class NotificationAttentionHelper { || signals.isCurrentProfile); } + private boolean isNotificationForWorkProfile(final NotificationRecord record) { + return (record.getUser().getIdentifier() == mCurrentWorkProfileId + && mCurrentWorkProfileId != UserHandle.USER_NULL); + } + + private int getManagedProfileId(int parentUserId) { + final List<UserInfo> profiles = mUm.getProfiles(parentUserId); + for (UserInfo profile : profiles) { + if (profile.isManagedProfile() + && profile.getUserHandle().getIdentifier() != parentUserId) { + return profile.getUserHandle().getIdentifier(); + } + } + return UserHandle.USER_NULL; + } + void sendAccessibilityEvent(NotificationRecord record) { if (!mAccessibilityManager.isEnabled()) { return; @@ -791,6 +987,16 @@ public final class NotificationAttentionHelper { mAccessibilityManager.sendAccessibilityEvent(event); } + /** + * Notify the attention helper of a user interaction with a notification + * @param record that was interacted with + */ + public void onUserInteraction(final NotificationRecord record) { + if (isPoliteNotificationFeatureEnabled(record)) { + mStrategy.onUserInteraction(record); + } + } + public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { pw.println("\n Notification attention state:"); pw.print(prefix); @@ -834,6 +1040,243 @@ public final class NotificationAttentionHelper { } } + abstract private static class PolitenessStrategy { + static final int POLITE_STATE_DEFAULT = 0; + static final int POLITE_STATE_POLITE = 1; + static final int POLITE_STATE_MUTED = 2; + + @IntDef(prefix = { "POLITE_STATE_" }, value = { + POLITE_STATE_DEFAULT, + POLITE_STATE_POLITE, + POLITE_STATE_MUTED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface PolitenessState {} + + protected final Map<String, Integer> mVolumeStates; + + // Cooldown timer for transitioning into polite state + protected final int mTimeoutPolite; + // Cooldown timer for transitioning into muted state + protected final int mTimeoutMuted; + // Volume for polite state + protected final float mVolumePolite; + // Volume for muted state + protected final float mVolumeMuted; + + public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, + int volumeMuted) { + mVolumeStates = new HashMap<>(); + + this.mTimeoutPolite = timeoutPolite; + this.mTimeoutMuted = timeoutMuted; + this.mVolumePolite = volumePolite / 100.0f; + this.mVolumeMuted = volumeMuted / 100.0f; + } + + abstract void onNotificationPosted(NotificationRecord record); + + String getChannelKey(final NotificationRecord record) { + // use conversationId if it's a conversation + String channelId = record.getChannel().getConversationId() != null + ? record.getChannel().getConversationId() : record.getChannel().getId(); + return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName() + + ":" + channelId; + } + + public float getSoundVolume(final NotificationRecord record) { + float volume = DEFAULT_VOLUME; + final String key = getChannelKey(record); + final @PolitenessState int volState = getPolitenessState(record); + + switch (volState) { + case POLITE_STATE_DEFAULT: + volume = DEFAULT_VOLUME; + break; + case POLITE_STATE_POLITE: + volume = mVolumePolite; + break; + case POLITE_STATE_MUTED: + volume = mVolumeMuted; + break; + default: + Log.w(TAG, "getSoundVolume unexpected volume state: " + volState); + break; + } + + if (DEBUG) { + Log.i(TAG, + "getSoundVolume state: " + volState + " vol: " + volume + " key: " + key); + } + + return volume; + } + + private float getVibrationIntensity(final NotificationRecord record) { + // TODO b/270456865: maybe use different scaling for vibration/sound ? + return getSoundVolume(record); + } + + public void onUserInteraction(final NotificationRecord record) { + final String key = getChannelKey(record); + // reset to default state after user interaction + mVolumeStates.put(key, POLITE_STATE_DEFAULT); + record.getChannel().setLastNotificationUpdateTimeMs(0); + } + + public final @PolitenessState int getPolitenessState(final NotificationRecord record) { + return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT); + } + } + + // TODO b/270456865: Only one of the two strategies will be released. + // The other one need to be removed + /** + * Polite notification strategy 1: + * - Transitions from default (loud) => polite (lower volume) state if a notification + * alerts the same channel before timeoutPolite. + * - Transitions from polite => muted state if a notification alerts the same channel + * before timeoutMuted OR transitions back to the default state if a notification alerts + * after timeoutPolite. + * - Transitions from muted => default state if the muted channel received more than maxPosted + * notifications OR transitions back to the polite state if a notification alerts + * after timeoutMuted. + * - Transitions back to the default state after a user interaction with a notification. + */ + public static class Strategy1 extends PolitenessStrategy { + // Keep track of the number of notifications posted per channel + private final Map<String, Integer> mNumPosted; + // Reset to default state if number of posted notifications exceed this value when muted + private final int mMaxPostedForReset; + + public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted, + int maxPosted) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + + mNumPosted = new HashMap<>(); + mMaxPostedForReset = maxPosted; + + if (DEBUG) { + Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted); + } + } + + @Override + public void onNotificationPosted(final NotificationRecord record) { + long timeSinceLastNotif = System.currentTimeMillis() + - record.getChannel().getLastNotificationUpdateTimeMs(); + + final String key = getChannelKey(record); + @PolitenessState int volState = getPolitenessState(record); + + int numPosted = mNumPosted.getOrDefault(key, 0) + 1; + mNumPosted.put(key, numPosted); + + switch (volState) { + case POLITE_STATE_DEFAULT: + if (timeSinceLastNotif < mTimeoutPolite) { + volState = POLITE_STATE_POLITE; + } + break; + case POLITE_STATE_POLITE: + if (timeSinceLastNotif < mTimeoutMuted) { + volState = POLITE_STATE_MUTED; + } else if (timeSinceLastNotif > mTimeoutPolite) { + volState = POLITE_STATE_DEFAULT; + } else { + volState = POLITE_STATE_POLITE; + } + break; + case POLITE_STATE_MUTED: + if (timeSinceLastNotif > mTimeoutMuted) { + volState = POLITE_STATE_POLITE; + } else { + volState = POLITE_STATE_MUTED; + } + if (numPosted >= mMaxPostedForReset) { + volState = POLITE_STATE_DEFAULT; + mNumPosted.put(key, 0); + } + break; + default: + Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState); + break; + } + + if (DEBUG) { + Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: " + + volState + " key: " + key + " numposted " + numPosted); + } + + mVolumeStates.put(key, volState); + } + + @Override + public void onUserInteraction(final NotificationRecord record) { + super.onUserInteraction(record); + mNumPosted.put(getChannelKey(record), 0); + } + } + + /** + * Polite notification strategy 2: + * - Transitions from default (loud) => muted state if a notification + * alerts the same channel before timeoutPolite. + * - Transitions from polite => default state if a notification + * alerts the same channel before timeoutMuted. + * - Transitions from muted => default state if a notification alerts after timeoutMuted, + * otherwise transitions to the polite state. + * - Transitions back to the default state after a user interaction with a notification. + */ + public static class Strategy2 extends PolitenessStrategy { + public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + + if (DEBUG) { + Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted); + } + } + + @Override + public void onNotificationPosted(final NotificationRecord record) { + long timeSinceLastNotif = System.currentTimeMillis() + - record.getChannel().getLastNotificationUpdateTimeMs(); + + final String key = getChannelKey(record); + @PolitenessState int volState = getPolitenessState(record); + + switch (volState) { + case POLITE_STATE_DEFAULT: + if (timeSinceLastNotif < mTimeoutPolite) { + volState = POLITE_STATE_MUTED; + } + break; + case POLITE_STATE_POLITE: + if (timeSinceLastNotif > mTimeoutMuted) { + volState = POLITE_STATE_DEFAULT; + } + break; + case POLITE_STATE_MUTED: + if (timeSinceLastNotif > mTimeoutMuted) { + volState = POLITE_STATE_DEFAULT; + } else { + volState = POLITE_STATE_POLITE; + } + break; + default: + Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState); + break; + } + + if (DEBUG) { + Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: " + + volState + " key: " + key); + } + + mVolumeStates.put(key, volState); + } + } + //====================== Observers ============================= private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -859,6 +1302,11 @@ public final class NotificationAttentionHelper { if (mNotificationLight != null) { mNotificationLight.turnOff(); } + } else if (action.equals(Intent.ACTION_USER_ADDED) + || action.equals(Intent.ACTION_USER_REMOVED) + || action.equals(Intent.ACTION_USER_SWITCHED) + || action.equals(Intent.ACTION_USER_UNLOCKED)) { + loadUserSettings(); } } }; @@ -867,6 +1315,12 @@ public final class NotificationAttentionHelper { private static final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor( Settings.System.NOTIFICATION_LIGHT_PULSE); + private static final Uri NOTIFICATION_COOLDOWN_ENABLED_URI = Settings.System.getUriFor( + Settings.System.NOTIFICATION_COOLDOWN_ENABLED); + private static final Uri NOTIFICATION_COOLDOWN_ALL_URI = Settings.System.getUriFor( + Settings.System.NOTIFICATION_COOLDOWN_ALL); + private static final Uri NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI = + Settings.System.getUriFor(Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED); public SettingsObserver() { super(null); } @@ -884,11 +1338,45 @@ public final class NotificationAttentionHelper { updateLightsLocked(); } } + if (mEnablePoliteNotificationsFeature) { + if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) { + mNotificationCooldownEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED, + UserHandle.USER_CURRENT) != 0; + + if (mCurrentWorkProfileId != UserHandle.USER_NULL) { + mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK, + mCurrentWorkProfileId) + != 0; + } else { + mNotificationCooldownForWorkEnabled = false; + } + } + if (NOTIFICATION_COOLDOWN_ALL_URI.equals(uri)) { + mNotificationCooldownApplyToAll = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, + DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT) + != 0; + } + if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) { + mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + UserHandle.USER_CURRENT) != 0; + } + } } } - //TODO: cleanup most (all?) of these + // TODO b/270456865: cleanup most (all?) of these //======================= FOR TESTS ===================== @VisibleForTesting void setIsAutomotive(boolean isAutomotive) { @@ -931,6 +1419,11 @@ public final class NotificationAttentionHelper { } @VisibleForTesting + void setUserPresent(boolean userPresent) { + mUserPresent = userPresent; + } + + @VisibleForTesting void setLights(LogicalLight light) { mNotificationLight = light; mAttentionLight = light; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 87c30674bfb7..837b76154363 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2532,7 +2532,7 @@ public class NotificationManagerService extends SystemService { if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager, - mAccessibilityManager, mPackageManagerClient, usageStats, + mAccessibilityManager, mPackageManagerClient, userManager, usageStats, mNotificationManagerPrivate, mZenModeHelper, flagResolver); } @@ -3369,6 +3369,10 @@ public class NotificationManagerService extends SystemService { mAppUsageStats.reportEvent(r.getSbn().getPackageName(), getRealUserId(r.getSbn().getUserId()), UsageEvents.Event.USER_INTERACTION); + + if (Flags.politeNotifications()) { + mAttentionHelper.onUserInteraction(r); + } } private int getRealUserId(int userId) { @@ -5730,13 +5734,18 @@ public class NotificationManagerService extends SystemService { public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId, boolean granted, boolean userSet) { Objects.requireNonNull(listener); + if (UserHandle.getCallingUserId() != userId) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + "setNotificationListenerAccessGrantedForUser for user " + userId); + } checkNotificationListenerAccess(); if (granted && listener.flattenToString().length() > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { throw new IllegalArgumentException( "Component name too long: " + listener.flattenToString()); } - if (!userSet && isNotificationListenerAccessUserSet(listener)) { + if (!userSet && isNotificationListenerAccessUserSet(listener, userId)) { // Don't override user's choice return; } @@ -5762,9 +5771,8 @@ public class NotificationManagerService extends SystemService { } } - private boolean isNotificationListenerAccessUserSet(ComponentName listener) { - return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), - getCallingUserHandle().getIdentifier()); + private boolean isNotificationListenerAccessUserSet(ComponentName listener, int userId) { + return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), userId); } @Override @@ -8612,7 +8620,7 @@ public class NotificationManagerService extends SystemService { .setCategory(MetricsEvent.NOTIFICATION_ALERT) .setType(MetricsEvent.TYPE_OPEN) .setSubtype(buzzBeepBlink)); - EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); + EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0, 0); } record.setAudiblyAlerted(buzz || beep); return buzzBeepBlink; @@ -8757,7 +8765,7 @@ public class NotificationManagerService extends SystemService { if (DBG) Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + record.getAudioAttributes()); player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes()); + record.getAudioAttributes(), 1.0f); return true; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index e5d07bc152c0..7204d05fdce7 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -50,6 +50,7 @@ public final class VibratorHelper { private final long[] mFallbackPattern; @Nullable private final float[] mDefaultPwlePattern; @Nullable private final float[] mFallbackPwlePattern; + private final int mDefaultVibrationAmplitude; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); @@ -65,6 +66,8 @@ public final class VibratorHelper { com.android.internal.R.array.config_defaultNotificationVibeWaveform); mFallbackPwlePattern = getFloatArray(context.getResources(), com.android.internal.R.array.config_notificationFallbackVibeWaveform); + mDefaultVibrationAmplitude = context.getResources().getInteger( + com.android.internal.R.integer.config_defaultVibrationAmplitude); } /** @@ -136,6 +139,14 @@ public final class VibratorHelper { } /** + * Scale vibration effect, valid range is [0.0f, 1.0f] + * Resolves default amplitude value if not already set. + */ + public VibrationEffect scale(VibrationEffect effect, float scale) { + return effect.resolve(mDefaultVibrationAmplitude).scale(scale); + } + + /** * Vibrate the device with given {@code effect}. * * <p>We need to vibrate as "android" so we can breakthrough DND. diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index f9876299e8e0..79cd2a0b236f 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -489,6 +489,9 @@ public interface Computer extends PackageDataSnapshot { boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId); + /** Check if the package is in a stopped state for a given user. */ + boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId); + boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId); @NonNull diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 5d2944e17943..7db7bf538c37 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -4938,7 +4938,7 @@ public class ComputerEngine implements Computer { int userId) { final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, - false /* checkShell */, "isPackageSuspendedForUser for user " + userId); + false /* checkShell */, "when asking about packages for user " + userId); final PackageStateInternal ps = mSettings.getPackage(packageName); if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) { throw new IllegalArgumentException("Unknown target package: " + packageName); @@ -4957,6 +4957,11 @@ public class ComputerEngine implements Computer { } @Override + public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) { + return getUserStageOrDefaultForUser(packageName, userId).isStopped(); + } + + @Override public boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId) { for (final PackageStateInternal packageState : getPackageStates().values()) { diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java index 76203ac7650d..9a0306b77c41 100644 --- a/services/core/java/com/android/server/pm/IPackageManagerBase.java +++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java @@ -961,6 +961,12 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { } @Override + public final boolean isPackageStoppedForUser(@NonNull String packageName, + @UserIdInt int userId) { + return snapshot().isPackageStoppedForUser(packageName, userId); + } + + @Override @Deprecated public final boolean isSafeMode() { // allow instant applications diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index c6388e7bede7..edb45aa0ffb4 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.content.pm.Flags.preventSdkLibApp; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; @@ -994,10 +995,11 @@ final class InstallPackageHelper { return; } final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0; - if (!isApex) { - createdAppId.put(packageName, optimisticallyRegisterAppId(request)); - } else { + final boolean isSdkLibrary = packageToScan.isSdkLibrary(); + if (isApex || (isSdkLibrary && preventSdkLibApp())) { request.getScannedPackageSetting().setAppId(Process.INVALID_UID); + } else { + createdAppId.put(packageName, optimisticallyRegisterAppId(request)); } versionInfos.put(packageName, mPm.getSettingsVersionForPackage(packageToScan)); diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 1c7024b7d239..7d822b50a293 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -34,6 +34,7 @@ import android.apex.ApexInfo; import android.app.AppOpsManager; import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; +import android.content.pm.Flags; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -922,7 +923,7 @@ final class InstallRequest { // Only report external profile warnings when installing from adb. The goal is to warn app // developers if they have provided bad external profiles, so it's not beneficial to report // those warnings in the normal app install workflow. - if (isInstallFromAdb()) { + if (isInstallFromAdb() && Flags.useArtServiceV2()) { var externalProfileErrors = new LinkedHashSet<String>(); for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { for (DexContainerFileDexoptResult fileResult : diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 11660a59afe6..a161e8c39ca2 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -107,19 +107,22 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.Preconditions; +import com.android.internal.util.SizedInputStream; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.ActivityTaskManagerInternal; +import java.io.DataInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.Arrays; @@ -130,6 +133,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.BiConsumer; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -216,6 +221,7 @@ public class LauncherAppsService extends SystemService { private final ShortcutChangeHandler mShortcutChangeHandler; private final Handler mCallbackHandler; + private final ExecutorService mOnDumpExecutor = Executors.newSingleThreadExecutor(); private PackageInstallerService mPackageInstallerService; @@ -1512,7 +1518,7 @@ public class LauncherAppsService extends SystemService { forEachViewCaptureWindow((fileName, is) -> { try { zipOs.putNextEntry(new ZipEntry("FS" + fileName)); - is.transferTo(zipOs); + transferViewCaptureData(is, zipOs); zipOs.closeEntry(); } catch (IOException e) { getErrPrintWriter().write("Failed to output " + fileName @@ -1553,8 +1559,9 @@ public class LauncherAppsService extends SystemService { private void dumpViewCaptureDataToWmTrace(@NonNull String fileName, @NonNull InputStream is) { Path outPath = Paths.get(fileName); - try { - Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING); + try (OutputStream os = Files.newOutputStream(outPath, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING)) { + transferViewCaptureData(is, os); Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS); } catch (IOException e) { Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e); @@ -1562,6 +1569,15 @@ public class LauncherAppsService extends SystemService { } /** + * Raw input stream reads hang on the final read when transferring data in via the pipe. + * The fix used below is to count and read the exact amount of bytes being sent. + */ + private void transferViewCaptureData(InputStream is, OutputStream os) throws IOException { + DataInputStream dataInputStream = new DataInputStream(is); + new SizedInputStream(dataInputStream, dataInputStream.readInt()).transferTo(os); + } + + /** * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data * to LauncherAppsService via the pipe's input provided. This data (as well as an output * file name) is provided to the consumer via an InputStream to output where it wants (for @@ -1569,24 +1585,37 @@ public class LauncherAppsService extends SystemService { */ private void forEachViewCaptureWindow( @NonNull BiConsumer<String, InputStream> outputtingConsumer) { - for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) { - String packageName = (String) mDumpCallbacks.getBroadcastCookie(i); - String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX; - - try { - // Order is important here. OnDump needs to be called before the BiConsumer - // accepts & starts blocking on reading the input stream. - ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); - mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]); - - InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]); - outputtingConsumer.accept(fileName, is); - is.close(); - } catch (Exception e) { - Log.d(TAG, "failed to pipe view capture data", e); - } + try { + // This multi-threading prevents ctrl-C command line command aborting from putting + // the mDumpCallbacks RemoteCallbackList in a bad Broadcast state. We need to wait + // for it to complete even though it is on a background thread. + mOnDumpExecutor.submit(() -> { + try { + for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) { + String packageName = (String) mDumpCallbacks.getBroadcastCookie(i); + String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX; + + try { + // Order is important here. OnDump needs to be called before the + // BiConsumer accepts & starts blocking on reading the input stream. + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]); + + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( + pipe[0]); + outputtingConsumer.accept(fileName, is); + is.close(); + } catch (Exception e) { + Log.d(TAG, "failed to pipe view capture data", e); + } + } + } finally { + mDumpCallbacks.finishBroadcast(); + } + }).get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "background work was interrupted", e); } - mDumpCallbacks.finishBroadcast(); } @RequiresPermission(READ_FRAME_BUFFER) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ddc8369738de..68aa93d28330 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4501,6 +4501,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService boolean stopped, @UserIdInt int userId) { if (!mUserManager.exists(userId)) return; final int callingUid = Binder.getCallingUid(); + boolean wasStopped = false; if (snapshot.getInstantAppPackageName(callingUid) == null) { final int permission = mContext.checkCallingOrSelfPermission( Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); @@ -4522,6 +4523,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService ? null : packageState.getUserStateOrDefault(userId); if (packageState != null && packageUserState.isStopped() != stopped) { boolean wasNotLaunched = packageUserState.isNotLaunched(); + wasStopped = packageUserState.isStopped(); commitPackageStateMutation(null, packageName, state -> { PackageUserStateWrite userState = state.userState(userId); userState.setStopped(stopped); @@ -4553,6 +4555,24 @@ public class PackageManagerService implements PackageSender, TestUtilityService ah.setHibernatingGlobally(packageName, false); } }); + // Send UNSTOPPED broadcast if necessary + if (wasStopped && Flags.stayStopped()) { + final PackageManagerInternal pmi = + mInjector.getLocalService(PackageManagerInternal.class); + final int [] userIds = resolveUserIds(userId); + final SparseArray<int[]> broadcastAllowList = + snapshotComputer().getVisibilityAllowLists(packageName, userIds); + final Bundle extras = new Bundle(); + extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId)); + extras.putInt(Intent.EXTRA_USER_HANDLE, userId); + mHandler.post(() -> { + mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED, + packageName, extras, + Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, + userIds, null, broadcastAllowList, null, + null); + }); + } } } @@ -6929,6 +6949,25 @@ public class PackageManagerService implements PackageSender, TestUtilityService public ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(int userId) { return mInstallerService.getHistoricalSessions(userId); } + + @Override + public void sendPackageRestartedBroadcast(@NonNull String packageName, + int uid, @Intent.Flags int flags) { + final int userId = UserHandle.getUserId(uid); + final int [] userIds = resolveUserIds(userId); + final SparseArray<int[]> broadcastAllowList = + snapshotComputer().getVisibilityAllowLists(packageName, userIds); + final Bundle extras = new Bundle(); + extras.putInt(Intent.EXTRA_UID, uid); + extras.putInt(Intent.EXTRA_USER_HANDLE, userId); + mHandler.post(() -> { + mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED, + packageName, extras, + flags, null, null, + userIds, null, broadcastAllowList, null, + null); + }); + } } private void setEnabledOverlayPackages(@UserIdInt int userId, diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 68a8e40d0528..0e98158d7210 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -75,13 +75,13 @@ import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.multiuser.Flags; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Environment; import android.os.FileUtils; -import android.os.Flags; import android.os.Handler; import android.os.IBinder; import android.os.IProgressListener; @@ -1559,7 +1559,7 @@ public class UserManagerService extends IUserManager.Stub { logQuietModeEnabled(userId, enableQuietMode, callingPackage); // Broadcast generic intents for all profiles - if (Flags.allowPrivateProfile()) { + if (android.os.Flags.allowPrivateProfile()) { broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(), enableQuietMode, false); } @@ -3785,6 +3785,8 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy({"mPackagesLock"}) private void readUserListLP() { + // Whether guest restrictions are present on userlist.xml + boolean guestRestrictionsArePresentOnUserListXml = false; try (ResilientAtomicFile file = getUserListFile()) { FileInputStream fin = null; try { @@ -3834,6 +3836,7 @@ public class UserManagerService extends IUserManager.Stub { } } } else if (name.equals(TAG_GUEST_RESTRICTIONS)) { + guestRestrictionsArePresentOnUserListXml = true; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.END_TAG) { if (type == XmlPullParser.START_TAG) { @@ -3852,6 +3855,7 @@ public class UserManagerService extends IUserManager.Stub { updateUserIds(); upgradeIfNecessaryLP(); + updateUsersWithFeatureFlags(guestRestrictionsArePresentOnUserListXml); } catch (Exception e) { // Remove corrupted file and retry. file.failRead(fin, e); @@ -3877,6 +3881,24 @@ public class UserManagerService extends IUserManager.Stub { } /** + * Update any user formats or Xml data that need to be updated based on the current user state + * and the feature flag settings. + */ + @GuardedBy({"mPackagesLock"}) + private void updateUsersWithFeatureFlags(boolean guestRestrictionsArePresentOnUserListXml) { + // User Xml re-writes are required when guest restrictions are saved on userlist.xml but + // as per the feature flag it should be on the SYSTEM user's xml or guest restrictions + // are saved on SYSTEM user's xml but as per the flags it should not be saved there. + if (guestRestrictionsArePresentOnUserListXml + == Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) { + for (int userId: getUserIds()) { + writeUserLP(getUserDataNoChecks(userId)); + } + writeUserListLP(); + } + } + + /** * Version of {@link #upgradeIfNecessaryLP()} that takes in the userVersion for testing * purposes. For non-tests, use {@link #upgradeIfNecessaryLP()}. */ @@ -4393,9 +4415,24 @@ public class UserManagerService extends IUserManager.Stub { UserRestrictionsUtils.writeRestrictions(serializer, mBaseUserRestrictions.getRestrictions(userInfo.id), TAG_RESTRICTIONS); - UserRestrictionsUtils.writeRestrictions(serializer, - mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL), - TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS); + if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) { + if (userInfo.id == UserHandle.USER_SYSTEM) { + UserRestrictionsUtils.writeRestrictions(serializer, + mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL), + TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS); + + serializer.startTag(null, TAG_GUEST_RESTRICTIONS); + synchronized (mGuestRestrictions) { + UserRestrictionsUtils.writeRestrictions(serializer, mGuestRestrictions, + TAG_RESTRICTIONS); + } + serializer.endTag(null, TAG_GUEST_RESTRICTIONS); + } + } else { + UserRestrictionsUtils.writeRestrictions(serializer, + mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL), + TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS); + } UserRestrictionsUtils.writeRestrictions(serializer, mDevicePolicyUserRestrictions.getRestrictions(userInfo.id), @@ -4471,12 +4508,15 @@ public class UserManagerService extends IUserManager.Stub { serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion); serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion); - serializer.startTag(null, TAG_GUEST_RESTRICTIONS); - synchronized (mGuestRestrictions) { - UserRestrictionsUtils - .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS); + if (!Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) { + serializer.startTag(null, TAG_GUEST_RESTRICTIONS); + synchronized (mGuestRestrictions) { + UserRestrictionsUtils + .writeRestrictions(serializer, mGuestRestrictions, + TAG_RESTRICTIONS); + } + serializer.endTag(null, TAG_GUEST_RESTRICTIONS); } - serializer.endTag(null, TAG_GUEST_RESTRICTIONS); int[] userIdsToWrite; synchronized (mUsersLock) { userIdsToWrite = new int[mUsers.size()]; @@ -4620,6 +4660,19 @@ public class UserManagerService extends IUserManager.Stub { localRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) { globalRestrictions = UserRestrictionsUtils.readRestrictions(parser); + } else if (TAG_GUEST_RESTRICTIONS.equals(tag)) { + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.END_TAG) { + if (type == XmlPullParser.START_TAG) { + if (parser.getName().equals(TAG_RESTRICTIONS)) { + synchronized (mGuestRestrictions) { + UserRestrictionsUtils + .readRestrictions(parser, mGuestRestrictions); + } + } + break; + } + } } else if (TAG_ACCOUNT.equals(tag)) { type = parser.next(); if (type == XmlPullParser.TEXT) { diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 85b60a07b003..29e0c35d88bd 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -295,6 +295,8 @@ public final class UserTypeFactory { .setCredentialShareableWithParent(false) .setMediaSharedWithParent(false) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) + .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) + .setHideInSettingsInQuietMode(true) .setCrossProfileIntentFilterAccessControl( UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM) .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d16a81267370..d804e01aa31e 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -444,7 +444,7 @@ public class PackageInfoUtils { updateApplicationInfo(info, flags, state); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up PackageStateUnserialized pkgState = pkgSetting.getTransientState(); @@ -690,7 +690,7 @@ public class PackageInfoUtils { info.splitDependencies = pkg.getSplitDependencies().size() == 0 ? null : pkg.getSplitDependencies(); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi(); info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi(); @@ -1006,7 +1006,7 @@ public class PackageInfoUtils { } private static void initForUser(ApplicationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid())); @@ -1016,6 +1016,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1050,7 +1057,7 @@ public class PackageInfoUtils { // This duplicates the ApplicationInfo variant because it uses field assignment and the classes // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead. private static void initForUser(InstrumentationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); if ("android".equals(packageName)) { @@ -1058,6 +1065,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1089,12 +1103,23 @@ public class PackageInfoUtils { } } - @NonNull + /** + * Returns the data dir of the app for the target user. Return null if the app isn't installed + * on the target user and doesn't have a data dir on the target user. + */ + @Nullable public static File getDataDir(PackageStateInternal ps, int userId) { if ("android".equals(ps.getPackageName())) { return Environment.getDataSystemDirectory(); } + if (android.content.pm.Flags.nullableDataDir() + && !ps.getUserStateOrDefault(userId).isInstalled() + && !ps.getUserStateOrDefault(userId).dataExists()) { + // The app has been uninstalled for the user and the data dir has been deleted + return null; + } + if (ps.isDefaultToDeviceProtectedStorage() && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) { return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId, diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index f14941b2d9c8..46121dcd9dae 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -18,6 +18,7 @@ package com.android.server.pm.pkg.parsing; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.Flags.preventSdkLibApp; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -403,8 +404,9 @@ public class ParsingPackageUtils { try { final File baseApk = new File(lite.getBaseApkPath()); + boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, - lite.getPath(), assetLoader, flags); + lite.getPath(), assetLoader, flags, shouldSkipComponents); if (result.isError()) { return input.error(result); } @@ -456,10 +458,11 @@ public class ParsingPackageUtils { final PackageLite lite = liteResult.getResult(); final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { + boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile, apkFile.getCanonicalPath(), - assetLoader, flags); + assetLoader, flags, shouldSkipComponents); if (result.isError()) { return input.error(result); } @@ -594,7 +597,8 @@ public class ParsingPackageUtils { } private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile, - String codePath, SplitAssetLoader assetLoader, int flags) { + String codePath, SplitAssetLoader assetLoader, int flags, + boolean shouldSkipComponents) { final String apkPath = apkFile.getAbsolutePath(); final String volumeUuid = getVolumeUuid(apkPath); @@ -619,7 +623,7 @@ public class ParsingPackageUtils { final Resources res = new Resources(assets, mDisplayMetrics, null); ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res, - parser, flags); + parser, flags, shouldSkipComponents); if (result.isError()) { return input.error(result.getErrorCode(), apkPath + " (at " + parser.getPositionDescription() + "): " @@ -719,11 +723,12 @@ public class ParsingPackageUtils { * @param res The resources from which to resolve values * @param parser The manifest parser * @param flags Flags how to parse + * @param shouldSkipComponents If the package is a sdk-library * @return Parsed package or null on error. */ private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath, - String codePath, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + String codePath, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { final String splitName; final String pkgName; @@ -751,7 +756,8 @@ public class ParsingPackageUtils { final ParsingPackage pkg = mCallback.startParsingPackage( pkgName, apkPath, codePath, manifestArray, isCoreApp); final ParseResult<ParsingPackage> result = - parseBaseApkTags(input, pkg, manifestArray, res, parser, flags); + parseBaseApkTags(input, pkg, manifestArray, res, parser, flags, + shouldSkipComponents); if (result.isError()) { return result; } @@ -987,10 +993,9 @@ public class ParsingPackageUtils { return ParsingUtils.unknownTag("<application>", pkg, parser, input); } } - private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg, - TypedArray sa, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + TypedArray sa, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa); if (sharedUserResult.isError()) { return sharedUserResult; @@ -1027,7 +1032,8 @@ public class ParsingPackageUtils { } } else { foundApp = true; - result = parseBaseApplication(input, pkg, res, parser, flags); + result = parseBaseApplication(input, pkg, res, parser, flags, + shouldSkipComponents); } } else { result = parseBaseApkTag(tagName, input, pkg, res, parser, flags); @@ -1972,8 +1978,8 @@ public class ParsingPackageUtils { * code moves around. */ private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input, - ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { final String pkgName = pkg.getPackageName(); int targetSdk = pkg.getTargetSdkVersion(); @@ -2213,6 +2219,9 @@ public class ParsingPackageUtils { isActivity = true; // fall-through case "receiver": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedActivity> activityResult = ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2232,6 +2241,9 @@ public class ParsingPackageUtils { result = activityResult; break; case "service": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2245,6 +2257,9 @@ public class ParsingPackageUtils { result = serviceResult; break; case "provider": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedProvider> providerResult = ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2256,6 +2271,9 @@ public class ParsingPackageUtils { result = providerResult; break; case "activity-alias": + if (shouldSkipComponents) { + continue; + } activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser, sUseRoundIcon, null /*defaultSplitName*/, input); @@ -2414,7 +2432,7 @@ public class ParsingPackageUtils { /** * For parsing non-MainComponents. Main ones have an order and some special handling which is * done directly in {@link #parseBaseApplication(ParseInput, ParsingPackage, Resources, - * XmlResourceParser, int)}. + * XmlResourceParser, int, boolean)}. */ private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) diff --git a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java index c908acdd1d6c..d5bc91278aa8 100644 --- a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java +++ b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java @@ -24,9 +24,10 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; -import android.security.keymaster.IKeyAttestationApplicationIdProvider; -import android.security.keymaster.KeyAttestationApplicationId; -import android.security.keymaster.KeyAttestationPackageInfo; +import android.security.keystore.IKeyAttestationApplicationIdProvider; +import android.security.keystore.KeyAttestationApplicationId; +import android.security.keystore.KeyAttestationPackageInfo; +import android.security.keystore.Signature; /** * @hide @@ -64,14 +65,25 @@ public class KeyAttestationApplicationIdProviderService for (int i = 0; i < packageNames.length; ++i) { PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageNames[i], PackageManager.GET_SIGNATURES, userId); - keyAttestationPackageInfos[i] = new KeyAttestationPackageInfo(packageNames[i], - packageInfo.getLongVersionCode(), packageInfo.signatures); + KeyAttestationPackageInfo pInfo = new KeyAttestationPackageInfo(); + pInfo.packageName = new String(packageNames[i]); + pInfo.versionCode = packageInfo.getLongVersionCode(); + pInfo.signatures = new Signature[packageInfo.signatures.length]; + for (int index = 0; index < packageInfo.signatures.length; index++) { + Signature sign = new Signature(); + sign.data = packageInfo.signatures[index].toByteArray(); + pInfo.signatures[index] = sign; + } + + keyAttestationPackageInfos[i] = pInfo; } } catch (NameNotFoundException nnfe) { throw new RemoteException(nnfe.getMessage()); } finally { Binder.restoreCallingIdentity(token); } - return new KeyAttestationApplicationId(keyAttestationPackageInfos); + KeyAttestationApplicationId attestAppId = new KeyAttestationApplicationId(); + attestAppId.packageInfos = keyAttestationPackageInfos; + return attestAppId; } } diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index bff6d502d566..6d580e97d578 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -39,8 +39,10 @@ import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.modules.expresslog.Counter; import com.android.server.infra.AbstractPerUserSystemService; import java.util.HashMap; @@ -64,6 +66,9 @@ final class SpeechRecognitionManagerServiceImpl extends private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid = new HashMap<>(); + @GuardedBy("mLock") + private final SparseIntArray mSessionCountByUid = new SparseIntArray(); + SpeechRecognitionManagerServiceImpl( @NonNull SpeechRecognitionManagerService master, @NonNull Object lock, @UserIdInt int userId) { @@ -216,6 +221,7 @@ final class SpeechRecognitionManagerServiceImpl extends service.shutdown(clientToken); } synchronized (mLock) { + decrementSessionCountForUidLocked(callingUid); if (!service.hasActiveSessions()) { removeService(callingUid, service); } @@ -239,6 +245,26 @@ final class SpeechRecognitionManagerServiceImpl extends return ComponentName.unflattenFromString(serviceName); } + @GuardedBy("mLock") + private int getSessionCountByUidLocked(int uid) { + return mSessionCountByUid.get(uid, 0); + } + + @GuardedBy("mLock") + private void incrementSessionCountForUidLocked(int uid) { + mSessionCountByUid.put(uid, mSessionCountByUid.get(uid, 0) + 1); + } + + @GuardedBy("mLock") + private void decrementSessionCountForUidLocked(int uid) { + int newCount = mSessionCountByUid.get(uid, 1) - 1; + if (newCount > 0) { + mSessionCountByUid.put(uid, newCount); + } else { + mSessionCountByUid.delete(uid); + } + } + private RemoteSpeechRecognitionService createService( int callingUid, ComponentName serviceComponent) { synchronized (mLock) { @@ -247,6 +273,18 @@ final class SpeechRecognitionManagerServiceImpl extends if (servicesForClient != null && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of remote services exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_service_connections_count", + callingUid); + return null; + } + + if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_session_count", + callingUid); return null; } @@ -262,6 +300,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Reused existing connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return existingService.get(); } } @@ -282,6 +321,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Creating a new connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return service; } } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index a5c0fb3c46af..cddc79db6106 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -1047,6 +1047,16 @@ public class TunerResourceManagerService extends SystemService implements IBinde // in use frontends when no available frontend has been found. int priority = getFrontendHighestClientPriority(fr.getOwnerClientId()); if (currentLowestPriority > priority) { + // we need to check the max used num if the target frontend type is not + // currently in primary use (and simply blocked due to exclusive group) + ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId()); + int primaryFeId = targetOwnerProfile.getPrimaryFrontend(); + FrontendResource primaryFe = getFrontendResource(primaryFeId); + if (fr.getType() != primaryFe.getType() + && isFrontendMaxNumUseReached(fr.getType())) { + continue; + } + // update the target frontend inUseLowestPriorityFrHandle = fr.getHandle(); currentLowestPriority = priority; isRequestFromSameProcess = (requestClient.getProcessId() diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java index e4f960763d54..a34621642bcd 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java @@ -19,6 +19,7 @@ package com.android.server.vibrator; import android.annotation.Nullable; import android.content.res.Resources; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.VibratorInfo; import android.os.vibrator.persistence.ParsedVibration; import android.os.vibrator.persistence.VibrationXmlParser; @@ -127,6 +128,10 @@ final class HapticFeedbackCustomization { VibrationXmlParser.VibrationXmlParserException, XmlParserException, XmlPullParserException { + if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) { + Slog.d(TAG, "Haptic feedback customization feature is not enabled."); + return null; + } String customizationFile = res.getString( com.android.internal.R.string.config_hapticFeedbackCustomizationFile); diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS index 9afa68210947..da5a476e9eb8 100644 --- a/services/core/java/com/android/server/vibrator/OWNERS +++ b/services/core/java/com/android/server/vibrator/OWNERS @@ -1,6 +1,5 @@ # Bug component: 345036 - +khalilahmad@google.com lsandrade@google.com michaelwr@google.com -sbowden@google.com -khalilahmad@google.com
\ No newline at end of file +roosa@google.com diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 01ea33f1aecd..0718f2f284a5 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2843,7 +2843,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId); WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); boolean systemValid = systemWallpaper != null; - boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled(); + boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled(); return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper} : systemValid ? new WallpaperData[]{systemWallpaper} : lockValid ? new WallpaperData[]{lockWallpaper} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ed10346d8495..c866dd013af0 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5367,18 +5367,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // rather than just direct membership. inFinishingTransition = mTransitionController.inFinishingTransition(this); if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) { - Slog.e(TAG, "setVisibility=" + visible - + " while transition is not collecting or finishing " - + this + " caller=" + Debug.getCallers(8)); - // Force showing the parents because they may be hidden by previous transition. if (visible) { - final Transaction t = getSyncTransaction(); - for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent; - p = p.getParent()) { - if (p.mSurfaceControl != null) { - t.show(p.mSurfaceControl); - } - } + mTransitionController.onVisibleWithoutCollectingTransition(this, + Debug.getCallers(1, 1)); + } else { + Slog.w(TAG, "Set invisible without transition " + this); } } } @@ -9269,13 +9262,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.w(TAG, errorMessage); } - // Configuration's equality doesn't consider seq so if only seq number changes in resolved - // override configuration. Therefore ConfigurationContainer doesn't change merged override - // configuration, but it's used to push configuration changes so explicitly update that. - if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) { - onMergedOverrideConfigurationChanged(); - } - // Before PiP animation is done, th windowing mode of the activity is still the previous // mode (see RootWindowContainer#moveActivityToPinnedRootTask). So once the windowing mode // of activity is changed, it is the signal of the last step to update the PiP states. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 27315bb23ae5..7cccf6b578ff 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -707,7 +707,7 @@ class ActivityStarter { } } - int res; + int res = START_CANCELED; synchronized (mService.mGlobalLock) { final boolean globalConfigWillChange = mRequest.globalConfig != null && mService.getGlobalConfiguration().diff(mRequest.globalConfig) != 0; @@ -719,22 +719,20 @@ class ActivityStarter { + "will change = %b", globalConfigWillChange); final long origId = Binder.clearCallingIdentity(); - - res = resolveToHeavyWeightSwitcherIfNeeded(); - if (res != START_SUCCESS) { - return res; - } - try { + res = resolveToHeavyWeightSwitcherIfNeeded(); + if (res != START_SUCCESS) { + return res; + } + res = executeRequest(mRequest); } finally { + Binder.restoreCallingIdentity(origId); mRequest.logMessage.append(" result code=").append(res); Slog.i(TAG, mRequest.logMessage.toString()); mRequest.logMessage.setLength(0); } - Binder.restoreCallingIdentity(origId); - if (globalConfigWillChange) { // If the caller also wants to switch to a new configuration, do so now. // This allows a clean switch, as we are waiting for the current activity diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 9fd472057e43..12e1e2cd1e41 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -47,6 +47,7 @@ import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; @@ -1592,6 +1593,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) { + rootTask.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_BACK, 0 /* flags */, + rootTask, rootTask.mDisplayContent, null /* remoteTransition */, + null /* displayChange */); /** * Workaround: Force-stop all the activities in the root pinned task before we reparent them * to the fullscreen root task. This is to guarantee that when we are removing a root task, diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 58d4e82961f6..794711262a75 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -223,9 +223,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** - * Update merged override configuration based on corresponding parent's config and notify all - * its children. If there is no parent, merged override configuration will set equal to current - * override config. + * Update merged override configuration based on corresponding parent's config. If there is no + * parent, merged override configuration will set equal to current override config. This + * doesn't cascade on its own since it's called by {@link #onConfigurationChanged}. * @see #mMergedOverrideConfiguration */ void onMergedOverrideConfigurationChanged() { @@ -240,10 +240,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } else { mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration); } - for (int i = getChildCount() - 1; i >= 0; --i) { - final ConfigurationContainer child = getChildAt(i); - child.onMergedOverrideConfigurationChanged(); - } } /** @@ -688,8 +684,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { if (newParent != null) { // Update full configuration of this container and all its children. onConfigurationChanged(newParent.mFullConfiguration); - // Update merged override configuration of this container and all its children. - onMergedOverrideConfigurationChanged(); } } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index f81e5d453434..df26b101a657 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -44,6 +44,7 @@ import android.util.proto.ProtoOutputStream; import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; @@ -79,6 +80,12 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { private final Configuration mTmpConfiguration = new Configuration(); /** + * Prevent duplicate calls to onDisplayAreaAppeared, or early call of onDisplayAreaInfoChanged. + */ + @VisibleForTesting + boolean mDisplayAreaAppearedSent; + + /** * Whether this {@link DisplayArea} should ignore fixed-orientation request. If {@code true}, it * can never specify orientation, but shows the fixed-orientation apps below it in the * letterbox; otherwise, it rotates based on the fixed-orientation request. @@ -582,18 +589,31 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { sendDisplayAreaVanished(lastOrganizer); if (!skipDisplayAreaAppeared) { sendDisplayAreaAppeared(); + } else if (organizer != null) { + // Set as sent since the DisplayAreaAppearedInfo will be sent back when registered. + mDisplayAreaAppearedSent = true; } } + @VisibleForTesting void sendDisplayAreaAppeared() { - if (mOrganizer == null) return; + if (mOrganizer == null || mDisplayAreaAppearedSent) return; mOrganizerController.onDisplayAreaAppeared(mOrganizer, this); + mDisplayAreaAppearedSent = true; + } + + @VisibleForTesting + void sendDisplayAreaInfoChanged() { + if (mOrganizer == null || !mDisplayAreaAppearedSent) return; + mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this); } + @VisibleForTesting void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) { - if (organizer == null) return; + if (organizer == null || !mDisplayAreaAppearedSent) return; migrateToNewSurfaceControl(getSyncTransaction()); mOrganizerController.onDisplayAreaVanished(organizer, this); + mDisplayAreaAppearedSent = false; } @Override @@ -603,7 +623,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { super.onConfigurationChanged(newParentConfig); if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) { - mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this); + sendDisplayAreaInfoChanged(); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f6fe9b1dc63d..b7b5c2af0e3e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -218,6 +218,7 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.DisplayShape; import android.view.Gravity; +import android.view.IDecorViewGestureListener; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; import android.view.IWindow; @@ -471,6 +472,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private final RemoteCallbackList<ISystemGestureExclusionListener> mSystemGestureExclusionListeners = new RemoteCallbackList<>(); + private final RemoteCallbackList<IDecorViewGestureListener> mDecorViewGestureListener = + new RemoteCallbackList<>(); private final Region mSystemGestureExclusion = new Region(); private boolean mSystemGestureExclusionWasRestricted = false; private final Region mSystemGestureExclusionUnrestricted = new Region(); @@ -5968,6 +5971,27 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mSystemGestureExclusionListeners.unregister(listener); } + void registerDecorViewGestureListener(IDecorViewGestureListener listener) { + mDecorViewGestureListener.register(listener); + } + + void unregisterDecorViewGestureListener(IDecorViewGestureListener listener) { + mDecorViewGestureListener.unregister(listener); + } + + void updateDecorViewGestureIntercepted(IBinder token, boolean intercepted) { + for (int i = mDecorViewGestureListener.beginBroadcast() - 1; i >= 0; --i) { + try { + mDecorViewGestureListener + .getBroadcastItem(i) + .onInterceptionChanged(token, intercepted); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify DecorViewGestureListener", e); + } + } + mDecorViewGestureListener.finishBroadcast(); + } + void updateKeepClearAreas() { final Set<Rect> restrictedKeepClearAreas = new ArraySet<>(); final Set<Rect> unrestrictedKeepClearAreas = new ArraySet<>(); diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 99831d3f6e48..23c135a7b83a 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY; +import static com.android.window.flags.Flags.explicitRefreshRateHints; + import android.hardware.display.DisplayManager; import android.view.Display; import android.view.Display.Mode; @@ -137,7 +139,7 @@ class RefreshRatePolicy { // to run in default refresh rate. But if the display size of default mode is different // from the using preferred mode, then still keep the preferred mode to avoid disturbing // the animation. - if (w.isAnimationRunningSelfOrParent()) { + if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { Display.Mode preferredMode = null; for (Display.Mode mode : mDisplayInfo.supportedModes) { if (preferredDisplayModeId == mode.getModeId()) { @@ -251,7 +253,7 @@ class RefreshRatePolicy { // If app is animating, it's not able to control refresh rate because we want the animation // to run in default refresh rate. - if (w.isAnimationRunningSelfOrParent()) { + if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { return w.mFrameRateVote.reset(); } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index e6d48667ffb0..3775ccd79d4c 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -560,6 +560,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override + public void reportDecorViewGestureInterceptionChanged(IWindow window, boolean intercepted) { + final long ident = Binder.clearCallingIdentity(); + try { + mService.reportDecorViewGestureChanged(this, window, intercepted); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void reportKeepClearAreasChanged(IWindow window, List<Rect> restricted, List<Rect> unrestricted) { if (!mSetsUnrestrictedKeepClearAreas && !unrestricted.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 882104a297ef..f3fb7c442b78 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -703,6 +703,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (dc == null || mTargetDisplays.contains(dc)) return; mTargetDisplays.add(dc); addOnTopTasks(dc, mOnTopTasksStart); + mController.startPerfHintForDisplay(dc.mDisplayId); } /** @@ -1391,7 +1392,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { dc.handleCompleteDeferredRemoval(); } validateKeyguardOcclusion(); - validateVisibility(); mState = STATE_FINISHED; // Rotation change may be deferred while there is a display change transition, so check @@ -2766,29 +2766,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } - private void validateVisibility() { - for (int i = mTargets.size() - 1; i >= 0; --i) { - if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) { - return; - } - } - // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly. - // If the window container should be visible, then recover it. - mController.mStateValidators.add(() -> { - for (int i = mTargets.size() - 1; i >= 0; --i) { - final ChangeInfo change = mTargets.get(i); - if (!change.mContainer.isVisibleRequested() - || change.mContainer.mSurfaceControl == null) { - continue; - } - Slog.e(TAG, "Force show for visible " + change.mContainer - + " which may be hidden by transition unexpectedly"); - change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl); - change.mContainer.scheduleAnimation(); - } - }); - } - /** * Returns {@code true} if the transition and the corresponding transaction should be applied * on display thread. Currently, this only checks for display rotation change because the order diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 78afaa880584..de7871e3c231 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -22,8 +22,10 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.SystemPerformanceHinter.HINT_SF; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,6 +41,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -48,6 +51,8 @@ import android.view.WindowManager; import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.RemoteTransition; +import android.window.SystemPerformanceHinter; +import android.window.SystemPerformanceHinter.HighPerfSession; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; @@ -125,6 +130,8 @@ class TransitionController { SnapshotController mSnapshotController; TransitionTracer mTransitionTracer; + private SystemPerformanceHinter mSystemPerformanceHinter; + private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = new ArrayList<>(); @@ -176,6 +183,24 @@ class TransitionController { private final IBinder.DeathRecipient mTransitionPlayerDeath; + /** + * Tracks active perf sessions that boost frame rate and hint sf to increase its + * estimated work duration. + */ + private final ArraySet<HighPerfSession> mHighPerfSessions = new ArraySet<>(); + + + /** + * Starts a perf hint session which will boost the refresh rate for the display and change + * sf duration to handle larger workloads. + */ + void startPerfHintForDisplay(int displayId) { + if (explicitRefreshRateHints()) { + mHighPerfSessions.add(mSystemPerformanceHinter.startSession(HINT_SF, displayId, + "Transition collected")); + } + } + static class QueuedTransition { final Transition mTransition; final OnStartCollect mOnStartCollect; @@ -255,6 +280,12 @@ class TransitionController { mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled; registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); setSyncEngine(wms.mSyncEngine); + setSystemPerformanceHinter(wms.mSystemPerformanceHinter); + } + + @VisibleForTesting + void setSystemPerformanceHinter(SystemPerformanceHinter hinter) { + mSystemPerformanceHinter = hinter; } @VisibleForTesting @@ -960,6 +991,36 @@ class TransitionController { mValidateDisplayVis.clear(); } + void onVisibleWithoutCollectingTransition(WindowContainer<?> wc, String caller) { + final boolean isPlaying = !mPlayingTransitions.isEmpty(); + Slog.e(TAG, "Set visible without transition " + wc + " playing=" + isPlaying + + " caller=" + caller); + if (!isPlaying) { + enforceSurfaceVisible(wc); + return; + } + // Update surface visibility after the playing transitions are finished, so the last + // visibility won't be replaced by the finish transaction of transition. + mStateValidators.add(() -> { + if (wc.isVisibleRequested()) { + enforceSurfaceVisible(wc); + } + }); + } + + private void enforceSurfaceVisible(WindowContainer<?> wc) { + if (wc.mSurfaceControl == null) return; + wc.getSyncTransaction().show(wc.mSurfaceControl); + // Force showing the parents because they may be hidden by previous transition. + for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent; + p = p.getParent()) { + if (p.mSurfaceControl != null) { + p.getSyncTransaction().show(p.mSurfaceControl); + } + } + wc.scheduleAnimation(); + } + /** * Called when the transition has a complete set of participants for its operation. In other * words, it is when the transition is "ready" but is still waiting for participants to draw. @@ -1006,12 +1067,20 @@ class TransitionController { // legacy sync mSyncEngine.startSyncSet(queued.mLegacySync); } - // Post this so that the now-playing transition logic isn't interrupted. - mAtm.mH.post(() -> { - synchronized (mAtm.mGlobalLock) { - queued.mOnStartCollect.onCollectStarted(true /* deferred */); - } - }); + if (queued.mTransition != null + && queued.mTransition.mType == WindowManager.TRANSIT_SLEEP) { + // SLEEP transitions are special in that they don't collect anything (in fact if they + // do collect things it can cause problems). So, we need to run it's onCollectStarted + // immediately. + queued.mOnStartCollect.onCollectStarted(true /* deferred */); + } else { + // Post this so that the now-playing transition logic isn't interrupted. + mAtm.mH.post(() -> { + synchronized (mAtm.mGlobalLock) { + queued.mOnStartCollect.onCollectStarted(true /* deferred */); + } + }); + } } void moveToPlaying(Transition transition) { @@ -1164,18 +1233,27 @@ class TransitionController { final boolean animatingState = !mPlayingTransitions.isEmpty() || (mCollectingTransition != null && mCollectingTransition.isStarted()); if (animatingState && !mAnimatingState) { - t.setEarlyWakeupStart(); + if (!explicitRefreshRateHints()) { + t.setEarlyWakeupStart(); + } // Usually transitions put quite a load onto the system already (with all the things // happening in app), so pause task snapshot persisting to not increase the load. mSnapshotController.setPause(true); mAnimatingState = true; Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { - t.setEarlyWakeupEnd(); + if (!explicitRefreshRateHints()) { + t.setEarlyWakeupEnd(); + } mAtm.mWindowManager.scheduleAnimationLocked(); mSnapshotController.setPause(false); mAnimatingState = false; Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); + // We close all perf sessions here when all transitions finish. The sessions are created + // when we collect transitions because we have access to the display id. + for (HighPerfSession perfSession : mHighPerfSessions) { + perfSession.close(); + } } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 7e5dabbea737..674ff487800f 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -85,13 +85,8 @@ class WallpaperController { // to another, and this is the previous wallpaper target. private WindowState mPrevWallpaperTarget = null; - private float mLastWallpaperX = -1; - private float mLastWallpaperY = -1; - private float mLastWallpaperXStep = -1; - private float mLastWallpaperYStep = -1; private float mLastWallpaperZoomOut = 0; - private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; - private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; + // Whether COMMAND_FREEZE was dispatched. private boolean mLastFrozen = false; @@ -116,8 +111,6 @@ class WallpaperController { private static final int WALLPAPER_DRAW_TIMEOUT = 2; private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL; - private boolean mShouldUpdateZoom; - @Nullable private Point mLargestDisplaySize = null; private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult(); @@ -370,6 +363,7 @@ class WallpaperController { // Full size of the wallpaper (usually larger than bounds above to parallax scroll when // swiping through Launcher pages). final Rect wallpaperFrame = wallpaperWin.getFrame(); + WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken(); final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width(); final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height(); @@ -394,10 +388,10 @@ class WallpaperController { // The 0 to 1 scale is because the "length" varies depending on how many home screens you // have, so 0 is the left of the first home screen, and 1 is the right of the last one (for // LTR, and the opposite for RTL). - float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX; + float wpx = token.mWallpaperX >= 0 ? token.mWallpaperX : defaultWallpaperX; // "Wallpaper X step size" is how much of that 0-1 is one "page" of the home screen // when scrolling. - float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; + float wpxs = token.mWallpaperXStep >= 0 ? token.mWallpaperXStep : -1.0f; // Difference between width of wallpaper image, and the last size of the wallpaper. // This is the horizontal surplus from the prior configuration. int availw = diffWidth; @@ -406,10 +400,10 @@ class WallpaperController { wallpaperWin.isRtl()); availw -= displayOffset; int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0; - if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + if (token.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { // if device is LTR, then offset wallpaper to the left (the wallpaper is drawn // always starting from the left of the screen). - offset += mLastWallpaperDisplayOffsetX; + offset += token.mWallpaperDisplayOffsetX; } else if (!wallpaperWin.isRtl()) { // In RTL the offset is calculated so that the wallpaper ends up right aligned (see // offset above). @@ -423,11 +417,11 @@ class WallpaperController { rawChanged = true; } - float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f; - float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; + float wpy = token.mWallpaperY >= 0 ? token.mWallpaperY : 0.5f; + float wpys = token.mWallpaperYStep >= 0 ? token.mWallpaperYStep : -1.0f; offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0; - if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - offset += mLastWallpaperDisplayOffsetY; + if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + offset += token.mWallpaperDisplayOffsetY; } newYOffset = offset; @@ -549,8 +543,10 @@ class WallpaperController { void setWallpaperZoomOut(WindowState window, float zoom) { if (Float.compare(window.mWallpaperZoomOut, zoom) != 0) { window.mWallpaperZoomOut = zoom; - mShouldUpdateZoom = true; - updateWallpaperOffsetLocked(window, false); + computeLastWallpaperZoomOut(); + for (WallpaperWindowToken token : mWallpaperTokens) { + token.updateWallpaperOffset(false); + } } } @@ -598,43 +594,48 @@ class WallpaperController { // zoom effect from home. target = changingTarget; } - if (target != null) { - if (target.mWallpaperX >= 0) { - mLastWallpaperX = target.mWallpaperX; - } else if (changingTarget.mWallpaperX >= 0) { - mLastWallpaperX = changingTarget.mWallpaperX; - } - if (target.mWallpaperY >= 0) { - mLastWallpaperY = target.mWallpaperY; - } else if (changingTarget.mWallpaperY >= 0) { - mLastWallpaperY = changingTarget.mWallpaperY; - } - computeLastWallpaperZoomOut(); - if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; - } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; - } - if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; - } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; - } - if (target.mWallpaperXStep >= 0) { - mLastWallpaperXStep = target.mWallpaperXStep; - } else if (changingTarget.mWallpaperXStep >= 0) { - mLastWallpaperXStep = changingTarget.mWallpaperXStep; - } - if (target.mWallpaperYStep >= 0) { - mLastWallpaperYStep = target.mWallpaperYStep; - } else if (changingTarget.mWallpaperYStep >= 0) { - mLastWallpaperYStep = changingTarget.mWallpaperYStep; - } - } - for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { - mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync); + WallpaperWindowToken token = getTokenForTarget(target); + if (token == null) return; + + if (target.mWallpaperX >= 0) { + token.mWallpaperX = target.mWallpaperX; + } else if (changingTarget.mWallpaperX >= 0) { + token.mWallpaperX = changingTarget.mWallpaperX; + } + if (target.mWallpaperY >= 0) { + token.mWallpaperY = target.mWallpaperY; + } else if (changingTarget.mWallpaperY >= 0) { + token.mWallpaperY = changingTarget.mWallpaperY; + } + if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; + } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; + } + if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; + } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; } + if (target.mWallpaperXStep >= 0) { + token.mWallpaperXStep = target.mWallpaperXStep; + } else if (changingTarget.mWallpaperXStep >= 0) { + token.mWallpaperXStep = changingTarget.mWallpaperXStep; + } + if (target.mWallpaperYStep >= 0) { + token.mWallpaperYStep = target.mWallpaperYStep; + } else if (changingTarget.mWallpaperYStep >= 0) { + token.mWallpaperYStep = changingTarget.mWallpaperYStep; + } + token.updateWallpaperOffset(sync); + } + + private WallpaperWindowToken getTokenForTarget(WindowState target) { + if (target == null) return null; + WindowState window = mFindResults.getTopWallpaper( + target.canShowWhenLocked() && mService.isKeyguardLocked()); + return window == null ? null : window.mToken.asWallpaperToken(); } void clearLastWallpaperTimeoutTime() { @@ -805,10 +806,11 @@ class WallpaperController { // all wallpapers go behind it. findWallpaperTarget(); updateWallpaperWindowsTarget(mFindResults); + WallpaperWindowToken token = getTokenForTarget(mWallpaperTarget); // The window is visible to the compositor...but is it visible to the user? // That is what the wallpaper cares about. - final boolean visible = mWallpaperTarget != null; + final boolean visible = token != null; if (DEBUG_WALLPAPER) { Slog.v(TAG, "Wallpaper visibility: " + visible + " at display " + mDisplayContent.getDisplayId()); @@ -816,19 +818,18 @@ class WallpaperController { if (visible) { if (mWallpaperTarget.mWallpaperX >= 0) { - mLastWallpaperX = mWallpaperTarget.mWallpaperX; - mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep; + token.mWallpaperX = mWallpaperTarget.mWallpaperX; + token.mWallpaperXStep = mWallpaperTarget.mWallpaperXStep; } - computeLastWallpaperZoomOut(); if (mWallpaperTarget.mWallpaperY >= 0) { - mLastWallpaperY = mWallpaperTarget.mWallpaperY; - mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; + token.mWallpaperY = mWallpaperTarget.mWallpaperY; + token.mWallpaperYStep = mWallpaperTarget.mWallpaperYStep; } if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; + token.mWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; } if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; + token.mWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; } } @@ -1020,13 +1021,11 @@ class WallpaperController { * we'll have conflicts and break the "depth system" mental model. */ private void computeLastWallpaperZoomOut() { - if (mShouldUpdateZoom) { - mLastWallpaperZoomOut = 0; - mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); - mShouldUpdateZoom = false; - } + mLastWallpaperZoomOut = 0; + mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); } + private float zoomOutToScale(float zoomOut) { return MathUtils.lerp(mMinWallpaperScale, mMaxWallpaperScale, 1 - zoomOut); } @@ -1034,19 +1033,28 @@ class WallpaperController { void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("displayId="); pw.println(mDisplayContent.getDisplayId()); pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget); + pw.print(prefix); pw.print("mLastWallpaperZoomOut="); pw.println(mLastWallpaperZoomOut); if (mPrevWallpaperTarget != null) { pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget); } - pw.print(prefix); pw.print("mLastWallpaperX="); pw.print(mLastWallpaperX); - pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY); - if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE - || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - pw.print(prefix); - pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX); - pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY); + + for (WallpaperWindowToken t : mWallpaperTokens) { + pw.print(prefix); pw.println("token " + t + ":"); + pw.print(prefix); pw.print(" canShowWhenLocked="); pw.println(t.canShowWhenLocked()); + dumpValue(pw, prefix, "mWallpaperX", t.mWallpaperX); + dumpValue(pw, prefix, "mWallpaperY", t.mWallpaperY); + dumpValue(pw, prefix, "mWallpaperXStep", t.mWallpaperXStep); + dumpValue(pw, prefix, "mWallpaperYStep", t.mWallpaperYStep); + dumpValue(pw, prefix, "mWallpaperDisplayOffsetX", t.mWallpaperDisplayOffsetX); + dumpValue(pw, prefix, "mWallpaperDisplayOffsetY", t.mWallpaperDisplayOffsetY); } } + private void dumpValue(PrintWriter pw, String prefix, String valueName, float value) { + pw.print(prefix); pw.print(" " + valueName + "="); + pw.println(value >= 0 ? value : "NA"); + } + /** Helper class for storing the results of a wallpaper target find operation. */ final private static class FindWallpaperTargetResult { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index c7fd147cfb4a..50ef52a4d9dd 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -42,6 +42,12 @@ class WallpaperWindowToken extends WindowToken { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM; private boolean mShowWhenLocked = false; + float mWallpaperX = -1; + float mWallpaperY = -1; + float mWallpaperXStep = -1; + float mWallpaperYStep = -1; + int mWallpaperDisplayOffsetX = Integer.MIN_VALUE; + int mWallpaperDisplayOffsetY = Integer.MIN_VALUE; WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens) { diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java new file mode 100644 index 000000000000..5b9acb2f67c4 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -0,0 +1,51 @@ +/* + * 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.wm; + +import com.android.window.flags.Flags; + +/** + * Utility class to read the flags used in the WindowManager server. + * + * It is not very cheap to read trunk stable flag, so having a centralized place to cache the flag + * values in the system server side. + * + * Flags should be defined in `core.java.android.window.flags` to allow access from client side. + * + * To override flag: + * adb shell device_config put [namespace] [package].[name] [true/false] + * adb reboot + * + * To access in wm: + * {@link WindowManagerService#mFlags} + * + * Notes: + * The system may use flags at anytime, so changing flags will only take effect after device + * reboot. Otherwise, it may result unexpected behavior, such as broken transition. + * When a flag needs to be read from both the server side and the client side, changing the flag + * value will result difference in server and client until device reboot. + */ +class WindowManagerFlags { + + /* Start Available Flags */ + + final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag(); + + final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag(); + + /* End Available Flags */ +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 074b4044fdaa..f339d249e272 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -249,6 +249,7 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.ICrossWindowBlurEnabledListener; +import android.view.IDecorViewGestureListener; import android.view.IDisplayChangeWindowController; import android.view.IDisplayFoldListener; import android.view.IDisplayWindowInsetsController; @@ -303,6 +304,7 @@ import android.window.ClientWindowFrames; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ScreenCapture; +import android.window.SystemPerformanceHinter; import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContextInfo; @@ -546,6 +548,8 @@ public class WindowManagerService extends IWindowManager.Stub @VisibleForTesting WindowManagerPolicy mPolicy; + final WindowManagerFlags mFlags; + final IActivityManager mActivityManager; final ActivityManagerInternal mAmInternal; final UserManagerInternal mUmInternal; @@ -1035,6 +1039,8 @@ public class WindowManagerService extends IWindowManager.Stub sThreadPriorityBooster.reset(); } + SystemPerformanceHinter mSystemPerformanceHinter; + void openSurfaceTransaction() { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction"); @@ -1152,6 +1158,7 @@ public class WindowManagerService extends IWindowManager.Stub mGlobalLock = atm.getGlobalLock(); mAtmService = atm; mContext = context; + mFlags = new WindowManagerFlags(); mIsPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mAllowBootMessages = showBootMsgs; mLimitedAlphaCompositing = context.getResources().getBoolean( @@ -1328,6 +1335,13 @@ public class WindowManagerService extends IWindowManager.Stub mBlurController = new BlurController(mContext, mPowerManager); mTaskFpsCallbackController = new TaskFpsCallbackController(mContext); mAccessibilityController = new AccessibilityController(this); + mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> { + synchronized (mGlobalLock) { + DisplayContent dc = mRoot.getDisplayContent(displayId); + return (dc == null) ? null : dc.getSurfaceControl(); + } + + }, mTransactionFactory); } DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() { @@ -3100,10 +3114,15 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void notifyKeyguardTrustedChanged() { - synchronized (mGlobalLock) { - if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { - mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { + mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + } } + } finally { + Binder.restoreCallingIdentity(origId); } } @@ -4629,8 +4648,9 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent == null) { - throw new IllegalArgumentException("Trying to register visibility event " - + "for invalid display: " + displayId); + throw new IllegalArgumentException( + "Trying to register system gesture exclusion event for invalid display: " + + displayId); } displayContent.registerSystemGestureExclusionListener(listener); } @@ -4642,13 +4662,64 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent == null) { - throw new IllegalArgumentException("Trying to register visibility event " - + "for invalid display: " + displayId); + throw new IllegalArgumentException( + "Trying to unregister system gesture exclusion event for invalid display: " + + displayId); } displayContent.unregisterSystemGestureExclusionListener(listener); } } + @Override + public void registerDecorViewGestureListener( + IDecorViewGestureListener listener, int displayId) { + if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT, + "registerDecorViewGestureListener()")) { + throw new SecurityException("Requires MONITOR_INPUT permission"); + } + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + throw new IllegalArgumentException( + "Trying to register DecorView gesture event listener" + + "for invalid display: " + + displayId); + } + displayContent.registerDecorViewGestureListener(listener); + } + } + + @Override + public void unregisterDecorViewGestureListener( + IDecorViewGestureListener listener, int displayId) { + if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT, + "unregisterSystemGestureExclusionListener()")) { + throw new SecurityException("Requires MONITOR_INPUT permission"); + } + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + throw new IllegalArgumentException( + "Trying to unregister DecorView gesture event listener" + + "for invalid display: " + + displayId); + } + displayContent.unregisterDecorViewGestureListener(listener); + } + } + + void reportDecorViewGestureChanged(Session session, IWindow window, boolean intercepted) { + synchronized (mGlobalLock) { + final WindowState win = + windowForClientLocked(session, window, false /* throwOnError */); + if (win == null) { + return; + } + win.getDisplayContent() + .updateDecorViewGestureIntercepted(win.mToken.token, intercepted); + } + } + void reportSystemGestureExclusionChanged(Session session, IWindow window, List<Rect> exclusionRects) { synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4beec2bc79e6..726d4d7c34e7 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -186,6 +186,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; +import android.app.servertransaction.WindowStateResizeItem; import android.content.Context; import android.content.res.Configuration; import android.graphics.Matrix; @@ -3732,30 +3733,44 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP markRedrawForSyncReported(); - try { - mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, - getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, - syncWithBuffers ? mSyncSeqId : -1, isDragResizing); - if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration - .getMergedConfiguration().windowConfiguration.getRotation()) { - mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Requested redraw for orientation change: %s", this); - } - - if (mWmService.mAccessibilityController.hasCallbacks()) { - mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + if (mWmService.mFlags.mWindowStateResizeItemFlag) { + getProcess().scheduleClientTransactionItem( + WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw, + mLastReportedConfiguration, getCompatInsetsState(), forceRelayout, + alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing)); + onResizePostDispatched(drawPending, prevRotation, displayId); + } else { + // TODO(b/301870955): cleanup after launch + try { + mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, + getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing); + onResizePostDispatched(drawPending, prevRotation, displayId); + } catch (RemoteException e) { + // Cancel orientation change of this window to avoid blocking unfreeze display. + setOrientationChanging(false); + mLastFreezeDuration = (int) (SystemClock.elapsedRealtime() + - mWmService.mDisplayFreezeTime); + Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } - } catch (RemoteException e) { - // Cancel orientation change of this window to avoid blocking unfreeze display. - setOrientationChanging(false); - mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - - mWmService.mDisplayFreezeTime); - Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) { + if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration + .getMergedConfiguration().windowConfiguration.getRotation()) { + mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Requested redraw for orientation change: %s", this); + } + + if (mWmService.mAccessibilityController.hasCallbacks()) { + mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + } + } + boolean inRelaunchingActivity() { return mActivityRecord != null && mActivityRecord.isRelaunching(); } diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp index 68e2c9a57c24..c7366173ecda 100644 --- a/services/core/jni/tvinput/JTvInputHal.cpp +++ b/services/core/jni/tvinput/JTvInputHal.cpp @@ -147,7 +147,6 @@ int JTvInputHal::removeStream(int deviceId, int streamId) { } int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) { - Mutex::Autolock autoLock(&mLock); if (!mTvInput->setTvMessageEnabled(deviceId, streamId, static_cast<AidlTvMessageEventType>(type), enabled) .isOk()) { @@ -188,7 +187,7 @@ static const std::map<std::pair<AidlAudioDeviceType, std::string>, audio_devices void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); mConnections.add(info.deviceId, KeyedVector<int, Connection>()); } JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -275,7 +274,7 @@ void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { void JTvInputHal::onDeviceUnavailable(int deviceId) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -289,7 +288,7 @@ void JTvInputHal::onDeviceUnavailable(int deviceId) { void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -330,7 +329,7 @@ void JTvInputHal::onTvMessage(int deviceId, int streamId, AidlTvMessageEventType void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) { sp<BufferProducerThread> thread; { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); Connection& connection = connections.editValueFor(streamId); if (connection.mThread == NULL) { diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h index b7b4b1621512..1d8d1629f67d 100644 --- a/services/core/jni/tvinput/JTvInputHal.h +++ b/services/core/jni/tvinput/JTvInputHal.h @@ -220,7 +220,6 @@ private: void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type, AidlTvMessage& message, signed char data[], int dataLength); - Mutex mLock; Mutex mStreamLock; jweak mThiz; sp<Looper> mLooper; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 43e47d7a45fc..49af89b5f02e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -872,17 +872,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "enable_permission_based_access"; private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = - "enable_device_policy_engine"; - private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true; - // TODO(b/265683382) remove the flag after rollout. public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false; - // TODO(b/261999445) remove the flag after rollout. - private static final String HEADLESS_FLAG = "headless"; - private static final boolean DEFAULT_HEADLESS_FLAG = true; - // TODO(b/266831522) remove the flag after rollout. private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions"; private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true; @@ -4025,75 +4017,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void clearDeviceOwnerUserRestriction(UserHandle userHandle) { - if (isHeadlessFlagEnabled()) { - for (int userId : mUserManagerInternal.getUserIds()) { - UserHandle user = UserHandle.of(userId); - // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the - // original state - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, - false, user); - } - // When a device owner is set, the system automatically restricts adding a - // managed profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - false, - user); - } - // When a device owner is set, the system automatically restricts adding a - // clone profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - false, user); - } - - // When a device owner is set, the system automatically restricts adding a - // private profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - false, user); - } - } - } else { - // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the original state - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, - userHandle); + for (int userId : mUserManagerInternal.getUserIds()) { + UserHandle user = UserHandle.of(userId); + // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the + // original state + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, + false, user); } // When a device owner is set, the system automatically restricts adding a // managed profile. // Remove this restriction when the device owner is cleared. if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - userHandle)) { + user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, - userHandle); + user); } - // When a device owner is set, the system automatically restricts adding a clone - // profile. + // When a device owner is set, the system automatically restricts adding a + // clone profile. // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - userHandle)) { + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - false, - userHandle); + false, user); } // When a device owner is set, the system automatically restricts adding a // private profile. // Remove this restriction when the device owner is cleared. if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - userHandle)) { + user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - false, userHandle); + false, user); } } + } /** @@ -6476,7 +6434,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { KeyChain.bindAsUser(mContext, userHandle)) { IKeyChainService keyChain = keyChainConnection.getService(); return keyChain.setGrant(granteeUid, alias, hasGrant); - } catch (RemoteException e) { + } catch (RemoteException | AssertionError e) { Slogf.e(LOG_TAG, "Setting grant for package.", e); return false; } @@ -7956,14 +7914,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { hasCallingOrSelfPermission(permission.TRIGGER_LOST_MODE)); synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + Preconditions.checkState(admin != null, "Lost mode location updates can only be sent on an organization-owned device."); mInjector.binderWithCleanCallingIdentity(() -> { @@ -9449,39 +9401,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // profile, such that the admin on that managed profile has extended management // capabilities that can affect the entire device (but not access private data // on the primary profile). - if (isHeadlessFlagEnabled()) { - for (int u : mUserManagerInternal.getUserIds()) { - mUserManager.setUserRestriction( - UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, - UserHandle.of(u)); - // Restrict adding a clone profile when a device owner is set on the device. - // That is to prevent the co-existence of a clone profile and a device owner - // on the same device. - // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - true, - UserHandle.of(u)); - - // Restrict adding a private profile when a device owner is set. - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - true, - UserHandle.of(u)); - } - } else { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - true, - UserHandle.of(userId)); + for (int u : mUserManagerInternal.getUserIds()) { + mUserManager.setUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, + UserHandle.of(u)); // Restrict adding a clone profile when a device owner is set on the device. // That is to prevent the co-existence of a clone profile and a device owner // on the same device. // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true, - UserHandle.of(userId)); + UserHandle.of(u)); + + // Restrict adding a private profile when a device owner is set. mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, true, - UserHandle.of(userId)); + UserHandle.of(u)); } + // TODO Send to system too? sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); }); @@ -20119,14 +20056,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { // Only DO or COPE PO can turn on CC mode, so take a shortcut here and only look at // their ActiveAdmin, instead of iterating through all admins. - ActiveAdmin admin; - // TODO(b/261999445): remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + return admin != null ? admin.mCommonCriteriaMode : false; } } @@ -21393,7 +21324,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void disallowAddUser() { - if (!isHeadlessFlagEnabled() || mIsAutomotive) { + if (mIsAutomotive) { // Auto still enables adding users due to the communal nature of those devices if (mInjector.userManagerIsHeadlessSystemUserMode()) { Slogf.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode."); @@ -21711,14 +21642,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean isUsbDataSignalingEnabledInternalLocked() { - // TODO(b/261999445): remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return admin == null || admin.mUsbDataSignalingEnabled; } @@ -21785,14 +21709,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getMinimumRequiredWifiSecurityLevel() { synchronized (getLockObject()) { - ActiveAdmin admin; - // TODO(b/261999445): remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN : admin.mWifiMinimumSecurityLevel; } @@ -23169,16 +23086,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isProfileOwnerOfOrganizationOwnedDevice(caller)); } synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = - getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } if (admin != null) { final String memtagProperty = "arm64.memtag.bootctl"; @@ -23211,29 +23120,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isSystemUid(caller)); } synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = - getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } return admin != null ? admin.mtePolicy : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; } } - private boolean isHeadlessFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - HEADLESS_FLAG, - DEFAULT_HEADLESS_FLAG); - } - @Override public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() { synchronized (getLockObject()) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java index 16876ac64e71..eb893fcfee1f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java @@ -251,7 +251,14 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private int runSetDeviceOwner(PrintWriter pw) { parseArgs(); - mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId); + boolean isAdminAdded = false; + try { + mService.setActiveAdmin(mComponent, /* refreshing= */ false, mUserId); + isAdminAdded = true; + } catch (IllegalArgumentException e) { + pw.printf("%s was already an admin for user %d. No need to set it again.\n", + mComponent.flattenToShortString(), mUserId); + } try { if (!mService.setDeviceOwner(mComponent, mUserId, @@ -260,8 +267,10 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { "Can't set package " + mComponent + " as device owner."); } } catch (Exception e) { - // Need to remove the admin that we just added. - mService.removeActiveAdmin(mComponent, UserHandle.USER_SYSTEM); + if (isAdminAdded) { + // Need to remove the admin that we just added. + mService.removeActiveAdmin(mComponent, mUserId); + } throw e; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 49ad84a8e6d9..59f1edcf309d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -51,6 +51,7 @@ import android.credentials.CredentialManager; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; import android.graphics.GraphicsStatsService; +import android.graphics.Typeface; import android.hardware.display.DisplayManagerInternal; import android.net.ConnectivityManager; import android.net.ConnectivityModuleConnector; @@ -916,6 +917,14 @@ public final class SystemServer implements Dumpable { SystemServerInitThreadPool tp = SystemServerInitThreadPool.start(); mDumper.addDumpable(tp); + // Lazily load the pre-installed system font map in SystemServer only if we're not doing + // the optimized font loading in the FontManagerService. + if (!com.android.text.flags.Flags.useOptimizedBoottimeFontLoading() + && Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { + Slog.i(TAG, "Loading pre-installed system font map."); + Typeface.loadPreinstalledSystemFontMap(); + } + // Attach JVMTI agent if this is a debuggable build and the system property is set. if (Build.IS_DEBUGGABLE) { // Property is of the form "library_path=parameters". diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 486ddb4cb354..a8902fcf77af 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -1391,7 +1391,6 @@ public class MidiService extends IMidiManager.Stub { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addLegacyPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { @@ -1529,7 +1528,6 @@ public class MidiService extends IMidiManager.Stub { @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addUmpPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt index c1d137f3a8bd..93530cf5b0a9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt +++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt @@ -43,8 +43,7 @@ import kotlin.contracts.contract @Keep class AccessCheckingService(context: Context) : SystemService(context) { - @Volatile - private lateinit var state: AccessState + @Volatile private lateinit var state: AccessState private val stateLock = Any() private val policy = AccessPolicy() @@ -86,17 +85,22 @@ class AccessCheckingService(context: Context) : SystemService(context) { val state = MutableAccessState() policy.initialize( - state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback, - configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist, + state, + userIds, + packageStates, + disabledSystemPackageStates, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, implicitToSourcePermissions ) persistence.initialize() persistence.read(state) this.state = state - mutateState { - with(policy) { onInitialized() } - } + mutateState { with(policy) { onInitialized() } } appOpService.initialize() permissionService.initialize() @@ -106,40 +110,40 @@ class AccessCheckingService(context: Context) : SystemService(context) { get() = PackageManager.FEATURE_LEANBACK in availableFeatures private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String> - get() = MutableIndexedListSet<String>().apply { - this += "android" - if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { - // Note that SystemProperties.get(String, String) forces returning an empty string - // even if we pass null for the def parameter. - val carServicePackage = SystemProperties.get("ro.android.car.carservice.package") - if (carServicePackage.isNotEmpty()) { - this += carServicePackage + get() = + MutableIndexedListSet<String>().apply { + this += "android" + if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { + // Note that SystemProperties.get(String, String) forces returning an empty + // string + // even if we pass null for the def parameter. + val carServicePackage = + SystemProperties.get("ro.android.car.carservice.package") + if (carServicePackage.isNotEmpty()) { + this += carServicePackage + } } } - } private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>> @Suppress("UNCHECKED_CAST") - get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { - splitPermissions.forEach { splitPermissionInfo -> - val sourcePermissionName = splitPermissionInfo.splitPermission - splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> - getOrPut(implicitPermissionName) { MutableIndexedListSet() } += - sourcePermissionName + get() = + MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { + splitPermissions.forEach { splitPermissionInfo -> + val sourcePermissionName = splitPermissionInfo.splitPermission + splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> + getOrPut(implicitPermissionName) { MutableIndexedListSet() } += + sourcePermissionName + } } - } - } as IndexedMap<String, IndexedListSet<String>> + } as IndexedMap<String, IndexedListSet<String>> internal fun onUserAdded(userId: Int) { - mutateState { - with(policy) { onUserAdded(userId) } - } + mutateState { with(policy) { onUserAdded(userId) } } } internal fun onUserRemoved(userId: Int) { - mutateState { - with(policy) { onUserRemoved(userId) } - } + mutateState { with(policy) { onUserRemoved(userId) } } } internal fun onStorageVolumeMounted( @@ -152,8 +156,12 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onStorageVolumeMounted( - packageStates, disabledSystemPackageStates, knownPackages, volumeUuid, - packageNames, isSystemUpdated + packageStates, + disabledSystemPackageStates, + knownPackages, + volumeUuid, + packageNames, + isSystemUpdated ) } } @@ -165,7 +173,10 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageAdded( - packageStates, disabledSystemPackageStates, knownPackages, packageName + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName ) } } @@ -177,7 +188,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageRemoved( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId ) } } @@ -189,7 +204,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageInstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, userId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + userId ) } } @@ -201,7 +220,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageUninstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId, + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId, userId ) } @@ -224,34 +247,42 @@ class AccessCheckingService(context: Context) : SystemService(context) { private fun PackageManagerInternal.getKnownPackages( packageStates: Map<String, PackageState> - ): IntMap<Array<String>> = MutableIntMap<Array<String>>().apply { - this[KnownPackages.PACKAGE_INSTALLER] = - getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_VERIFIER] = - getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SETUP_WIZARD] = - getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames( - KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_CONFIGURATOR] = - getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames( - KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_APP_PREDICTOR] = - getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_COMPANION] = - getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_RETAIL_DEMO] = - getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) - .filter { isProfileOwner(it, packageStates) }.toTypedArray() - this[KnownPackages.PACKAGE_RECENTS] = - getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) - } + ): IntMap<Array<String>> = + MutableIntMap<Array<String>>().apply { + this[KnownPackages.PACKAGE_INSTALLER] = + getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = + getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_VERIFIER] = + getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SETUP_WIZARD] = + getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = + getKnownPackageNames( + KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_CONFIGURATOR] = + getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = + getKnownPackageNames( + KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_APP_PREDICTOR] = + getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_COMPANION] = + getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_RETAIL_DEMO] = + getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) + .filter { isProfileOwner(it, packageStates) } + .toTypedArray() + this[KnownPackages.PACKAGE_RECENTS] = + getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) + } private fun isProfileOwner( packageName: String, diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt index a3f65af6bc82..d0913d29f504 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt @@ -38,16 +38,11 @@ import com.android.server.permission.access.util.writeWithReserveCopy import java.io.File import java.io.FileNotFoundException -class AccessPersistence( - private val policy: AccessPolicy -) { +class AccessPersistence(private val policy: AccessPolicy) { private val scheduleLock = Any() - @GuardedBy("scheduleLock") - private val pendingMutationTimesMillis = SparseLongArray() - @GuardedBy("scheduleLock") - private val pendingStates = MutableIntMap<AccessState>() - @GuardedBy("scheduleLock") - private lateinit var writeHandler: WriteHandler + @GuardedBy("scheduleLock") private val pendingMutationTimesMillis = SparseLongArray() + @GuardedBy("scheduleLock") private val pendingStates = MutableIntMap<AccessState>() + @GuardedBy("scheduleLock") private lateinit var writeHandler: WriteHandler private val writeLock = Any() @@ -60,17 +55,16 @@ class AccessPersistence( */ fun read(state: MutableAccessState) { readSystemState(state) - state.externalState.userIds.forEachIndexed { _, userId -> - readUserState(state, userId) - } + state.externalState.userIds.forEachIndexed { _, userId -> readUserState(state, userId) } } private fun readSystemState(state: MutableAccessState) { - val fileExists = systemFile.parse { - // This is the canonical way to call an extension function in a different class. - // TODO(b/259469752): Use context receiver for this when it becomes stable. - with(policy) { parseSystemState(state) } - } + val fileExists = + systemFile.parse { + // This is the canonical way to call an extension function in a different class. + // TODO(b/259469752): Use context receiver for this when it becomes stable. + with(policy) { parseSystemState(state) } + } if (!fileExists) { policy.migrateSystemState(state) @@ -79,9 +73,8 @@ class AccessPersistence( } private fun readUserState(state: MutableAccessState, userId: Int) { - val fileExists = getUserFile(userId).parse { - with(policy) { parseUserState(state, userId) } - } + val fileExists = + getUserFile(userId).parse { with(policy) { parseUserState(state, userId) } } if (!fileExists) { policy.migrateUserState(state, userId) @@ -90,8 +83,8 @@ class AccessPersistence( } /** - * @return {@code true} if the file is successfully read from the disk; {@code false} if - * the file doesn't exist yet. + * @return {@code true} if the file is successfully read from the disk; {@code false} if the + * file doesn't exist yet. */ private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean = try { @@ -106,9 +99,7 @@ class AccessPersistence( fun write(state: AccessState) { state.systemState.write(state, UserHandle.USER_ALL) - state.userStates.forEachIndexed { _, userId, userState -> - userState.write(state, userId) - } + state.userStates.forEachIndexed { _, userId, userState -> userState.write(state, userId) } } private fun WritableState.write(state: AccessState, userId: Int) { @@ -127,8 +118,10 @@ class AccessPersistence( if (currentDelayMillis > MAX_WRITE_DELAY_MILLIS) { message.sendToTarget() } else { - val newDelayMillis = WRITE_DELAY_TIME_MILLIS - .coerceAtMost(MAX_WRITE_DELAY_MILLIS - currentDelayMillis) + val newDelayMillis = + WRITE_DELAY_TIME_MILLIS.coerceAtMost( + MAX_WRITE_DELAY_MILLIS - currentDelayMillis + ) writeHandler.sendMessageDelayed(message, newDelayMillis) } } @@ -161,15 +154,11 @@ class AccessPersistence( } private fun writeSystemState(state: AccessState) { - systemFile.serialize { - with(policy) { serializeSystemState(state) } - } + systemFile.serialize { with(policy) { serializeSystemState(state) } } } private fun writeUserState(state: AccessState, userId: Int) { - getUserFile(userId).serialize { - with(policy) { serializeUserState(state, userId) } - } + getUserFile(userId).serialize { with(policy) { serializeUserState(state, userId) } } } private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt index 6a349e237ffe..754f77ec38f9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -37,21 +37,24 @@ import com.android.server.permission.access.util.tagName import com.android.server.pm.permission.PermissionAllowlist import com.android.server.pm.pkg.PackageState -class AccessPolicy private constructor( +class AccessPolicy +private constructor( private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>> ) { @Suppress("UNCHECKED_CAST") - constructor() : this( - MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { - fun addPolicy(policy: SchemePolicy) { - getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy - } - addPolicy(AppIdPermissionPolicy()) - addPolicy(DevicePermissionPolicy()) - addPolicy(AppIdAppOpPolicy()) - addPolicy(PackageAppOpPolicy()) - } as IndexedMap<String, IndexedMap<String, SchemePolicy>> - ) + constructor() : + this( + MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { + fun addPolicy(policy: SchemePolicy) { + getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = + policy + } + addPolicy(AppIdPermissionPolicy()) + addPolicy(DevicePermissionPolicy()) + addPolicy(AppIdAppOpPolicy()) + addPolicy(PackageAppOpPolicy()) + } as IndexedMap<String, IndexedMap<String, SchemePolicy>> + ) fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy = checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) { @@ -92,23 +95,17 @@ class AccessPolicy private constructor( } fun GetStateScope.onStateMutated() { - forEachSchemePolicy { - with(it) { onStateMutated() } - } + forEachSchemePolicy { with(it) { onStateMutated() } } } fun MutateStateScope.onInitialized() { - forEachSchemePolicy { - with(it) { onInitialized() } - } + forEachSchemePolicy { with(it) { onInitialized() } } } fun MutateStateScope.onUserAdded(userId: Int) { newState.mutateExternalState().mutateUserIds() += userId newState.mutateUserStatesNoWrite()[userId] = MutableUserState() - forEachSchemePolicy { - with(it) { onUserAdded(userId) } - } + forEachSchemePolicy { with(it) { onUserAdded(userId) } } newState.externalState.packageStates.forEach { (_, packageState) -> upgradePackageVersion(packageState, userId) } @@ -117,9 +114,7 @@ class AccessPolicy private constructor( fun MutateStateScope.onUserRemoved(userId: Int) { newState.mutateExternalState().mutateUserIds() -= userId newState.mutateUserStatesNoWrite() -= userId - forEachSchemePolicy { - with(it) { onUserRemoved(userId) } - } + forEachSchemePolicy { with(it) { onUserRemoved(userId) } } } fun MutateStateScope.onStorageVolumeMounted( @@ -154,9 +149,7 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } addedAppIds.forEachIndexed { _, appId -> - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } forEachSchemePolicy { with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) } @@ -192,13 +185,9 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } if (isAppIdAdded) { - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } - } - forEachSchemePolicy { - with(it) { onPackageAdded(packageState) } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } + forEachSchemePolicy { with(it) { onPackageAdded(packageState) } } newState.userStates.forEachIndexed { _, userId, _ -> upgradePackageVersion(packageState, userId) } @@ -227,13 +216,9 @@ class AccessPolicy private constructor( } setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageRemoved(packageName, appId) } - } + forEachSchemePolicy { with(it) { onPackageRemoved(packageName, appId) } } if (isAppIdRemoved) { - forEachSchemePolicy { - with(it) { onAppIdRemoved(appId) } - } + forEachSchemePolicy { with(it) { onAppIdRemoved(appId) } } } newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (packageName in userState.packageVersions) { @@ -258,9 +243,7 @@ class AccessPolicy private constructor( checkNotNull(packageState) { "Installed package $packageName isn't found in packageStates in onPackageInstalled()" } - forEachSchemePolicy { - with(it) { onPackageInstalled(packageState, userId) } - } + forEachSchemePolicy { with(it) { onPackageInstalled(packageState, userId) } } } fun MutateStateScope.onPackageUninstalled( @@ -276,9 +259,7 @@ class AccessPolicy private constructor( setDisabledSystemPackageStates(disabledSystemPackageStates) setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageUninstalled(packageName, appId, userId) } - } + forEachSchemePolicy { with(it) { onPackageUninstalled(packageName, appId, userId) } } } fun MutateStateScope.onSystemReady( @@ -292,21 +273,15 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) setSystemReady(true) } - forEachSchemePolicy { - with(it) { onSystemReady() } - } + forEachSchemePolicy { with(it) { onSystemReady() } } } fun migrateSystemState(state: MutableAccessState) { - forEachSchemePolicy { - with(it) { migrateSystemState(state) } - } + forEachSchemePolicy { with(it) { migrateSystemState(state) } } } fun migrateUserState(state: MutableAccessState, userId: Int) { - forEachSchemePolicy { - with(it) { migrateUserState(state, userId) } - } + forEachSchemePolicy { with(it) { migrateUserState(state, userId) } } } private fun MutateStateScope.upgradePackageVersion(packageState: PackageState, userId: Int) { @@ -330,10 +305,12 @@ class AccessPolicy private constructor( VERSION_LATEST } version == VERSION_LATEST -> {} - else -> Slog.w( - LOG_TAG, "Unexpected version $version for package $packageName," + - "latest version is $VERSION_LATEST" - ) + else -> + Slog.w( + LOG_TAG, + "Unexpected version $version for package $packageName," + + "latest version is $VERSION_LATEST" + ) } } @@ -341,11 +318,7 @@ class AccessPolicy private constructor( forEachTag { when (tagName) { TAG_ACCESS -> { - forEachTag { - forEachSchemePolicy { - with(it) { parseSystemState(state) } - } - } + forEachTag { forEachSchemePolicy { with(it) { parseSystemState(state) } } } } else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state") } @@ -353,11 +326,7 @@ class AccessPolicy private constructor( } fun BinaryXmlSerializer.serializeSystemState(state: AccessState) { - tag(TAG_ACCESS) { - forEachSchemePolicy { - with(it) { serializeSystemState(state) } - } - } + tag(TAG_ACCESS) { forEachSchemePolicy { with(it) { serializeSystemState(state) } } } } fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { @@ -370,9 +339,7 @@ class AccessPolicy private constructor( TAG_DEFAULT_PERMISSION_GRANT -> parseDefaultPermissionGrant(state, userId) else -> { - forEachSchemePolicy { - with(it) { parseUserState(state, userId) } - } + forEachSchemePolicy { with(it) { parseUserState(state, userId) } } } } } @@ -428,9 +395,7 @@ class AccessPolicy private constructor( serializeDefaultPermissionGrantFingerprint( state.userStates[userId]!!.defaultPermissionGrantFingerprint ) - forEachSchemePolicy { - with(it) { serializeUserState(state, userId) } - } + forEachSchemePolicy { with(it) { serializeUserState(state, userId) } } } } @@ -451,9 +416,7 @@ class AccessPolicy private constructor( fingerprint: String? ) { if (fingerprint != null) { - tag(TAG_DEFAULT_PERMISSION_GRANT) { - attributeInterned(ATTR_FINGERPRINT, fingerprint) - } + tag(TAG_DEFAULT_PERMISSION_GRANT) { attributeInterned(ATTR_FINGERPRINT, fingerprint) } } } @@ -462,9 +425,7 @@ class AccessPolicy private constructor( private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) { schemePolicies.forEachIndexed { _, _, objectSchemePolicies -> - objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> - action(schemePolicy) - } + objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> action(schemePolicy) } } } diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt index 94c878a453c9..49d2f813c487 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -28,7 +28,9 @@ private typealias ExternalStateReference = MutableReference<ExternalState, Mutab private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState> typealias UserStates = IntReferenceMap<UserState, MutableUserState> + typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState> + private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates> sealed class AccessState( @@ -48,22 +50,22 @@ sealed class AccessState( override fun toMutable(): MutableAccessState = MutableAccessState(this) } -class MutableAccessState private constructor( +class MutableAccessState +private constructor( externalStateReference: ExternalStateReference, systemStateReference: SystemStateReference, userStatesReference: UserStatesReference -) : AccessState( - externalStateReference, - systemStateReference, - userStatesReference -) { - constructor() : this( - ExternalStateReference(MutableExternalState()), - SystemStateReference(MutableSystemState()), - UserStatesReference(MutableUserStates()) - ) - - internal constructor(accessState: AccessState) : this( +) : AccessState(externalStateReference, systemStateReference, userStatesReference) { + constructor() : + this( + ExternalStateReference(MutableExternalState()), + SystemStateReference(MutableSystemState()), + UserStatesReference(MutableUserStates()) + ) + + internal constructor( + accessState: AccessState + ) : this( accessState.externalStateReference.toImmutable(), accessState.systemStateReference.toImmutable(), accessState.userStatesReference.toImmutable() @@ -86,8 +88,10 @@ class MutableAccessState private constructor( private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet> typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + typealias MutableAppIdPackageNames = MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + private typealias AppIdPackageNamesReference = MutableReference<AppIdPackageNames, MutableAppIdPackageNames> @@ -142,7 +146,8 @@ sealed class ExternalState( override fun toMutable(): MutableExternalState = MutableExternalState(this) } -class MutableExternalState private constructor( +class MutableExternalState +private constructor( userIdsReference: UserIdsReference, packageStates: Map<String, PackageState>, disabledSystemPackageStates: Map<String, PackageState>, @@ -154,34 +159,38 @@ class MutableExternalState private constructor( permissionAllowlist: PermissionAllowlist, implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>, isSystemReady: Boolean -) : ExternalState( - userIdsReference, - packageStates, - disabledSystemPackageStates, - appIdPackageNamesReference, - knownPackages, - isLeanback, - configPermissions, - privilegedPermissionAllowlistPackages, - permissionAllowlist, - implicitToSourcePermissions, - isSystemReady -) { - constructor() : this( - UserIdsReference(MutableIntSet()), - emptyMap(), - emptyMap(), - AppIdPackageNamesReference(MutableAppIdPackageNames()), - MutableIntMap(), - false, - emptyMap(), - MutableIndexedListSet(), - PermissionAllowlist(), - MutableIndexedMap(), - false - ) - - internal constructor(externalState: ExternalState) : this( +) : + ExternalState( + userIdsReference, + packageStates, + disabledSystemPackageStates, + appIdPackageNamesReference, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, + implicitToSourcePermissions, + isSystemReady + ) { + constructor() : + this( + UserIdsReference(MutableIntSet()), + emptyMap(), + emptyMap(), + AppIdPackageNamesReference(MutableAppIdPackageNames()), + MutableIntMap(), + false, + emptyMap(), + MutableIndexedListSet(), + PermissionAllowlist(), + MutableIndexedMap(), + false + ) + + internal constructor( + externalState: ExternalState + ) : this( externalState.userIdsReference.toImmutable(), externalState.packageStates, externalState.disabledSystemPackageStates, @@ -249,9 +258,10 @@ class MutableExternalState private constructor( } } -private typealias PermissionGroupsReference = MutableReference< - IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> -> +private typealias PermissionGroupsReference = + MutableReference< + IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> + > private typealias PermissionTreesReference = MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>> @@ -280,25 +290,31 @@ sealed class SystemState( override fun toMutable(): MutableSystemState = MutableSystemState(this) } -class MutableSystemState private constructor( +class MutableSystemState +private constructor( permissionGroupsReference: PermissionGroupsReference, permissionTreesReference: PermissionTreesReference, permissionsReference: PermissionsReference, writeMode: Int -) : SystemState( - permissionGroupsReference, - permissionTreesReference, - permissionsReference, - writeMode -), MutableWritableState { - constructor() : this( - PermissionGroupsReference(MutableIndexedMap()), - PermissionTreesReference(MutableIndexedMap()), - PermissionsReference(MutableIndexedMap()), - WriteMode.NONE - ) - - internal constructor(systemState: SystemState) : this( +) : + SystemState( + permissionGroupsReference, + permissionTreesReference, + permissionsReference, + writeMode + ), + MutableWritableState { + constructor() : + this( + PermissionGroupsReference(MutableIndexedMap()), + PermissionTreesReference(MutableIndexedMap()), + PermissionsReference(MutableIndexedMap()), + WriteMode.NONE + ) + + internal constructor( + systemState: SystemState + ) : this( systemState.permissionGroupsReference.toImmutable(), systemState.permissionTreesReference.toImmutable(), systemState.permissionsReference.toImmutable(), @@ -311,8 +327,7 @@ class MutableSystemState private constructor( fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> = permissionTreesReference.mutate() - fun mutatePermissions(): MutableIndexedMap<String, Permission> = - permissionsReference.mutate() + fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate() override fun requestWriteMode(writeMode: Int) { this.writeMode = maxOf(this.writeMode, writeMode) @@ -324,34 +339,42 @@ private typealias PackageVersionsReference = typealias AppIdPermissionFlags = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdPermissionFlags = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdPermissionFlagsReference = MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags> - typealias DevicePermissionFlags = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableDevicePermissionFlags = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias AppIdDevicePermissionFlags = IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + typealias MutableAppIdDevicePermissionFlags = MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + private typealias AppIdDevicePermissionFlagsReference = MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags> -typealias AppIdAppOpModes = - IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> +typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdAppOpModes = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdAppOpModesReference = MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes> typealias PackageAppOpModes = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutablePackageAppOpModes = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias PackageAppOpModesReference = MutableReference<PackageAppOpModes, MutablePackageAppOpModes> @@ -388,7 +411,8 @@ sealed class UserState( override fun toMutable(): MutableUserState = MutableUserState(this) } -class MutableUserState private constructor( +class MutableUserState +private constructor( packageVersionsReference: PackageVersionsReference, appIdPermissionFlagsReference: AppIdPermissionFlagsReference, appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, @@ -396,26 +420,31 @@ class MutableUserState private constructor( packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, writeMode: Int -) : UserState( - packageVersionsReference, - appIdPermissionFlagsReference, - appIdDevicePermissionFlagsReference, - appIdAppOpModesReference, - packageAppOpModesReference, - defaultPermissionGrantFingerprint, - writeMode -), MutableWritableState { - constructor() : this( - PackageVersionsReference(MutableIndexedMap<String, Int>()), - AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), - AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), - AppIdAppOpModesReference(MutableAppIdAppOpModes()), - PackageAppOpModesReference(MutablePackageAppOpModes()), - null, - WriteMode.NONE - ) - - internal constructor(userState: UserState) : this( +) : + UserState( + packageVersionsReference, + appIdPermissionFlagsReference, + appIdDevicePermissionFlagsReference, + appIdAppOpModesReference, + packageAppOpModesReference, + defaultPermissionGrantFingerprint, + writeMode + ), + MutableWritableState { + constructor() : + this( + PackageVersionsReference(MutableIndexedMap<String, Int>()), + AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), + AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), + AppIdAppOpModesReference(MutableAppIdAppOpModes()), + PackageAppOpModesReference(MutablePackageAppOpModes()), + null, + WriteMode.NONE + ) + + internal constructor( + userState: UserState + ) : this( userState.packageVersionsReference.toImmutable(), userState.appIdPermissionFlagsReference.toImmutable(), userState.appIdDevicePermissionFlagsReference.toImmutable(), @@ -461,11 +490,7 @@ interface MutableWritableState : WritableState { fun requestWriteMode(writeMode: Int) } -open class GetStateScope( - val state: AccessState -) +open class GetStateScope(val state: AccessState) -class MutateStateScope( - val oldState: AccessState, - val newState: MutableAccessState -) : GetStateScope(newState) +class MutateStateScope(val oldState: AccessState, val newState: MutableAccessState) : + GetStateScope(newState) diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt index 1d46ca71fe0f..1f5a4df33cc9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessUri.kt +++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt @@ -18,9 +18,7 @@ package com.android.server.permission.access import android.os.UserHandle -sealed class AccessUri( - val scheme: String -) { +sealed class AccessUri(val scheme: String) { override fun equals(other: Any?): Boolean { throw NotImplementedError() } @@ -34,9 +32,7 @@ sealed class AccessUri( } } -data class AppOpUri( - val appOpName: String -) : AccessUri(SCHEME) { +data class AppOpUri(val appOpName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$appOpName" companion object { @@ -44,10 +40,7 @@ data class AppOpUri( } } -data class PackageUri( - val packageName: String, - val userId: Int -) : AccessUri(SCHEME) { +data class PackageUri(val packageName: String, val userId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$packageName/$userId" companion object { @@ -55,9 +48,7 @@ data class PackageUri( } } -data class PermissionUri( - val permissionName: String -) : AccessUri(SCHEME) { +data class PermissionUri(val permissionName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName" companion object { @@ -65,10 +56,7 @@ data class PermissionUri( } } -data class DevicePermissionUri( - val permissionName: String, - val deviceId: Int -) : AccessUri(SCHEME) { +data class DevicePermissionUri(val permissionName: String, val deviceId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName/$deviceId" companion object { @@ -76,9 +64,7 @@ data class DevicePermissionUri( } } -data class UidUri( - val uid: Int -) : AccessUri(SCHEME) { +data class UidUri(val uid: Int) : AccessUri(SCHEME) { val userId: Int get() = UserHandle.getUserId(uid) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt index 96d315e923ba..6c1b080e4ff4 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt @@ -46,9 +46,7 @@ class AppIdAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() appIdAppOpModes[appId] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } if (packageNames != null) { val packageVersions = userState.mutatePackageVersions() diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt index 4c7e94688d00..f291b1ab77e9 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt @@ -51,8 +51,10 @@ class AppIdAppOpPersistence : BaseAppOpPersistence() { } userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ -> // Non-application UIDs may not have an Android package but may still have app op state. - if (appId !in state.externalState.appIdPackageNames && - appId >= Process.FIRST_APPLICATION_UID) { + if ( + appId !in state.externalState.appIdPackageNames && + appId >= Process.FIRST_APPLICATION_UID + ) { Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state") appIdAppOpModes.removeAt(appIdIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt index c02fe4df67c0..94caf2865b66 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt @@ -46,7 +46,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId) if (appIdIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutateAppIdAppOpModes() .removeAt(appIdIndex) // Skip notifying the change listeners since the app ID no longer exists. } @@ -61,8 +63,8 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { if (userStateIndex < 0) { return false } - val appIdIndex = newState.userStates.valueAt(userStateIndex).appIdAppOpModes - .indexOfKey(appId) + val appIdIndex = + newState.userStates.valueAt(userStateIndex).appIdAppOpModes.indexOfKey(appId) if (appIdIndex < 0) { return false } @@ -71,7 +73,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { } fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int = - state.userStates[userId]?.appIdAppOpModes?.get(appId) + state.userStates[userId] + ?.appIdAppOpModes + ?.get(appId) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -81,8 +85,10 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .appIdAppOpModes[appId] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -122,9 +128,7 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt index 12df95e26ec4..10c77645bf15 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt @@ -28,11 +28,13 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode(packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND) setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode + packageState.appId, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + appOpMode ) } } @@ -40,14 +42,19 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (permissionName in packageState.androidPackage!!.requestedPermissions) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM - ) + val appOpMode = + getAppOpMode( + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM + ) val defaultAppOpMode = AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (appOpMode == defaultAppOpMode) { setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, AppOpsManager.MODE_ALLOWED ) } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index 5b91ad9834df..26ea9d24f918 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -33,19 +33,16 @@ import com.android.server.permission.access.UidUri import com.android.server.permission.access.collection.forEachIndexed import com.android.server.permission.access.collection.set -class AppOpService( - private val service: AccessCheckingService -) : AppOpsCheckingServiceInterface { - private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) - as PackageAppOpPolicy - private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) - as AppIdAppOpPolicy +class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface { + private val packagePolicy = + service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy + private val appIdPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy private val context = service.context private lateinit var handler: Handler - @Volatile - private var listeners = ArraySet<AppOpsModeChangedListener>() + @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>() private val listenersLock = Any() fun initialize() { @@ -86,9 +83,7 @@ class AppOpService( val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) - return service.getState { - with(appIdPolicy) { getAppOpMode(appId, userId, opName) } - } + return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } } private fun getUidModes(uid: Int): ArrayMap<String, Int>? { @@ -115,10 +110,7 @@ class AppOpService( } } - private fun getPackageModes( - packageName: String, - userId: Int - ): ArrayMap<String, Int>? = + private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? = service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) { @@ -131,15 +123,13 @@ class AppOpService( override fun removeUid(uid: Int) { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - service.mutateState { - with(appIdPolicy) { removeAppOpModes(appId, userId) } - } + service.mutateState { with(appIdPolicy) { removeAppOpModes(appId, userId) } } } override fun removePackage(packageName: String, userId: Int): Boolean { var wasChanged = false service.mutateState { - wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) } + wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) } } return wasChanged } diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt index a267637dd0c4..edeef713e6d7 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt @@ -52,9 +52,7 @@ abstract class BaseAppOpPersistence { } protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) { - appOpModes.forEachIndexed { _, name, mode -> - serializeAppOp(name, mode) - } + appOpModes.forEachIndexed { _, name, mode -> serializeAppOp(name, mode) } } private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) { diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt index c0a85f8e4b3f..758cec00da50 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.AppOpUri import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.SchemePolicy -abstract class BaseAppOpPolicy( - private val persistence: BaseAppOpPersistence -) : SchemePolicy() { +abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() { override val objectScheme: String get() = AppOpUri.SCHEME diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt index 03311a238410..8797e39754d7 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt @@ -44,9 +44,7 @@ class PackageAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() packageAppOpModes[packageName] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } userState.mutatePackageVersions()[packageName] = version } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt index 5398a57d084e..0d9470edc4ea 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt @@ -46,7 +46,9 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName) if (packageNameIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) // Skip notifying the change listeners since the package no longer exists. } @@ -61,18 +63,22 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { if (userStateIndex < 0) { return false } - val packageNameIndex = newState.userStates.valueAt(userStateIndex).packageAppOpModes - .indexOfKey(packageName) + val packageNameIndex = + newState.userStates.valueAt(userStateIndex).packageAppOpModes.indexOfKey(packageName) if (packageNameIndex < 0) { return false } - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) return true } fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int = - state.userStates[userId]?.packageAppOpModes?.get(packageName) + state.userStates[userId] + ?.packageAppOpModes + ?.get(packageName) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -82,8 +88,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .packageAppOpModes[packageName] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -123,9 +131,7 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt index 8e370936291f..f5eedf714158 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt @@ -28,11 +28,16 @@ class PackageAppOpUpgrade(private val policy: PackageAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode( + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_IN_BACKGROUND + ) setAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode ) } diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt index 686db42bbe63..b74f47734701 100644 --- a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt @@ -49,7 +49,9 @@ inline fun <K, V> ArrayMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> U } inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt index ce4aa4446698..ea8e07fd31ff 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt @@ -16,12 +16,8 @@ package com.android.server.permission.access.immutable -/** - * Immutable list with index-based access. - */ -sealed class IndexedList<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedList<T>> { +/** Immutable list with index-based access. */ +sealed class IndexedList<T>(internal val list: ArrayList<T>) : Immutable<MutableIndexedList<T>> { val size: Int get() = list.size @@ -29,20 +25,15 @@ sealed class IndexedList<T>( operator fun contains(element: T): Boolean = list.contains(element) - @Suppress("ReplaceGetOrSet") - operator fun get(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") operator fun get(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this) override fun toString(): String = list.toString() } -/** - * Mutable list with index-based access. - */ -class MutableIndexedList<T>( - list: ArrayList<T> = ArrayList() -) : IndexedList<T>(list) { +/** Mutable list with index-based access. */ +class MutableIndexedList<T>(list: ArrayList<T> = ArrayList()) : IndexedList<T>(list) { constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list)) @Suppress("ReplaceGetOrSet") diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt index dc9bae323662..a9d804ec2fd2 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedList<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt index 77e71baf0ab7..3a2fd2f386c8 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt @@ -16,12 +16,9 @@ package com.android.server.permission.access.immutable -/** - * Immutable set with index-based access, implemented using a list. - */ -sealed class IndexedListSet<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedListSet<T>> { +/** Immutable set with index-based access, implemented using a list. */ +sealed class IndexedListSet<T>(internal val list: ArrayList<T>) : + Immutable<MutableIndexedListSet<T>> { val size: Int get() = list.size @@ -31,20 +28,15 @@ sealed class IndexedListSet<T>( fun indexOf(element: T): Int = list.indexOf(element) - @Suppress("ReplaceGetOrSet") - fun elementAt(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") fun elementAt(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this) override fun toString(): String = list.toString() } -/** - * Mutable set with index-based access, implemented using a list. - */ -class MutableIndexedListSet<T>( - list: ArrayList<T> = ArrayList() -) : IndexedListSet<T>(list) { +/** Mutable set with index-based access, implemented using a list. */ +class MutableIndexedListSet<T>(list: ArrayList<T> = ArrayList()) : IndexedListSet<T>(list) { constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list)) fun add(element: T): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt index 13fc141c5a66..2634b5308049 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedListSet<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt index 299cc89d9a07..873c9c83607e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt @@ -18,12 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap -/** - * Immutable map with index-based access. - */ -sealed class IndexedMap<K, V>( - internal val map: ArrayMap<K, V> -) : Immutable<MutableIndexedMap<K, V>> { +/** Immutable map with index-based access. */ +sealed class IndexedMap<K, V>(internal val map: ArrayMap<K, V>) : + Immutable<MutableIndexedMap<K, V>> { val size: Int get() = map.size @@ -31,8 +28,7 @@ sealed class IndexedMap<K, V>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): V? = map.get(key) + @Suppress("ReplaceGetOrSet") operator fun get(key: K): V? = map.get(key) fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -45,12 +41,8 @@ sealed class IndexedMap<K, V>( override fun toString(): String = map.toString() } -/** - * Mutable map with index-based access. - */ -class MutableIndexedMap<K, V>( - map: ArrayMap<K, V> = ArrayMap() -) : IndexedMap<K, V>(map) { +/** Mutable map with index-based access. */ +class MutableIndexedMap<K, V>(map: ArrayMap<K, V> = ArrayMap()) : IndexedMap<K, V>(map) { constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map)) fun put(key: K, value: V): V? = map.put(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt index 69f1779cff8d..48637cc271bf 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean) inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -75,9 +77,7 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapIndexedTo( destination: C, transform: (Int, K, V) -> R, ): C { - forEachIndexed { index, key, value -> - transform(index, key, value).let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value).let { destination += it } } return destination } @@ -85,14 +85,14 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapNotNullIndexe destination: C, transform: (Int, K, V) -> R? ): C { - forEachIndexed { index, key, value -> - transform(index, key, value)?.let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value)?.let { destination += it } } return destination } inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt index ff76a4745c8b..6fe471802878 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): I? = map.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: K): I? = map.get(key)?.get() fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( map: ArrayMap<K, MutableReference<I, M>> = ArrayMap() ) : IndexedReferenceMap<K, I, M>(map) { - constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this( + constructor( + indexedReferenceMap: IndexedReferenceMap<K, I, M> + ) : this( ArrayMap(indexedReferenceMap.map).apply { for (i in 0 until size) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: K): M? = map.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: K): M? = map.get(key)?.mutate() fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt index 22b4d521176e..43a902b23ba5 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.muta key: K, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt index 547e56cef62a..cbc24b1a64a7 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.ArraySet -/** - * Immutable set with index-based access. - */ -sealed class IndexedSet<T>( - internal val set: ArraySet<T> -) : Immutable<MutableIndexedSet<T>> { +/** Immutable set with index-based access. */ +sealed class IndexedSet<T>(internal val set: ArraySet<T>) : Immutable<MutableIndexedSet<T>> { val size: Int get() = set.size @@ -40,12 +36,8 @@ sealed class IndexedSet<T>( override fun toString(): String = set.toString() } -/** - * Mutable set with index-based access. - */ -class MutableIndexedSet<T>( - set: ArraySet<T> = ArraySet() -) : IndexedSet<T>(set) { +/** Mutable set with index-based access. */ +class MutableIndexedSet<T>(set: ArraySet<T> = ArraySet()) : IndexedSet<T>(set) { constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set)) fun add(element: T): Boolean = set.add(element) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt index 7ed29e8813ac..e9a405f9fd6f 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseArray -/** - * Immutable map with index-based access and [Int] keys. - */ -sealed class IntMap<T>( - internal val array: SparseArray<T> -) : Immutable<MutableIntMap<T>> { +/** Immutable map with index-based access and [Int] keys. */ +sealed class IntMap<T>(internal val array: SparseArray<T>) : Immutable<MutableIntMap<T>> { val size: Int get() = array.size() @@ -44,12 +40,8 @@ sealed class IntMap<T>( override fun toString(): String = array.toString() } -/** - * Mutable map with index-based access and [Int] keys. - */ -class MutableIntMap<T>( - array: SparseArray<T> = SparseArray() -) : IntMap<T>(array) { +/** Mutable map with index-based access and [Int] keys. */ +class MutableIntMap<T>(array: SparseArray<T> = SparseArray()) : IntMap<T>(array) { constructor(intMap: IntMap<T>) : this(intMap.array.clone()) fun put(key: Int, value: T): T? = array.putReturnOld(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt index 9aa0a4182eb7..09d7319ec62e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolea inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -72,7 +74,9 @@ inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boole } inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt index 160b2279a0ba..3f2651736a54 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( operator fun contains(key: Int): Boolean = array.contains(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: Int): I? = array.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: Int): I? = array.get(key)?.get() fun indexOfKey(key: Int): Int = array.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( class MutableIntReferenceMap<I : Immutable<M>, M : I>( array: SparseArray<MutableReference<I, M>> = SparseArray() ) : IntReferenceMap<I, M>(array) { - constructor(intReferenceMap: IntReferenceMap<I, M>) : this( + constructor( + intReferenceMap: IntReferenceMap<I, M> + ) : this( intReferenceMap.array.clone().apply { for (i in 0 until size()) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIntReferenceMap<I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: Int): M? = array.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: Int): M? = array.get(key)?.mutate() fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt index 1ed4f8a777d2..a1bab9579c3d 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.mutateOrPut( key: Int, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt index 21f2af20c3a9..125479706586 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseBooleanArray -/** - * Immutable set with index-based access and [Int] elements. - */ -sealed class IntSet( - internal val array: SparseBooleanArray -) : Immutable<MutableIntSet> { +/** Immutable set with index-based access and [Int] elements. */ +sealed class IntSet(internal val array: SparseBooleanArray) : Immutable<MutableIntSet> { val size: Int get() = array.size() @@ -40,12 +36,8 @@ sealed class IntSet( override fun toString(): String = array.toString() } -/** - * Mutable set with index-based access and [Int] elements. - */ -class MutableIntSet( - array: SparseBooleanArray = SparseBooleanArray() -) : IntSet(array) { +/** Mutable set with index-based access and [Int] elements. */ +class MutableIntSet(array: SparseBooleanArray = SparseBooleanArray()) : IntSet(array) { constructor(intSet: IntSet) : this(intSet.array.clone()) fun add(element: Int): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt index 163ebbf85aed..9d0d14f01ce0 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt @@ -66,7 +66,7 @@ inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean { operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element } -fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values } +fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply { this += values } operator fun MutableIntSet.plusAssign(element: Int) { array.put(element, true) diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt index 171cfeb4379d..471a71b04635 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt @@ -27,21 +27,17 @@ package com.android.server.permission.access.immutable * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate] * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the * data structure is mutated/copied, a new instance of this class should be obtained with - * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents - * further modifications to a data structure accessed with its immutable interface. + * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents further + * modifications to a data structure accessed with its immutable interface. * * @see MutableIndexedReferenceMap * @see MutableIntReferenceMap */ -class MutableReference<I : Immutable<M>, M : I> private constructor( - private var immutable: I, - private var mutable: M? -) { +class MutableReference<I : Immutable<M>, M : I> +private constructor(private var immutable: I, private var mutable: M?) { constructor(mutable: M) : this(mutable, mutable) - /** - * Return an immutable reference to the wrapped mutable data structure. - */ + /** Return an immutable reference to the wrapped mutable data structure. */ fun get(): I = immutable /** @@ -50,7 +46,9 @@ class MutableReference<I : Immutable<M>, M : I> private constructor( * already mutable. */ fun mutate(): M { - mutable?.let { return it } + mutable?.let { + return it + } return immutable.toMutable().also { immutable = it mutable = it diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt index 691ed8f10220..29838959ccb3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.immutable.* // ktlint-disable no-wil import com.android.server.permission.access.util.PackageVersionMigration import com.android.server.pm.permission.PermissionMigrationHelper -/** - * This class migrate legacy permissions to unified permission subsystem - */ +/** This class migrate legacy permissions to unified permission subsystem */ class AppIdPermissionMigration { internal fun migrateSystemState(state: MutableAccessState) { val legacyPermissionsManager = @@ -34,10 +32,15 @@ class AppIdPermissionMigration { return } - migratePermissions(state.mutateSystemState().mutatePermissions(), - legacyPermissionsManager.legacyPermissions) - migratePermissions(state.mutateSystemState().mutatePermissionTrees(), - legacyPermissionsManager.legacyPermissionTrees, true) + migratePermissions( + state.mutateSystemState().mutatePermissions(), + legacyPermissionsManager.legacyPermissions + ) + migratePermissions( + state.mutateSystemState().mutatePermissionTrees(), + legacyPermissionsManager.legacyPermissionTrees, + true + ) } private fun migratePermissions( @@ -46,14 +49,15 @@ class AppIdPermissionMigration { isPermissionTree: Boolean = false ) { legacyPermissions.forEach { (_, legacyPermission) -> - val permission = Permission( - legacyPermission.permissionInfo, false, legacyPermission.type, 0 - ) + val permission = + Permission(legacyPermission.permissionInfo, false, legacyPermission.type, 0) permissions[permission.name] = permission if (DEBUG_MIGRATION) { - Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " + - "${permission.type}, appId: ${permission.appId}, protectionLevel: " + - "${permission.protectionLevel}, tree: $isPermissionTree" + Slog.v( + LOG_TAG, + "Migrated permission: ${permission.name}, type: " + + "${permission.type}, appId: ${permission.appId}, protectionLevel: " + + "${permission.protectionLevel}, tree: $isPermissionTree" ) } } @@ -81,25 +85,23 @@ class AppIdPermissionMigration { val permissionFlags = MutableIndexedMap<String, Int>() appIdPermissionFlags[appId] = permissionFlags - legacyPermissionStates.forEach forEachPermission@ { + legacyPermissionStates.forEach forEachPermission@{ (permissionName, legacyPermissionState) -> val permission = state.systemState.permissions[permissionName] if (permission == null) { Slog.w( - LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" + + LOG_TAG, + "Dropping unknown permission $permissionName for app ID $appId" + " when migrating permission state" ) return@forEachPermission } - permissionFlags[permissionName] = migratePermissionFlags( - permission, legacyPermissionState, appId, userId - ) + permissionFlags[permissionName] = + migratePermissionFlags(permission, legacyPermissionState, appId, userId) } val packageVersions = userState.mutatePackageVersions() - packageNames.forEachIndexed { _, packageName -> - packageVersions[packageName] = version - } + packageNames.forEachIndexed { _, packageName -> packageVersions[packageName] = version } } } @@ -109,29 +111,35 @@ class AppIdPermissionMigration { appId: Int, userId: Int ): Int { - var flags = when { - permission.isNormal -> if (legacyPermissionState.isGranted) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } - permission.isSignature || permission.isInternal -> - if (legacyPermissionState.isGranted) { - if (permission.isDevelopment || permission.isRole) { - PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + var flags = + when { + permission.isNormal -> + if (legacyPermissionState.isGranted) { + PermissionFlags.INSTALL_GRANTED } else { - PermissionFlags.PROTECTION_GRANTED + PermissionFlags.INSTALL_REVOKED } - } else { - 0 - } - permission.isRuntime -> - if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 - else -> 0 - } - flags = PermissionFlags.updateFlags( - permission, flags, legacyPermissionState.flags, legacyPermissionState.flags - ) + permission.isSignature || permission.isInternal -> + if (legacyPermissionState.isGranted) { + if (permission.isDevelopment || permission.isRole) { + PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + } else { + PermissionFlags.PROTECTION_GRANTED + } + } else { + 0 + } + permission.isRuntime -> + if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 + else -> 0 + } + flags = + PermissionFlags.updateFlags( + permission, + flags, + legacyPermissionState.flags, + legacyPermissionState.flags + ) if (DEBUG_MIGRATION) { val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags) val newFlagString = PermissionFlags.toString(flags) @@ -139,7 +147,8 @@ class AppIdPermissionMigration { val newGrantState = PermissionFlags.isPermissionGranted(flags) val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags) Slog.v( - LOG_TAG, "Migrated appId: $appId, permission: " + + LOG_TAG, + "Migrated appId: $appId, permission: " + "${permission.name}, user: $userId, oldGrantState: $oldGrantState" + ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " + "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch" diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt index 2c8175b585af..1f40f01ffef4 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt @@ -57,11 +57,12 @@ class AppIdPermissionPersistence { isPermissionTree: Boolean ) { val systemState = state.mutateSystemState(WriteMode.NONE) - val permissions = if (isPermissionTree) { - systemState.mutatePermissionTrees() - } else { - systemState.mutatePermissions() - } + val permissions = + if (isPermissionTree) { + systemState.mutatePermissionTrees() + } else { + systemState.mutatePermissions() + } forEachTag { when (val tagName = tagName) { TAG_PERMISSION -> parsePermission(permissions) @@ -71,10 +72,13 @@ class AppIdPermissionPersistence { permissions.forEachReversedIndexed { permissionIndex, _, permission -> val packageName = permission.packageName val externalState = state.externalState - if (packageName !in externalState.packageStates && - packageName !in externalState.disabledSystemPackageStates) { + if ( + packageName !in externalState.packageStates && + packageName !in externalState.disabledSystemPackageStates + ) { Slog.w( - LOG_TAG, "Dropping permission ${permission.name} from unknown package" + + LOG_TAG, + "Dropping permission ${permission.name} from unknown package" + " $packageName when parsing permissions" ) permissions.removeAt(permissionIndex) @@ -88,11 +92,12 @@ class AppIdPermissionPersistence { ) { val name = getAttributeValueOrThrow(ATTR_NAME).intern() @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - this.name = name - packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() - protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) - } + val permissionInfo = + PermissionInfo().apply { + this.name = name + packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() + protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) + } val type = getAttributeIntOrThrow(ATTR_TYPE) when (type) { Permission.TYPE_MANIFEST -> {} @@ -125,15 +130,14 @@ class AppIdPermissionPersistence { tagName: String, permissions: IndexedMap<String, Permission> ) { - tag(tagName) { - permissions.forEachIndexed { _, _, it -> serializePermission(it) } - } + tag(tagName) { permissions.forEachIndexed { _, _, it -> serializePermission(it) } } } private fun BinaryXmlSerializer.serializePermission(permission: Permission) { val type = permission.type when (type) { - Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {} + Permission.TYPE_MANIFEST, + Permission.TYPE_DYNAMIC -> {} Permission.TYPE_CONFIG -> return else -> { Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type") @@ -228,11 +232,12 @@ class AppIdPermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 345f101cbc14..08ba75397a09 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -55,7 +55,8 @@ class AppIdPermissionPolicy : SchemePolicy() { @Volatile private var onPermissionFlagsChangedListeners: - IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet() + IndexedListSet<OnPermissionFlagsChangedListener> = + MutableIndexedListSet() private val onPermissionFlagsChangedListenersLock = Any() private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>() @@ -73,30 +74,37 @@ class AppIdPermissionPolicy : SchemePolicy() { override fun MutateStateScope.onInitialized() { newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) -> val oldPermission = newState.systemState.permissions[permissionName] - val newPermission = if (oldPermission != null) { - if (permissionEntry.gids != null) { - oldPermission.copy( - gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser - ) - } else { - return@forEach - } - } else { - @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - name = permissionName - packageName = PLATFORM_PACKAGE_NAME - protectionLevel = PermissionInfo.PROTECTION_SIGNATURE - } - if (permissionEntry.gids != null) { - Permission( - permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids, - permissionEntry.perUser - ) + val newPermission = + if (oldPermission != null) { + if (permissionEntry.gids != null) { + oldPermission.copy( + gids = permissionEntry.gids, + areGidsPerUser = permissionEntry.perUser + ) + } else { + return@forEach + } } else { - Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + @Suppress("DEPRECATION") + val permissionInfo = + PermissionInfo().apply { + name = permissionName + packageName = PLATFORM_PACKAGE_NAME + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE + } + if (permissionEntry.gids != null) { + Permission( + permissionInfo, + false, + Permission.TYPE_CONFIG, + 0, + permissionEntry.gids, + permissionEntry.perUser + ) + } else { + Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + } } - } newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission } } @@ -200,30 +208,32 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isHardOrSoftRestricted) { return@forEach } - val isRequestedBySystemPackage = anyPackageInAppId(appId) { - it.isSystem && permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedBySystemPackage = + anyPackageInAppId(appId) { + it.isSystem && permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedBySystemPackage) { return@forEach } val oldFlags = getPermissionFlags(appId, userId, permissionName) var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -243,15 +253,15 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isRuntime || permission.isRemoved) { return@forEach } - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -260,13 +270,15 @@ class AppIdPermissionPolicy : SchemePolicy() { return@forEach } var newFlags = oldFlags - newFlags = if ( - newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT) - ) { - newFlags or PermissionFlags.RUNTIME_GRANTED - } else { - newFlags andInv PermissionFlags.RUNTIME_GRANTED - } + newFlags = + if ( + newFlags.hasBits(PermissionFlags.ROLE) || + newFlags.hasBits(PermissionFlags.PREGRANT) + ) { + newFlags or PermissionFlags.RUNTIME_GRANTED + } else { + newFlags andInv PermissionFlags.RUNTIME_GRANTED + } newFlags = newFlags andInv USER_SETTABLE_MASK if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) { newFlags = newFlags or PermissionFlags.IMPLICIT @@ -285,24 +297,32 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!canAdoptPermissions(packageName, originalPackageName)) { return@forEachIndexed } - newState.systemState.permissions.forEachIndexed permissions@ { - permissionIndex, permissionName, oldPermission -> + newState.systemState.permissions.forEachIndexed permissions@{ + permissionIndex, + permissionName, + oldPermission -> if (oldPermission.packageName != originalPackageName) { return@permissions } @Suppress("DEPRECATION") - val newPermissionInfo = PermissionInfo().apply { - name = oldPermission.permissionInfo.name - this.packageName = packageName - protectionLevel = oldPermission.permissionInfo.protectionLevel - } + val newPermissionInfo = + PermissionInfo().apply { + name = oldPermission.permissionInfo.name + this.packageName = packageName + protectionLevel = oldPermission.permissionInfo.protectionLevel + } // Different from the old implementation, which removes the GIDs upon permission // adoption, but adds them back on the next boot, we now just consistently keep the // GIDs. - val newPermission = oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = false, appId = 0 - ) - newState.mutateSystemState().mutatePermissions() + val newPermission = + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = false, + appId = 0 + ) + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, newPermission) changedPermissionNames += permissionName } @@ -313,18 +333,20 @@ class AppIdPermissionPolicy : SchemePolicy() { packageName: String, originalPackageName: String ): Boolean { - val originalPackageState = newState.externalState.packageStates[originalPackageName] - ?: return false + val originalPackageState = + newState.externalState.packageStates[originalPackageName] ?: return false if (!originalPackageState.isSystem) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package not in system partition" ) return false } if (originalPackageState.androidPackage != null) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package still exists" ) return false @@ -339,20 +361,25 @@ class AppIdPermissionPolicy : SchemePolicy() { val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp } if (isInstantApp) { Slog.w( - LOG_TAG, "Ignoring permission groups declared in package" + + LOG_TAG, + "Ignoring permission groups declared in package" + " ${packageState.packageName}: instant apps cannot declare permission groups" ) return } packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup -> - val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo( - parsedPermissionGroup, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionGroup = + PackageInfoUtils.generatePermissionGroupInfo( + parsedPermissionGroup, + PackageManager.GET_META_DATA.toLong() + )!! // TODO: Clear permission state on group take-over? val permissionGroupName = newPermissionGroup.name val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName] - if (oldPermissionGroup != null && - newPermissionGroup.packageName != oldPermissionGroup.packageName) { + if ( + oldPermissionGroup != null && + newPermissionGroup.packageName != oldPermissionGroup.packageName + ) { val newPackageName = newPermissionGroup.packageName val oldPackageName = oldPermissionGroup.packageName // Different from the old implementation, which defines permission group on @@ -361,7 +388,8 @@ class AppIdPermissionPolicy : SchemePolicy() { // to permissions so that we no longer need to rely on the scan order. if (!packageState.isSystem) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " package $newPackageName: already declared in another" + " package $oldPackageName" ) @@ -369,14 +397,16 @@ class AppIdPermissionPolicy : SchemePolicy() { } if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " system package $newPackageName: already declared in another" + " system package $oldPackageName" ) return@forEachIndexed } Slog.w( - LOG_TAG, "Overriding permission group $permissionGroupName with" + + LOG_TAG, + "Overriding permission group $permissionGroupName with" + " new declaration in system package $newPackageName: originally" + " declared in another package $oldPackageName" ) @@ -393,20 +423,23 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage!! // This may not be the same package as the old permission because the old permission owner // can be different, hence using this somewhat strange name to prevent misuse. - val oldNewPackage = oldState.externalState.packageStates[packageState.packageName] - ?.androidPackage - val isPackageSigningChanged = oldNewPackage != null && - androidPackage.signingDetails != oldNewPackage.signingDetails + val oldNewPackage = + oldState.externalState.packageStates[packageState.packageName]?.androidPackage + val isPackageSigningChanged = + oldNewPackage != null && androidPackage.signingDetails != oldNewPackage.signingDetails androidPackage.permissions.forEachIndexed { _, parsedPermission -> - val newPermissionInfo = PackageInfoUtils.generatePermissionInfo( - parsedPermission, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionInfo = + PackageInfoUtils.generatePermissionInfo( + parsedPermission, + PackageManager.GET_META_DATA.toLong() + )!! val permissionName = newPermissionInfo.name - val oldPermission = if (parsedPermission.isTree) { - newState.systemState.permissionTrees[permissionName] - } else { - newState.systemState.permissions[permissionName] - } + val oldPermission = + if (parsedPermission.isTree) { + newState.systemState.permissionTrees[permissionName] + } else { + newState.systemState.permissions[permissionName] + } // Different from the old implementation, which may add an (incomplete) signature // permission inside another package's permission tree, we now consistently ignore such // permissions. @@ -414,128 +447,152 @@ class AppIdPermissionPolicy : SchemePolicy() { val newPackageName = newPermissionInfo.packageName if (permissionTree != null && newPackageName != permissionTree.packageName) { Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + + LOG_TAG, + "Ignoring permission $permissionName declared in package" + " $newPackageName: base permission tree ${permissionTree.name} is" + " declared in another package ${permissionTree.packageName}" ) return@forEachIndexed } - val newPermission = if (oldPermission != null && - newPackageName != oldPermission.packageName) { - val oldPackageName = oldPermission.packageName - // Only allow system apps to redefine non-system permissions. - if (!packageState.isSystem) { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + - " $newPackageName: already declared in another package" + - " $oldPackageName" - ) - return@forEachIndexed - } - if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) { - // It's a config permission and has no owner, take ownership now. - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) - } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) { - Slog.w( - LOG_TAG, "Overriding permission $permissionName with new declaration in" + - " system package $newPackageName: originally declared in another" + - " package $oldPackageName" - ) - // Remove permission state on owner change. - newState.externalState.userIds.forEachIndexed { _, userId -> - newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - setPermissionFlags(appId, userId, permissionName, 0) - } + val newPermission = + if (oldPermission != null && newPackageName != oldPermission.packageName) { + val oldPackageName = oldPermission.packageName + // Only allow system apps to redefine non-system permissions. + if (!packageState.isSystem) { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in package" + + " $newPackageName: already declared in another package" + + " $oldPackageName" + ) + return@forEachIndexed } - // Different from the old implementation, which removes the GIDs upon permission - // override, but adds them back on the next boot, we now just consistently keep - // the GIDs. - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId, - oldPermission.gids, oldPermission.areGidsPerUser - ) - } else { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in system package" + - " $newPackageName: already declared in another system package" + - " $oldPackageName" - ) - return@forEachIndexed - } - } else { - if (oldPermission != null && oldPermission.isReconciled) { - val isPermissionGroupChanged = newPermissionInfo.isRuntime && - newPermissionInfo.group != null && - newPermissionInfo.group != oldPermission.groupName - val isPermissionProtectionChanged = - oldPermission.type != Permission.TYPE_CONFIG && ( - (newPermissionInfo.isRuntime && !oldPermission.isRuntime) || - (newPermissionInfo.isInternal && !oldPermission.isInternal) + if ( + oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled + ) { + // It's a config permission and has no owner, take ownership now. + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId ) - if (isPermissionGroupChanged || isPermissionProtectionChanged) { + } else if ( + newState.externalState.packageStates[oldPackageName]?.isSystem != true + ) { + Slog.w( + LOG_TAG, + "Overriding permission $permissionName with new declaration in" + + " system package $newPackageName: originally declared in another" + + " package $oldPackageName" + ) + // Remove permission state on owner change. newState.externalState.userIds.forEachIndexed { _, userId -> newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - if (isPermissionGroupChanged) { - // We might auto-grant permissions if any permission of - // the group is already granted. Hence if the group of - // a granted permission changes we need to revoke it to - // avoid having permissions of the new group auto-granted. - Slog.w( - LOG_TAG, "Revoking runtime permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " group changed from ${oldPermission.groupName}" + - " to ${newPermissionInfo.group}" - ) - } - if (isPermissionProtectionChanged) { - Slog.w( - LOG_TAG, "Revoking permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " protection changed." - ) - } setPermissionFlags(appId, userId, permissionName, 0) } } + // Different from the old implementation, which removes the GIDs upon + // permission + // override, but adds them back on the next boot, we now just consistently + // keep + // the GIDs. + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId, + oldPermission.gids, + oldPermission.areGidsPerUser + ) + } else { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in system package" + + " $newPackageName: already declared in another system package" + + " $oldPackageName" + ) + return@forEachIndexed } - } - - // Different from the old implementation, which doesn't update the permission - // definition upon app update, but does update it on the next boot, we now - // consistently update the permission definition upon app update. - @Suppress("IfThenToElvis") - if (oldPermission != null) { - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) } else { - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId - ) + if (oldPermission != null && oldPermission.isReconciled) { + val isPermissionGroupChanged = + newPermissionInfo.isRuntime && + newPermissionInfo.group != null && + newPermissionInfo.group != oldPermission.groupName + val isPermissionProtectionChanged = + oldPermission.type != Permission.TYPE_CONFIG && + ((newPermissionInfo.isRuntime && !oldPermission.isRuntime) || + (newPermissionInfo.isInternal && !oldPermission.isInternal)) + if (isPermissionGroupChanged || isPermissionProtectionChanged) { + newState.externalState.userIds.forEachIndexed { _, userId -> + newState.externalState.appIdPackageNames.forEachIndexed { + _, + appId, + _ -> + if (isPermissionGroupChanged) { + // We might auto-grant permissions if any permission of + // the group is already granted. Hence if the group of + // a granted permission changes we need to revoke it to + // avoid having permissions of the new group auto-granted. + Slog.w( + LOG_TAG, + "Revoking runtime permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " group changed from ${oldPermission.groupName}" + + " to ${newPermissionInfo.group}" + ) + } + if (isPermissionProtectionChanged) { + Slog.w( + LOG_TAG, + "Revoking permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " protection changed." + ) + } + setPermissionFlags(appId, userId, permissionName, 0) + } + } + } + } + + // Different from the old implementation, which doesn't update the permission + // definition upon app update, but does update it on the next boot, we now + // consistently update the permission definition upon app update. + @Suppress("IfThenToElvis") + if (oldPermission != null) { + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId + ) + } else { + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId + ) + } } - } if (parsedPermission.isTree) { newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission } else { newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission - val isPermissionChanged = oldPermission == null || - newPackageName != oldPermission.packageName || - newPermission.protectionLevel != oldPermission.protectionLevel || ( - oldPermission.isReconciled && ( - (newPermission.isSignature && isPackageSigningChanged) || ( - newPermission.isKnownSigner && - newPermission.knownCerts != oldPermission.knownCerts - ) || ( - newPermission.isRuntime && newPermission.groupName != null && - newPermission.groupName != oldPermission.groupName - ) - ) - ) + val isPermissionChanged = + oldPermission == null || + newPackageName != oldPermission.packageName || + newPermission.protectionLevel != oldPermission.protectionLevel || + (oldPermission.isReconciled && + ((newPermission.isSignature && isPackageSigningChanged) || + (newPermission.isKnownSigner && + newPermission.knownCerts != oldPermission.knownCerts) || + (newPermission.isRuntime && + newPermission.groupName != null && + newPermission.groupName != oldPermission.groupName))) if (isPermissionChanged) { changedPermissionNames += permissionName } @@ -552,39 +609,47 @@ class AppIdPermissionPolicy : SchemePolicy() { if (packageState != null && androidPackage == null) { return } - val disabledSystemPackage = newState.externalState.disabledSystemPackageStates[packageName] - ?.androidPackage + val disabledSystemPackage = + newState.externalState.disabledSystemPackageStates[packageName]?.androidPackage // Unlike in the previous implementation, we now also retain permission trees defined by // disabled system packages for consistency with permissions. newState.systemState.permissionTrees.forEachReversedIndexed { - permissionTreeIndex, permissionTreeName, permissionTree -> - if (permissionTree.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } != true - )) { + permissionTreeIndex, + permissionTreeName, + permissionTree -> + if ( + permissionTree.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + it.isTree && it.name == permissionTreeName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + it.isTree && it.name == permissionTreeName + } != true) + ) { newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex) } } newState.systemState.permissions.forEachReversedIndexed { - permissionIndex, permissionName, permission -> + permissionIndex, + permissionName, + permission -> val updatedPermission = updatePermissionIfDynamic(permission) - newState.mutateSystemState().mutatePermissions() + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, updatedPermission) - if (updatedPermission.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - !it.isTree && it.name == permissionName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - !it.isTree && it.name == permissionName - } != true - )) { + if ( + updatedPermission.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + !it.isTree && it.name == permissionName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + !it.isTree && it.name == permissionName + } != true) + ) { // Different from the old implementation where we keep the permission state if the // permission is declared by a disabled system package (ag/15189282), we now // shouldn't be notified when the updated system package is removed but the disabled @@ -608,9 +673,12 @@ class AppIdPermissionPolicy : SchemePolicy() { val permissionTree = findPermissionTree(permission.name) ?: return permission @Suppress("DEPRECATION") return permission.copy( - permissionInfo = PermissionInfo(permission.permissionInfo).apply { - packageName = permissionTree.packageName - }, appId = permissionTree.appId, isReconciled = true + permissionInfo = + PermissionInfo(permission.permissionInfo).apply { + packageName = permissionTree.packageName + }, + appId = permissionTree.appId, + isReconciled = true ) } @@ -636,8 +704,9 @@ class AppIdPermissionPolicy : SchemePolicy() { } private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) { - val hasOldPackage = appId in oldState.externalState.appIdPackageNames && - anyPackageInAppId(appId, oldState) { true } + val hasOldPackage = + appId in oldState.externalState.appIdPackageNames && + anyPackageInAppId(appId, oldState) { true } if (!hasOldPackage) { // Don't revoke anything if this isn't a package update, i.e. if information about the // old package isn't available. Notably, this also means skipping packages changed via @@ -650,46 +719,58 @@ class AppIdPermissionPolicy : SchemePolicy() { // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. val oldTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } val newTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q && - newTargetSdkVersion < Build.VERSION_CODES.Q + val isTargetSdkVersionDowngraded = + oldTargetSdkVersion >= Build.VERSION_CODES.Q && + newTargetSdkVersion < Build.VERSION_CODES.Q @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q && - newTargetSdkVersion >= Build.VERSION_CODES.Q - val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded && - !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage - val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage || - isTargetSdkVersionDowngraded + val isTargetSdkVersionUpgraded = + oldTargetSdkVersion < Build.VERSION_CODES.Q && + newTargetSdkVersion >= Build.VERSION_CODES.Q + val oldIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, oldState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val newIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, newState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val isNewlyRequestingLegacyExternalStorage = + !isTargetSdkVersionUpgraded && + !oldIsRequestLegacyExternalStorage && + newIsRequestLegacyExternalStorage + val shouldRevokeStorageAndMediaPermissions = + isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded if (shouldRevokeStorageAndMediaPermissions) { newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdPermissionFlags[appId]?.forEachReversedIndexed { - _, permissionName, oldFlags -> + _, + permissionName, + oldFlags -> // Do not revoke the permission during an upgrade if it's POLICY_FIXED or // SYSTEM_FIXED. Otherwise the user cannot grant back the permission. - if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS && - oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && - !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) { + if ( + permissionName in STORAGE_AND_MEDIA_PERMISSIONS && + oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && + !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK) + ) { Slog.v( - LOG_TAG, "Revoking storage permission: $permissionName for appId: " + + LOG_TAG, + "Revoking storage permission: $permissionName for appId: " + " $appId and user: $userId" ) - val newFlags = oldFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK - ) + val newFlags = + oldFlags andInv (PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK) setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -704,9 +785,10 @@ class AppIdPermissionPolicy : SchemePolicy() { val externalState = newState.externalState externalState.userIds.forEachIndexed { _, userId -> externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - val isPermissionRequested = anyPackageInAppId(appId) { - permissionName in it.androidPackage!!.requestedPermissions - } + val isPermissionRequested = + anyPackageInAppId(appId) { + permissionName in it.androidPackage!!.requestedPermissions + } if (isPermissionRequested) { evaluatePermissionState(appId, userId, permissionName, installedPackageState) } @@ -720,7 +802,9 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { newState.externalState.userIds.forEachIndexed { _, userId -> evaluateAllPermissionStatesForPackageAndUser( - packageState, userId, installedPackageState + packageState, + userId, + installedPackageState ) } } @@ -732,7 +816,10 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { packageState.androidPackage?.requestedPermissions?.forEach { permissionName -> evaluatePermissionState( - packageState.appId, userId, permissionName, installedPackageState + packageState.appId, + userId, + permissionName, + installedPackageState ) } } @@ -779,57 +866,71 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) if (!wasGranted) { val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) - val isRequestedByInstalledPackage = installedPackageState != null && - permissionName in installedPackageState.androidPackage!!.requestedPermissions + val isRequestedByInstalledPackage = + installedPackageState != null && + permissionName in + installedPackageState.androidPackage!!.requestedPermissions val isRequestedBySystemPackage = requestingPackageStates.anyIndexed { _, it -> it.isSystem } - val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it -> - isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) - } + val isCompatibilityPermission = + requestingPackageStates.anyIndexed { _, it -> + isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) + } // If this is an existing, non-system package, // then we can't add any new permissions to it. // Except if this is a permission that was added to the platform - var newFlags = if (!wasRevoked || isRequestedByInstalledPackage || - isRequestedBySystemPackage || isCompatibilityPermission) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } + var newFlags = + if ( + !wasRevoked || + isRequestedByInstalledPackage || + isRequestedBySystemPackage || + isCompatibilityPermission + ) { + PermissionFlags.INSTALL_GRANTED + } else { + PermissionFlags.INSTALL_REVOKED + } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) - var newFlags = if (hasMissingPackage && wasProtectionGranted) { - // Keep the non-runtime permission grants for shared UID with missing androidPackage - PermissionFlags.PROTECTION_GRANTED - } else { - val mayGrantByPrivileged = !permission.isPrivileged || - requestingPackageStates.anyIndexed { _, it -> - checkPrivilegedPermissionAllowlist(it, permission) - } - val shouldGrantBySignature = permission.isSignature && - requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionBySignature(it, permission) - } - val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionByProtectionFlags(it, permission) - } - if (mayGrantByPrivileged && - (shouldGrantBySignature || shouldGrantByProtectionFlags)) { + var newFlags = + if (hasMissingPackage && wasProtectionGranted) { + // Keep the non-runtime permission grants for shared UID with missing + // androidPackage PermissionFlags.PROTECTION_GRANTED } else { - 0 + val mayGrantByPrivileged = + !permission.isPrivileged || + requestingPackageStates.anyIndexed { _, it -> + checkPrivilegedPermissionAllowlist(it, permission) + } + val shouldGrantBySignature = + permission.isSignature && + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionBySignature(it, permission) + } + val shouldGrantByProtectionFlags = + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionByProtectionFlags(it, permission) + } + if ( + mayGrantByPrivileged && + (shouldGrantBySignature || shouldGrantByProtectionFlags) + ) { + PermissionFlags.PROTECTION_GRANTED + } else { + 0 + } } - } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } // Different from the old implementation, which seemingly allows granting an // unallowlisted privileged permission via development or role but revokes it upon next @@ -840,9 +941,9 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED) } if (permission.isRole) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)) } setPermissionFlags(appId, userId, permissionName, newFlags) } else if (permission.isRuntime) { @@ -850,7 +951,9 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags) val targetSdkVersion = requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) { - targetSdkVersion, _, packageState -> + targetSdkVersion, + _, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } if (targetSdkVersion < Build.VERSION_CODES.M) { @@ -883,23 +986,27 @@ class AppIdPermissionPolicy : SchemePolicy() { } } val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED) - val isLeanbackNotificationsPermission = newState.externalState.isLeanback && - permissionName in NOTIFICATIONS_PERMISSIONS - val isImplicitPermission = requestingPackageStates.anyIndexed { _, it -> - permissionName in it.androidPackage!!.implicitPermissions - } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[permissionName] - val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed { - _, sourcePermissionName -> - val sourcePermission = newState.systemState.permissions[sourcePermissionName] - checkNotNull(sourcePermission) { - "Unknown source permission $sourcePermissionName in split permissions" + val isLeanbackNotificationsPermission = + newState.externalState.isLeanback && permissionName in NOTIFICATIONS_PERMISSIONS + val isImplicitPermission = + requestingPackageStates.anyIndexed { _, it -> + permissionName in it.androidPackage!!.implicitPermissions + } + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[permissionName] + val isAnySourcePermissionNonRuntime = + sourcePermissions?.anyIndexed { _, sourcePermissionName -> + val sourcePermission = + newState.systemState.permissions[sourcePermissionName] + checkNotNull(sourcePermission) { + "Unknown source permission $sourcePermissionName in split permissions" + } + !sourcePermission.isRuntime } - !sourcePermission.isRuntime - } ?: false - val shouldGrantByImplicit = isLeanbackNotificationsPermission || - (isImplicitPermission && isAnySourcePermissionNonRuntime) + ?: false + val shouldGrantByImplicit = + isLeanbackNotificationsPermission || + (isImplicitPermission && isAnySourcePermissionNonRuntime) if (shouldGrantByImplicit) { newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED if (wasRevoked) { @@ -907,26 +1014,31 @@ class AppIdPermissionPolicy : SchemePolicy() { } } else { newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED - if ((wasGrantedByLegacy || wasGrantedByImplicit) && - newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)) { + if ( + (wasGrantedByLegacy || wasGrantedByImplicit) && + newFlags.hasBits(PermissionFlags.APP_OP_REVOKED) + ) { // The permission was granted from a compatibility grant or an implicit // grant, however this flag might still be set if the user denied this // permission in the settings. Hence upon app upgrade and when this // permission is no longer LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke // the permission, we want to remove this flag so that the app can request // the permission again. - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED) } } if (!isImplicitPermission && hasImplicitFlag) { newFlags = newFlags andInv PermissionFlags.IMPLICIT var shouldRetainAsNearbyDevices = false if (permissionName in NEARBY_DEVICES_PERMISSIONS) { - val accessBackgroundLocationFlags = getPermissionFlags( - appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION - ) + val accessBackgroundLocationFlags = + getPermissionFlags( + appId, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION + ) shouldRetainAsNearbyDevices = PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) && !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT) @@ -937,46 +1049,57 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED } } else { - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or - PermissionFlags.USER_FIXED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED) } } } val wasExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) val wasRestricted = newFlags.hasAnyBit(PermissionFlags.MASK_RESTRICTED) - val isExempt = if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { - // All restricted permissions start as exempt. If there's an installer for the - // package, we will drop this UPGRADE_EXEMPT flag when we receive the - // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. - // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag that - // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, and to - // apps with missing permission state. - // This way we make sure both pre-installed apps, and apps updated/installed after - // a rollback snapshot is taken, can get the allowlist for permissions that won't be - // allowlisted otherwise. - newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT - true - } else { - wasExempt - } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + val isExempt = + if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { + // All restricted permissions start as exempt. If there's an installer for the + // package, we will drop this UPGRADE_EXEMPT flag when we receive the + // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. + // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag + // that + // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, + // and to + // apps with missing permission state. + // This way we make sure both pre-installed apps, and apps updated/installed + // after + // a rollback snapshot is taken, can get the allowlist for permissions that + // won't be + // allowlisted otherwise. + newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT + true + } else { + wasExempt + } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } else { - Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" + - "for permission ${permission.name} while evaluating permission state" + - "for appId $appId and userId $userId") + Slog.e( + LOG_TAG, + "Unknown protection level ${permission.protectionLevel}" + + "for permission ${permission.name} while evaluating permission state" + + "for appId $appId and userId $userId" + ) } } @@ -985,7 +1108,7 @@ class AppIdPermissionPolicy : SchemePolicy() { forEachPackageInAppId(appId) { implicitPermissions += it.androidPackage!!.implicitPermissions } - implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName -> + implicitPermissions.forEachIndexed implicitPermissions@{ _, implicitPermissionName -> val implicitPermission = newState.systemState.permissions[implicitPermissionName] checkNotNull(implicitPermission) { "Unknown implicit permission $implicitPermissionName in split permissions" @@ -999,10 +1122,11 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!isNewPermission) { return@implicitPermissions } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[implicitPermissionName] + ?: return@implicitPermissions var newFlags = getPermissionFlags(appId, userId, implicitPermissionName) - sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName -> + sourcePermissions.forEachIndexed sourcePermissions@{ _, sourcePermissionName -> val sourcePermission = newState.systemState.permissions[sourcePermissionName] checkNotNull(sourcePermission) { "Unknown source permission $sourcePermissionName in split permissions" @@ -1032,11 +1156,14 @@ class AppIdPermissionPolicy : SchemePolicy() { permissionName: String ): Boolean { for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) { - if (compatibilityPermission.name == permissionName && - androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) { + if ( + compatibilityPermission.name == permissionName && + androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion + ) { Slog.i( - LOG_TAG, "Auto-granting $permissionName to old package" + - " ${androidPackage.packageName}" + LOG_TAG, + "Auto-granting $permissionName to old package" + + " ${androidPackage.packageName}" ) return true } @@ -1058,15 +1185,23 @@ class AppIdPermissionPolicy : SchemePolicy() { // and the defining package still trusts the old certificate for permissions // - or it shares the above relationships with the system package val packageSigningDetails = packageState.androidPackage!!.signingDetails - val sourceSigningDetails = newState.externalState - .packageStates[permission.packageName]?.androidPackage?.signingDetails - val platformSigningDetails = newState.externalState - .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails - return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) == true || + val sourceSigningDetails = + newState.externalState.packageStates[permission.packageName] + ?.androidPackage + ?.signingDetails + val platformSigningDetails = + newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!! + .androidPackage!! + .signingDetails + return sourceSigningDetails?.hasCommonSignerWithCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) == true || packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || - platformSigningDetails.checkCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) + platformSigningDetails.checkCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) } private fun MutateStateScope.checkPrivilegedPermissionAllowlist( @@ -1082,8 +1217,9 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!(packageState.isSystem && packageState.isPrivileged)) { return true } - if (permission.packageName !in - newState.externalState.privilegedPermissionAllowlistPackages) { + if ( + permission.packageName !in newState.externalState.privilegedPermissionAllowlistPackages + ) { return true } val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name) @@ -1099,13 +1235,15 @@ class AppIdPermissionPolicy : SchemePolicy() { // Apps that are in updated apex's do not need to be allowlisted if (!packageState.isApkInUpdatedApex) { Slog.w( - LOG_TAG, "Privileged permission ${permission.name} for package" + - " ${packageState.packageName} (${packageState.path}) not in" + - " privileged permission allowlist" + LOG_TAG, + "Privileged permission ${permission.name} for package" + + " ${packageState.packageName} (${packageState.path}) not in" + + " privileged permission allowlist" ) if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - privilegedPermissionAllowlistViolations += "${packageState.packageName}" + - " (${packageState.path}): ${permission.name}" + privilegedPermissionAllowlistViolations += + "${packageState.packageName}" + + " (${packageState.path}): ${permission.name}" } } } @@ -1124,32 +1262,40 @@ class AppIdPermissionPolicy : SchemePolicy() { val apexModuleName = packageState.apexModuleName val packageName = packageState.packageName return when { - packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState( - packageName, permissionName - ) - packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState( - packageName, permissionName - ) + packageState.isVendor -> + permissionAllowlist.getVendorPrivilegedAppAllowlistState( + packageName, + permissionName + ) + packageState.isProduct -> + permissionAllowlist.getProductPrivilegedAppAllowlistState( + packageName, + permissionName + ) packageState.isSystemExt -> permissionAllowlist.getSystemExtPrivilegedAppAllowlistState( - packageName, permissionName + packageName, + permissionName ) apexModuleName != null -> { - val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState( - packageName, permissionName - ) + val nonApexAllowlistState = + permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) if (nonApexAllowlistState != null) { // TODO(andreionea): Remove check as soon as all apk-in-apex // permission allowlists are migrated. Slog.w( - LOG_TAG, "Package $packageName is an APK in APEX but has permission" + + LOG_TAG, + "Package $packageName is an APK in APEX but has permission" + " allowlist on the system image, please bundle the allowlist in the" + " $apexModuleName APEX instead" ) } - val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState( - apexModuleName, packageName, permissionName - ) + val apexAllowlistState = + permissionAllowlist.getApexPrivilegedAppAllowlistState( + apexModuleName, + packageName, + permissionName + ) apexAllowlistState ?: nonApexAllowlistState } else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) @@ -1208,18 +1354,19 @@ class AppIdPermissionPolicy : SchemePolicy() { val knownPackages = newState.externalState.knownPackages val packageName = packageState.packageName if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) { - val shouldGrant = if (packageState.isUpdatedSystemApp) { - // For updated system applications, a privileged/oem permission - // is granted only if it had been defined by the original application. - val disabledSystemPackageState = newState.externalState - .disabledSystemPackageStates[packageState.packageName] - val disabledSystemPackage = disabledSystemPackageState?.androidPackage - disabledSystemPackage != null && - permission.name in disabledSystemPackage.requestedPermissions && - shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) - } else { - shouldGrantPrivilegedOrOemPermission(packageState, permission) - } + val shouldGrant = + if (packageState.isUpdatedSystemApp) { + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + val disabledSystemPackageState = + newState.externalState.disabledSystemPackageStates[packageState.packageName] + val disabledSystemPackage = disabledSystemPackageState?.androidPackage + disabledSystemPackage != null && + permission.name in disabledSystemPackage.requestedPermissions && + shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) + } else { + shouldGrantPrivilegedOrOemPermission(packageState, permission) + } if (shouldGrant) { return true } @@ -1230,16 +1377,18 @@ class AppIdPermissionPolicy : SchemePolicy() { // we still want to blindly grant it to old apps. return true } - if (permission.isInstaller && ( - packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || - packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!! - )) { + if ( + permission.isInstaller && + (packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || + packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!) + ) { // If this permission is to be granted to the system installer and // this app is an installer or permission controller, then it gets the permission. return true } - if (permission.isVerifier && - packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) { + if ( + permission.isVerifier && packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!! + ) { // If this permission is to be granted to the system verifier and // this app is a verifier, then it gets the permission. return true @@ -1248,53 +1397,67 @@ class AppIdPermissionPolicy : SchemePolicy() { // Any pre-installed system app is allowed to get this permission. return true } - if (permission.isKnownSigner && - androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) { + if ( + permission.isKnownSigner && + androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts) + ) { // If the permission is to be granted to a known signer then check if any of this // app's signing certificates are in the trusted certificate digest Set. return true } - if (permission.isSetup && - packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) { + if ( + permission.isSetup && packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!! + ) { // If this permission is to be granted to the system setup wizard and // this app is a setup wizard, then it gets the permission. return true } - if (permission.isSystemTextClassifier && - packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) { + if ( + permission.isSystemTextClassifier && + packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!! + ) { // Special permissions for the system default text classifier. return true } - if (permission.isConfigurator && - packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) { + if ( + permission.isConfigurator && + packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!! + ) { // Special permissions for the device configurator. return true } - if (permission.isIncidentReportApprover && - packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) { + if ( + permission.isIncidentReportApprover && + packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!! + ) { // If this permission is to be granted to the incident report approver and // this app is the incident report approver, then it gets the permission. return true } - if (permission.isAppPredictor && - packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) { + if ( + permission.isAppPredictor && + packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!! + ) { // Special permissions for the system app predictor. return true } - if (permission.isCompanion && - packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) { + if ( + permission.isCompanion && + packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!! + ) { // Special permissions for the system companion device manager. return true } - if (permission.isRetailDemo && - packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) { + if ( + permission.isRetailDemo && + packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!! + ) { // Special permission granted only to the OEM specified retail demo app. // Note that the original code was passing app ID as UID, so this behavior is kept // unchanged. return true } - if (permission.isRecents && - packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { + if (permission.isRecents && packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { // Special permission for the recents app. return true } @@ -1319,9 +1482,10 @@ class AppIdPermissionPolicy : SchemePolicy() { // flag. if (packageState.isVendor && !permission.isVendorPrivileged) { Slog.w( - LOG_TAG, "Permission $permissionName cannot be granted to privileged" + - " vendor app $packageName because it isn't a vendorPrivileged" + - " permission" + LOG_TAG, + "Permission $permissionName cannot be granted to privileged" + + " vendor app $packageName because it isn't a vendorPrivileged" + + " permission" ) return false } @@ -1330,8 +1494,11 @@ class AppIdPermissionPolicy : SchemePolicy() { } permission.isOem -> { if (packageState.isOem) { - val allowlistState = newState.externalState.permissionAllowlist - .getOemAppAllowlistState(packageName, permissionName) + val allowlistState = + newState.externalState.permissionAllowlist.getOemAppAllowlistState( + packageName, + permissionName + ) checkNotNull(allowlistState) { "OEM permission $permissionName requested by package" + " $packageName must be explicitly declared granted or not" @@ -1358,13 +1525,18 @@ class AppIdPermissionPolicy : SchemePolicy() { val appId = externalState.packageStates[packageName]?.appId ?: continue newState.userStates.forEachIndexed { _, userId, _ -> evaluatePermissionState( - appId, userId, Manifest.permission.PACKAGE_USAGE_STATS, null + appId, + userId, + Manifest.permission.PACKAGE_USAGE_STATS, + null ) } } if (!privilegedPermissionAllowlistViolations.isEmpty()) { - throw IllegalStateException("Signature|privileged permissions not in privileged" + - " permission allowlist: $privilegedPermissionAllowlistViolations") + throw IllegalStateException( + "Signature|privileged permissions not in privileged" + + " permission allowlist: $privilegedPermissionAllowlistViolations" + ) } } @@ -1389,10 +1561,14 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.findPermissionTree(permissionName: String): Permission? = state.systemState.permissionTrees.firstNotNullOfOrNullIndexed { - _, permissionTreeName, permissionTree -> - if (permissionName.startsWith(permissionTreeName) && - permissionName.length > permissionTreeName.length && - permissionName[permissionTreeName.length] == '.') { + _, + permissionTreeName, + permissionTree -> + if ( + permissionName.startsWith(permissionTreeName) && + permissionName.length > permissionTreeName.length && + permissionName[permissionTreeName.length] == '.' + ) { permissionTree } else { null @@ -1403,15 +1579,11 @@ class AppIdPermissionPolicy : SchemePolicy() { newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission } - /** - * returns all permission group definitions available in the system - */ + /** returns all permission group definitions available in the system */ fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> = state.systemState.permissionGroups - /** - * returns all permission definitions available in the system - */ + /** returns all permission definitions available in the system */ fun GetStateScope.getPermissions(): IndexedMap<String, Permission> = state.systemState.permissions @@ -1430,11 +1602,8 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? = state.userStates[userId]?.appIdPermissionFlags?.get(appId) - fun GetStateScope.getPermissionFlags( - appId: Int, - userId: Int, - permissionName: String - ): Int = getPermissionFlags(state, appId, userId, permissionName) + fun GetStateScope.getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int = + getPermissionFlags(state, appId, userId, permissionName) private fun MutateStateScope.getOldStatePermissionFlags( appId: Int, @@ -1465,8 +1634,10 @@ class AppIdPermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { - val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId] - .getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdPermissionFlags[appId] + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false @@ -1517,38 +1688,37 @@ class AppIdPermissionPolicy : SchemePolicy() { private const val PLATFORM_PACKAGE_NAME = "android" // A set of permissions that we don't want to revoke when they are no longer implicit. - private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.ACTIVITY_RECOGNITION, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - ) - - private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf( - Manifest.permission.BLUETOOTH_ADVERTISE, - Manifest.permission.BLUETOOTH_CONNECT, - Manifest.permission.BLUETOOTH_SCAN, - Manifest.permission.NEARBY_WIFI_DEVICES - ) + private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.ACTIVITY_RECOGNITION, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + ) - private val NOTIFICATIONS_PERMISSIONS = indexedSetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NEARBY_DEVICES_PERMISSIONS = + indexedSetOf( + Manifest.permission.BLUETOOTH_ADVERTISE, + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.NEARBY_WIFI_DEVICES + ) - private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(Manifest.permission.POST_NOTIFICATIONS) + + private val STORAGE_AND_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) - /** - * Mask for all permission flags that can be set by the user - */ + /** Mask for all permission flags that can be set by the user */ private const val USER_SETTABLE_MASK = PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or @@ -1558,16 +1728,14 @@ class AppIdPermissionPolicy : SchemePolicy() { PermissionFlags.USER_SELECTED /** - * Mask for all permission flags that imply we shouldn't automatically modify the - * permission grant state. + * Mask for all permission flags that imply we shouldn't automatically modify the permission + * grant state. */ private const val SYSTEM_OR_POLICY_FIXED_MASK = PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED } - /** - * Listener for permission flags changes. - */ + /** Listener for permission flags changes. */ abstract class OnPermissionFlagsChangedListener { /** * Called when a permission flags change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt index b644d8fe7388..edacda03f277 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt @@ -32,7 +32,6 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { * Upgrade the package permissions, if needed. * * @param version package version - * * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion] */ fun MutateStateScope.upgradePackageState( @@ -43,7 +42,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { val packageName = packageState.packageName if (version <= 3) { Slog.v( - LOG_TAG, "Allowlisting and upgrading background location permission for " + + LOG_TAG, + "Allowlisting and upgrading background location permission for " + "package: $packageName, version: $version, user:$userId" ) allowlistRestrictedPermissions(packageState, userId) @@ -51,7 +51,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } if (version <= 10) { Slog.v( - LOG_TAG, "Upgrading access media location permission for package: $packageName" + + LOG_TAG, + "Upgrading access media location permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeAccessMediaLocationPermission(packageState, userId) @@ -59,7 +60,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastT check, when moving subsystem to mainline. if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) { Slog.v( - LOG_TAG, "Upgrading scoped permissions for package: $packageName" + + LOG_TAG, + "Upgrading scoped permissions for package: $packageName" + ", version: $version, user: $userId" ) upgradeAuralVisualMediaPermissions(packageState, userId) @@ -67,7 +69,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastU check, when moving subsystem to mainline. if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) { Slog.v( - LOG_TAG, "Upgrading visual media permission for package: $packageName" + + LOG_TAG, + "Upgrading visual media permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeUserSelectedVisualMediaPermission(packageState, userId) @@ -84,8 +87,11 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { if (permissionName in LEGACY_RESTRICTED_PERMISSIONS) { with(policy) { updatePermissionFlags( - packageState.appId, userId, permissionName, - PermissionFlags.UPGRADE_EXEMPT, PermissionFlags.UPGRADE_EXEMPT + packageState.appId, + userId, + permissionName, + PermissionFlags.UPGRADE_EXEMPT, + PermissionFlags.UPGRADE_EXEMPT ) } } @@ -96,21 +102,27 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in - packageState.androidPackage!!.requestedPermissions) { + if ( + Manifest.permission.ACCESS_BACKGROUND_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { val appId = packageState.appId - val accessFineLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) - } - val accessCoarseLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) - } + val accessFineLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) + } + val accessCoarseLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) + } val isForegroundLocationGranted = PermissionFlags.isAppOpGranted(accessFineLocationFlags) || PermissionFlags.isAppOpGranted(accessCoarseLocationFlags) if (isForegroundLocationGranted) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION ) } } @@ -120,24 +132,29 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_MEDIA_LOCATION in - packageState.androidPackage!!.requestedPermissions) { - val flags = with(policy) { - getPermissionFlags( - packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE - ) - } + if ( + Manifest.permission.ACCESS_MEDIA_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { + val flags = + with(policy) { + getPermissionFlags( + packageState.appId, + userId, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + } if (PermissionFlags.isAppOpGranted(flags)) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_MEDIA_LOCATION ) } } } - /** - * Upgrade permissions based on storage permissions grant - */ + /** Upgrade permissions based on storage permissions grant */ private fun MutateStateScope.upgradeAuralVisualMediaPermissions( packageState: PackageState, userId: Int @@ -147,15 +164,15 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isStorageUserGranted = + STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isStorageUserGranted) { AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName -> if (permissionName in requestedPermissionNames) { @@ -165,9 +182,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } } - /** - * Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] - */ + /** Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] */ private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission( packageState: PackageState, userId: Int @@ -177,19 +192,21 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isVisualMediaUserGranted = VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isVisualMediaUserGranted = + VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isVisualMediaUserGranted) { if (Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED in requestedPermissionNames) { grantRuntimePermission( - packageState, userId, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + packageState, + userId, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED ) } } @@ -201,7 +218,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { permissionName: String ) { Slog.v( - LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " + + LOG_TAG, + "Granting runtime permission for package: ${packageState.packageName}, " + "permission: $permissionName, userId: $userId" ) val permission = newState.systemState.permissions[permissionName]!! @@ -220,13 +238,13 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } flags = flags or PermissionFlags.RUNTIME_GRANTED - flags = flags andInv ( - PermissionFlags.APP_OP_REVOKED or - PermissionFlags.IMPLICIT or - PermissionFlags.LEGACY_GRANTED or - PermissionFlags.HIBERNATION or - PermissionFlags.ONE_TIME - ) + flags = + flags andInv + (PermissionFlags.APP_OP_REVOKED or + PermissionFlags.IMPLICIT or + PermissionFlags.LEGACY_GRANTED or + PermissionFlags.HIBERNATION or + PermissionFlags.ONE_TIME) with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -234,39 +252,45 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { private val LOG_TAG = AppIdPermissionUpgrade::class.java.simpleName private const val MASK_ANY_FIXED = - PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or - PermissionFlags.POLICY_FIXED or PermissionFlags.SYSTEM_FIXED + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.SYSTEM_FIXED - private val LEGACY_RESTRICTED_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_BACKGROUND_LOCATION, - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SEND_SMS, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.RECEIVE_WAP_PUSH, - Manifest.permission.RECEIVE_MMS, - Manifest.permission.READ_CELL_BROADCASTS, - Manifest.permission.READ_CALL_LOG, - Manifest.permission.WRITE_CALL_LOG, - Manifest.permission.PROCESS_OUTGOING_CALLS - ) + private val LEGACY_RESTRICTED_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_BACKGROUND_LOCATION, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.RECEIVE_WAP_PUSH, + Manifest.permission.RECEIVE_MMS, + Manifest.permission.READ_CELL_BROADCASTS, + Manifest.permission.READ_CALL_LOG, + Manifest.permission.WRITE_CALL_LOG, + Manifest.permission.PROCESS_OUTGOING_CALLS + ) - private val STORAGE_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - private val AURAL_VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val STORAGE_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + private val AURAL_VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) // Visual media permissions in T - private val VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION - ) + private val VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION + ) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt index 37a4a90f8f80..1bee356dfbf3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt @@ -135,9 +135,7 @@ class DevicePermissionPersistence { ) { tag(TAG_DEVICE) { attributeInterned(ATTR_ID, deviceId) - permissionFlags.forEachIndexed { _, name, flags -> - serializePermission(name, flags) - } + permissionFlags.forEachIndexed { _, name, flags -> serializePermission(name, flags) } } } @@ -145,11 +143,12 @@ class DevicePermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } @@ -166,4 +165,4 @@ class DevicePermissionPersistence { private const val ATTR_NAME = "name" private const val ATTR_FLAGS = "flags" } -}
\ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 240585c000fd..15a58593432e 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -53,8 +53,8 @@ class DevicePermissionPolicy : SchemePolicy() { override fun MutateStateScope.onAppIdRemoved(appId: Int) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (appId in userState.appIdDevicePermissionFlags) { - newState.mutateUserStateAt(userStateIndex) - .mutateAppIdDevicePermissionFlags() -= appId + newState.mutateUserStateAt(userStateIndex).mutateAppIdDevicePermissionFlags() -= + appId } } } @@ -85,10 +85,10 @@ class DevicePermissionPolicy : SchemePolicy() { appId: Int, userId: Int ) { - resetPermissionStates(packageName, userId) + resetRuntimePermissions(packageName, userId) } - private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) { + fun MutateStateScope.resetRuntimePermissions(packageName: String, userId: Int) { // It's okay to skip resetting permissions for packages that are removed, // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved() val packageState = newState.externalState.packageStates[packageName] ?: return @@ -96,10 +96,11 @@ class DevicePermissionPolicy : SchemePolicy() { val appId = packageState.appId val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags androidPackage.requestedPermissions.forEach { permissionName -> - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -116,7 +117,9 @@ class DevicePermissionPolicy : SchemePolicy() { } newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed { - _, deviceId, permissionFlags -> + _, + deviceId, + permissionFlags -> permissionFlags.forEachReversedIndexed { _, permissionName, _ -> if (permissionName !in requestedPermissions) { setPermissionFlags(appId, deviceId, userId, permissionName, 0) @@ -166,11 +169,17 @@ class DevicePermissionPolicy : SchemePolicy() { userId: Int, permissionName: String ): Int { - val flags = state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId) - ?.getWithDefault(permissionName, 0) ?: 0 + val flags = + state.userStates[userId] + ?.appIdDevicePermissionFlags + ?.get(appId) + ?.get(deviceId) + ?.getWithDefault(permissionName, 0) + ?: 0 if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "getPermissionFlags: appId=$appId, userId=$userId," + + LOG_TAG, + "getPermissionFlags: appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " flags=${PermissionFlags.toString(flags)}" ) @@ -186,7 +195,12 @@ class DevicePermissionPolicy : SchemePolicy() { flags: Int ): Boolean = updatePermissionFlags( - appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags + appId, + deviceId, + userId, + permissionName, + PermissionFlags.MASK_ALL, + flags ) private fun MutateStateScope.updatePermissionFlags( @@ -201,20 +215,23 @@ class DevicePermissionPolicy : SchemePolicy() { Slog.w(LOG_TAG, "$permissionName is not a device aware permission.") return false } - val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId] - ?.get(deviceId).getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdDevicePermissionFlags[appId] + ?.get(deviceId) + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false } val appIdDevicePermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags() - val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) { - MutableIndexedReferenceMap() - } + val devicePermissionFlags = + appIdDevicePermissionFlags.mutateOrPut(appId) { MutableIndexedReferenceMap() } if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "setPermissionFlags(): appId=$appId, userId=$userId," + + LOG_TAG, + "setPermissionFlags(): appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " newFlags=${PermissionFlags.toString(newFlags)}" ) @@ -229,40 +246,39 @@ class DevicePermissionPolicy : SchemePolicy() { } listeners.forEachIndexed { _, it -> it.onDevicePermissionFlagsChanged( - appId, userId, deviceId, permissionName, oldFlags, newFlags + appId, + userId, + deviceId, + permissionName, + oldFlags, + newFlags ) } return true } fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners + listener - } + synchronized(listenersLock) { listeners = listeners + listener } } fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners - listener - } + synchronized(listenersLock) { listeners = listeners - listener } } private fun isDeviceAwarePermission(permissionName: String): Boolean = - DEVICE_AWARE_PERMISSIONS.contains(permissionName) + DEVICE_AWARE_PERMISSIONS.contains(permissionName) companion object { private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName - /** - * These permissions are supported for virtual devices. - */ + /** These permissions are supported for virtual devices. */ // TODO: b/298661870 - Use new API to get the list of device aware permissions. val DEVICE_AWARE_PERMISSIONS = emptySet<String>() } /** - * TODO: b/289355341 - implement listener for permission changes - * Listener for permission flags changes. + * TODO: b/289355341 - implement listener for permission changes Listener for permission flags + * changes. */ abstract class OnDevicePermissionFlagsChangedListener { /** @@ -288,4 +304,4 @@ class DevicePermissionPolicy : SchemePolicy() { */ abstract fun onStateMutated() } -}
\ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt index c7fe1a9e744e..aa569280eadf 100644 --- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt +++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt @@ -26,8 +26,7 @@ data class Permission( val isReconciled: Boolean, val type: Int, val appId: Int, - @Suppress("ArrayInDataClass") - val gids: IntArray = EmptyArray.INT, + @Suppress("ArrayInDataClass") val gids: IntArray = EmptyArray.INT, val areGidsPerUser: Boolean = false ) { inline val name: String @@ -43,8 +42,7 @@ data class Permission( get() = type == TYPE_DYNAMIC inline val protectionLevel: Int - @Suppress("DEPRECATION") - get() = permissionInfo.protectionLevel + @Suppress("DEPRECATION") get() = permissionInfo.protectionLevel inline val protection: Int get() = permissionInfo.protection diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index 550d1480fc81..b9d89c2184b7 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -32,15 +32,12 @@ import com.android.server.permission.access.util.hasBits * * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so * that: - * * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package * `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing * edge cases during module rollbacks. - * * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and * implicit permissions split from non-runtime permissions are never revoked, without checking * split permissions and package state everywhere slowly and in slightly different ways. - * * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and * potentially re-granting permissions upon restriction state changes. * @@ -55,9 +52,7 @@ import com.android.server.permission.access.util.hasBits * don't have any effect on the binary permission state. */ object PermissionFlags { - /** - * Permission flag for a normal permission that is granted at package installation. - */ + /** Permission flag for a normal permission that is granted at package installation. */ const val INSTALL_GRANTED = 1 shl 0 /** @@ -97,8 +92,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission whose state is set by the user. * - * For example, this flag may be set when the permission is allowed by the user in the - * request permission dialog, or managed in the permission settings. + * For example, this flag may be set when the permission is allowed by the user in the request + * permission dialog, or managed in the permission settings. * * @see PackageManager.FLAG_PERMISSION_USER_SET */ @@ -290,8 +285,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission that is selected by the user. * - * For example, this flag may be set when one of the coarse/fine location accuracies is - * selected by the user. + * For example, this flag may be set when one of the coarse/fine location accuracies is selected + * by the user. * * This flag is informational and managed by PermissionController. * @@ -299,28 +294,37 @@ object PermissionFlags { */ const val USER_SELECTED = 1 shl 23 - /** - * Mask for all permission flags. - */ + /** Mask for all permission flags. */ const val MASK_ALL = 0.inv() - /** - * Mask for all permission flags that may be applied to a runtime permission. - */ - const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or - SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or - USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or - SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or - APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED - - /** - * Mask for all permission flags about permission exemption. - */ + /** Mask for all permission flags that may be applied to a runtime permission. */ + const val MASK_RUNTIME = + ROLE or + RUNTIME_GRANTED or + USER_SET or + USER_FIXED or + POLICY_FIXED or + SYSTEM_FIXED or + PREGRANT or + LEGACY_GRANTED or + IMPLICIT_GRANTED or + IMPLICIT or + USER_SENSITIVE_WHEN_GRANTED or + USER_SENSITIVE_WHEN_REVOKED or + INSTALLER_EXEMPT or + SYSTEM_EXEMPT or + UPGRADE_EXEMPT or + RESTRICTION_REVOKED or + SOFT_RESTRICTED or + APP_OP_REVOKED or + ONE_TIME or + HIBERNATION or + USER_SELECTED + + /** Mask for all permission flags about permission exemption. */ const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT - /** - * Mask for all permission flags about permission restriction. - */ + /** Mask for all permission flags about permission restriction. */ const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED fun isPermissionGranted(flags: Int): Boolean { @@ -363,11 +367,13 @@ object PermissionFlags { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT } if (flags.hasBits(IMPLICIT)) { - apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) { - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - } else { - PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED - } + apiFlags = + apiFlags or + if (flags.hasBits(LEGACY_GRANTED)) { + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + } else { + PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED + } } if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED @@ -440,8 +446,10 @@ object PermissionFlags { } flags = flags or (oldFlags and LEGACY_GRANTED) flags = flags or (oldFlags and IMPLICIT_GRANTED) - if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || - apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) { + if ( + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) + ) { flags = flags or IMPLICIT } if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) { diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index bb24d514e14b..ab3d78c9958c 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -41,10 +41,10 @@ import android.os.RemoteException import android.os.ServiceManager import android.os.UserHandle import android.os.UserManager -import android.permission.flags.Flags import android.permission.IOnPermissionsChangeListener import android.permission.PermissionControllerManager import android.permission.PermissionManager +import android.permission.flags.Flags import android.provider.Settings import android.util.ArrayMap import android.util.ArraySet @@ -88,28 +88,25 @@ import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService import com.android.server.pm.parsing.pkg.AndroidPackageUtils import com.android.server.pm.permission.LegacyPermission -import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.LegacyPermissionSettings import com.android.server.pm.permission.LegacyPermissionState +import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.PermissionManagerServiceInterface import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.policy.SoftRestrictedPermissionPolicy -import libcore.util.EmptyArray import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException +import libcore.util.EmptyArray -/** - * Modern implementation of [PermissionManagerServiceInterface]. - */ -class PermissionService( - private val service: AccessCheckingService -) : PermissionManagerServiceInterface { +/** Modern implementation of [PermissionManagerServiceInterface]. */ +class PermissionService(private val service: AccessCheckingService) : + PermissionManagerServiceInterface { private val policy = service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy @@ -131,8 +128,7 @@ class PermissionService( private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener private val storageVolumeLock = Any() - @GuardedBy("storageVolumeLock") - private val mountedStorageVolumes = ArraySet<String?>() + @GuardedBy("storageVolumeLock") private val mountedStorageVolumes = ArraySet<String?>() @GuardedBy("storageVolumeLock") private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>() @@ -144,8 +140,8 @@ class PermissionService( * A permission backup might contain apps that are not installed. In this case we delay the * restoration until the app is installed. * - * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where - * there is **no more** delayed backup left. + * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where there is **no + * more** delayed backup left. */ private val isDelayedPermissionBackupFinished = SparseBooleanArray() @@ -154,9 +150,10 @@ class PermissionService( packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java) packageManagerLocal = LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java) - platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) - ) + platformCompat = + IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) + ) systemConfig = SystemConfig.getInstance() userManagerInternal = LocalServices.getService(UserManagerInternal::class.java) userManagerService = UserManagerService.getInstance() @@ -166,8 +163,8 @@ class PermissionService( PackageManager.invalidatePackageInfoCache() PermissionManager.disablePackageNamePermissionCache() - handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true) - .apply { start() } + handlerThread = + ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true).apply { start() } handler = Handler(handlerThread.looper) onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper) onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener() @@ -181,9 +178,7 @@ class PermissionService( return emptyList() } - val permissionGroups = service.getState { - with(policy) { getPermissionGroups() } - } + val permissionGroups = service.getState { with(policy) { getPermissionGroups() } } return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup -> if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { @@ -206,9 +201,9 @@ class PermissionService( return null } - permissionGroup = service.getState { - with(policy) { getPermissionGroups()[permissionGroupName] } - } ?: return null + permissionGroup = + service.getState { with(policy) { getPermissionGroups()[permissionGroupName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { return null @@ -242,29 +237,28 @@ class PermissionService( return null } - permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) { return null } val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage - targetSdkVersion = when { - // System sees all flags. - isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT - opPackage != null -> opPackage.targetSdkVersion - else -> Build.VERSION_CODES.CUR_DEVELOPMENT - } + targetSdkVersion = + when { + // System sees all flags. + isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT + opPackage != null -> opPackage.targetSdkVersion + else -> Build.VERSION_CODES.CUR_DEVELOPMENT + } } return permission.generatePermissionInfo(flags, targetSdkVersion) } - /** - * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. - */ + /** Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. */ private fun Permission.generatePermissionInfo( flags: Int, targetSdkVersion: Int = Build.VERSION_CODES.CUR_DEVELOPMENT @@ -296,22 +290,27 @@ class PermissionService( return null } - val permissions = service.getState { - if (permissionGroupName != null) { - val permissionGroup = - with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null + val permissions = + service.getState { + if (permissionGroupName != null) { + val permissionGroup = + with(policy) { getPermissionGroups()[permissionGroupName] } + ?: return null - if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { - return null + if ( + !snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid) + ) { + return null + } } - } - with(policy) { getPermissions() } - } + with(policy) { getPermissions() } + } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> - if (permission.groupName == permissionGroupName && - snapshot.isPackageVisibleToUid(permission.packageName, callingUid) + if ( + permission.groupName == permissionGroupName && + snapshot.isPackageVisibleToUid(permission.packageName, callingUid) ) { permission.generatePermissionInfo(flags) } else { @@ -334,9 +333,7 @@ class PermissionService( private inline fun getPermissionsWithProtectionOrProtectionFlags( predicate: (Permission) -> Boolean ): List<PermissionInfo> { - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> if (predicate(permission)) { @@ -348,18 +345,16 @@ class PermissionService( } override fun getPermissionGids(permissionName: String, userId: Int): IntArray { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return EmptyArray.INT + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return EmptyArray.INT return permission.getGidsForUser(userId) } override fun getInstalledPermissions(packageName: String): Set<String> { requireNotNull(packageName) { "packageName cannot be null" } - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission -> if (permission.packageName == packageName) { @@ -398,9 +393,8 @@ class PermissionService( permissionInfo.protectionLevel = PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel) - val newPermission = Permission( - permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId - ) + val newPermission = + Permission(permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId) with(policy) { addPermission(newPermission, !async) } } @@ -431,7 +425,7 @@ class PermissionService( val callingUid = Binder.getCallingUid() val permissionTree = with(policy) { findPermissionTree(permissionName) } if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) { - return permissionTree + return permissionTree } throw SecurityException( @@ -447,8 +441,9 @@ class PermissionService( // if that plus the size of 'info' would exceed our stated maximum. if (permissionTree.appId != Process.SYSTEM_UID) { val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree) - if (permissionTreeFootprint + permissionInfo.calculateFootprint() > - MAX_PERMISSION_TREE_FOOTPRINT + if ( + permissionTreeFootprint + permissionInfo.calculateFootprint() > + MAX_PERMISSION_TREE_FOOTPRINT ) { throw SecurityException("Permission tree size cap exceeded") } @@ -483,14 +478,16 @@ class PermissionService( packageManagerInternal.getPackageStateInternal(androidPackage.packageName) if (packageState == null) { Slog.e( - LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" + + LOG_TAG, + "checkUidPermission: PackageState not found for AndroidPackage" + " $androidPackage" ) return PackageManager.PERMISSION_DENIED } - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { + isPermissionGranted(packageState, userId, permissionName, deviceId) + } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -505,9 +502,7 @@ class PermissionService( } } - /** - * Internal implementation that should only be called by [checkUidPermission]. - */ + /** Internal implementation that should only be called by [checkUidPermission]. */ private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean { val uidPermissions = systemConfig.systemPermissions[uid] ?: return false if (permissionName in uidPermissions) { @@ -532,12 +527,14 @@ class PermissionService( return PackageManager.PERMISSION_DENIED } - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return PackageManager.PERMISSION_DENIED - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -566,8 +563,15 @@ class PermissionService( } val fullerPermissionName = FULLER_PERMISSIONS[permissionName] - if (fullerPermissionName != null && - isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId) + if ( + fullerPermissionName != null && + isSinglePermissionGranted( + appId, + userId, + isInstantApp, + fullerPermissionName, + deviceId + ) ) { return true } @@ -575,9 +579,7 @@ class PermissionService( return false } - /** - * Internal implementation that should only be called by [isPermissionGranted]. - */ + /** Internal implementation that should only be called by [isPermissionGranted]. */ private fun GetStateScope.isSinglePermissionGranted( appId: Int, userId: Int, @@ -604,20 +606,27 @@ class PermissionService( requireNotNull(packageName) { "packageName cannot be null" } Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName") return emptySet() } service.getState { - val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) } - ?: return emptySet() + val permissionFlags = + with(policy) { getUidPermissionFlags(packageState.appId, userId) } + ?: return emptySet() return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ -> - if (isPermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) { + if ( + isPermissionGranted( + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT + ) + ) { permissionName } else { null @@ -635,8 +644,8 @@ class PermissionService( // permission state is not found, now we always return at least global GIDs. This is // more consistent with the pre-S-refactor behavior. This is also because we are now // actively trimming the per-UID objects when empty. - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return globalGids.copyOf() + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return globalGids.copyOf() val gids = GrowingIntArray.wrap(globalGids) permissionFlags.forEachIndexed { _, permissionName, flags -> @@ -644,8 +653,8 @@ class PermissionService( return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed val permissionGids = permission.getGidsForUser(userId) if (permissionGids.isEmpty()) { return@forEachIndexed @@ -662,9 +671,7 @@ class PermissionService( deviceId: Int, userId: Int ) { - setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = true - ) + setRuntimePermissionGranted(packageName, userId, permissionName, deviceId, isGranted = true) } override fun revokeRuntimePermission( @@ -675,7 +682,12 @@ class PermissionService( reason: String? ) { setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason + packageName, + userId, + permissionName, + deviceId, + isGranted = false, + revokeReason = reason ) } @@ -684,8 +696,12 @@ class PermissionService( userId: Int ) { setRuntimePermissionGranted( - packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, - isGranted = false, skipKillUid = true + packageName, + userId, + Manifest.permission.POST_NOTIFICATIONS, + Context.DEVICE_ID_DEFAULT, + isGranted = false, + skipKillUid = true ) } @@ -704,19 +720,24 @@ class PermissionService( ) { val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission" val callingUid = Binder.getCallingUid() - val isDebugEnabled = if (isGranted) { - PermissionManager.DEBUG_TRACE_GRANTS - } else { - PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES - } - if (isDebugEnabled && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { + val isDebugEnabled = + if (isGranted) { + PermissionManager.DEBUG_TRACE_GRANTS + } else { + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES + } + if ( + isDebugEnabled && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "$methodName(packageName = $packageName," + + LOG_TAG, + "$methodName(packageName = $packageName," + " permissionName = $permissionName" + (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") + - ", userId = $userId," + " callingUid = $callingUidName ($callingUid))", + ", userId = $userId," + + " callingUid = $callingUidName ($callingUid))", RuntimeException() ) } @@ -727,23 +748,31 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, methodName + userId, + enforceFullPermission = true, + enforceShellRestriction = true, + methodName ) - val enforcedPermissionName = if (isGranted) { - Manifest.permission.GRANT_RUNTIME_PERMISSIONS - } else { - Manifest.permission.REVOKE_RUNTIME_PERMISSIONS - } + val enforcedPermissionName = + if (isGranted) { + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + } else { + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + } context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName) val packageState: PackageState? - val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ).first() + val permissionControllerPackageName = + packageManagerInternal + .getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + .first() val permissionControllerPackageState: PackageState? packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> - packageState = snapshot.filtered(callingUid, userId) - .use { it.getPackageState(packageName) } + packageState = + snapshot.filtered(callingUid, userId).use { it.getPackageState(packageName) } permissionControllerPackageState = snapshot.getPackageState(permissionControllerPackageName) } @@ -756,11 +785,13 @@ class PermissionService( return } - val canManageRolePermission = isRootOrSystemUid(callingUid) || - UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId - val overridePolicyFixed = context.checkCallingOrSelfPermission( - Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY - ) == PackageManager.PERMISSION_GRANTED + val canManageRolePermission = + isRootOrSystemUid(callingUid) || + UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId + val overridePolicyFixed = + context.checkCallingOrSelfPermission( + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + ) == PackageManager.PERMISSION_GRANTED service.mutateState { with(onPermissionFlagsChangedListener) { @@ -773,8 +804,15 @@ class PermissionService( } setRuntimePermissionGranted( - packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission, - overridePolicyFixed, reportError = true, methodName + packageState, + userId, + permissionName, + deviceId, + isGranted, + canManageRolePermission, + overridePolicyFixed, + reportError = true, + methodName ) } } @@ -791,8 +829,9 @@ class PermissionService( PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {} else -> { Slog.w( - LOG_TAG, "setRequestedPermissionStates: Unknown permission state" + - " $permissionState for permission $permissionName" + LOG_TAG, + "setRequestedPermissionStates: Unknown permission state" + + " $permissionState for permission $permissionName" ) return@forEachIndexed } @@ -800,35 +839,50 @@ class PermissionService( if (permissionName !in packageState.androidPackage!!.requestedPermissions) { return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed when { permission.isDevelopment || permission.isRuntime -> { - if (permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) { + if ( + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + ) { setRuntimePermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT, - isGranted = true, canManageRolePermission = false, - overridePolicyFixed = false, reportError = false, + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + isGranted = true, + canManageRolePermission = false, + overridePolicyFixed = false, + reportError = false, "setRequestedPermissionStates" ) updatePermissionFlags( - packageState.appId, userId, permissionName, + packageState.appId, + userId, + permissionName, Context.DEVICE_ID_DEFAULT, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or - PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, + PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + 0, canUpdateSystemFlags = false, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "setRequestedPermissionStates", + isPermissionRequested = true, + "setRequestedPermissionStates", packageState.packageName ) } } - permission.isAppOp && permissionName in + permission.isAppOp && + permissionName in PackageInstallerService.INSTALLER_CHANGEABLE_APP_OP_PERMISSIONS -> setAppOpPermissionGranted( - packageState, userId, permissionName, permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + packageState, + userId, + permissionName, + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED ) else -> {} } @@ -836,9 +890,7 @@ class PermissionService( } } - /** - * Set whether a runtime permission is granted, without any validation on caller. - */ + /** Set whether a runtime permission is granted, without any validation on caller. */ private fun MutateStateScope.setRuntimePermissionGranted( packageState: PackageState, userId: Int, @@ -876,8 +928,11 @@ class PermissionService( // their permissions as always granted return } - if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp && - !permission.isInstant) { + if ( + isGranted && + packageState.getUserStateOrDefault(userId).isInstantApp && + !permission.isInstant + ) { if (reportError) { throw SecurityException( "Cannot grant non-instant permission $permissionName to package" + @@ -913,7 +968,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change system fixed permission $permissionName" + " for package $packageName" ) } @@ -923,7 +979,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change policy fixed permission $permissionName" + " for package $packageName" ) } @@ -933,7 +990,8 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant hard-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -942,14 +1000,19 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) { // TODO: Refactor SoftRestrictedPermissionPolicy. - val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission( - context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), - androidPackage, UserHandle.of(userId), permissionName - ) + val softRestrictedPermissionPolicy = + SoftRestrictedPermissionPolicy.forPermission( + context, + AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), + androidPackage, + UserHandle.of(userId), + permissionName + ) if (!softRestrictedPermissionPolicy.mayGrantPermission()) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant soft-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -965,15 +1028,17 @@ class PermissionService( setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) if (permission.isRuntime) { - val action = if (isGranted) { - MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED - } else { - MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED - } - val log = LogMaker(action).apply { - setPackageName(packageName) - addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) - } + val action = + if (isGranted) { + MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED + } else { + MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED + } + val log = + LogMaker(action).apply { + setPackageName(packageName) + addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) + } metricsLogger.write(log) } } @@ -984,8 +1049,8 @@ class PermissionService( permissionName: String, isGranted: Boolean ) { - val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as - AppIdAppOpPolicy + val appOpPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy val appOpName = AppOpsManager.permissionToOp(permissionName)!! val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } @@ -1003,17 +1068,20 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "getPermissionFlags" ) enforceCallingOrSelfAnyPermission( - "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "getPermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, Manifest.permission.GET_RUNTIME_PERMISSIONS ) - val packageState = packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withFilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName") return 0 @@ -1045,12 +1113,17 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "isPermissionRevokedByPolicy" ) - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return false service.getState { if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { @@ -1069,12 +1142,13 @@ class PermissionService( // TODO(b/173235285): Some caller may pass USER_ALL as userId. // Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } + ?: return false - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(packageState.appId, userId) } - } ?: return false + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(packageState.appId, userId) } } + ?: return false return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) } } @@ -1090,13 +1164,18 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "shouldShowRequestPermissionRationale" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return false val appId = packageState.appId if (UserHandle.getAppId(callingUid) != appId) { return false @@ -1115,17 +1194,24 @@ class PermissionService( } if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) { - val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity { - try { - platformCompat.isChangeEnabledByPackageName( - BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId - ) - } catch (e: RemoteException) { - Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" + - " compatibility change is enabled", e) - false + val isBackgroundRationaleChangeEnabled = + Binder::class.withClearedCallingIdentity { + try { + platformCompat.isChangeEnabledByPackageName( + BACKGROUND_RATIONALE_CHANGE_ID, + packageName, + userId + ) + } catch (e: RemoteException) { + Slog.e( + LOG_TAG, + "shouldShowRequestPermissionRationale: Unable to check if" + + " compatibility change is enabled", + e + ) + false + } } - } if (isBackgroundRationaleChangeEnabled) { return true } @@ -1144,20 +1230,30 @@ class PermissionService( userId: Int ) { val callingUid = Binder.getCallingUid() - if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + if ( + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlags(packageName = $packageName," + + LOG_TAG, + "updatePermissionFlags(packageName = $packageName," + " permissionName = $permissionName, flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1167,11 +1263,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlags" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1208,8 +1307,10 @@ class PermissionService( // Different from the old implementation, which returns when package doesn't exist but // throws when package exists but isn't visible, we now return in both cases to avoid // leaking the package existence. - if (androidPackage == null || - packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) { + if ( + androidPackage == null || + packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false) + ) { Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName") return } @@ -1219,26 +1320,35 @@ class PermissionService( // permissions. val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) { - // Fast path, the current package has requested the permission. - true - } else { - // Slow path, go through all shared user packages. - val sharedUserPackageNames = - packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) - sharedUserPackageNames.any { sharedUserPackageName -> - val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) - sharedUserPackage != null && - permissionName in sharedUserPackage.requestedPermissions + val isPermissionRequested = + if (permissionName in androidPackage.requestedPermissions) { + // Fast path, the current package has requested the permission. + true + } else { + // Slow path, go through all shared user packages. + val sharedUserPackageNames = + packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) + sharedUserPackageNames.any { sharedUserPackageName -> + val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) + sharedUserPackage != null && + permissionName in sharedUserPackage.requestedPermissions + } } - } val appId = packageState.appId service.mutateState { updatePermissionFlags( - appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags, - reportErrorForUnknownPermission = true, isPermissionRequested, - "updatePermissionFlags", packageName + appId, + userId, + permissionName, + deviceId, + flagMask, + flagValues, + canUpdateSystemFlags, + reportErrorForUnknownPermission = true, + isPermissionRequested, + "updatePermissionFlags", + packageName ) } } @@ -1246,17 +1356,25 @@ class PermissionService( override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) { val callingUid = Binder.getCallingUid() if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + + LOG_TAG, + "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1266,11 +1384,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlagsForAllApps" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlagsForAllApps", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1278,26 +1399,30 @@ class PermissionService( // flag, we now properly sanitize all flags as in updatePermissionFlags(). val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val packageStates = packageManagerLocal.withUnfilteredSnapshot() - .use { it.packageStates } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } service.mutateState { packageStates.forEach { (packageName, packageState) -> val androidPackage = packageState.androidPackage ?: return@forEach androidPackage.requestedPermissions.forEach { permissionName -> updatePermissionFlags( - packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT, - flagMask, flagValues, canUpdateSystemFlags, + packageState.appId, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + flagMask, + flagValues, + canUpdateSystemFlags, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName + isPermissionRequested = true, + "updatePermissionFlagsForAllApps", + packageName ) } } } } - /** - * Update flags for a permission, without any validation on caller. - */ + /** Update flags for a permission, without any validation on caller. */ private fun MutateStateScope.updatePermissionFlags( appId: Int, userId: Int, @@ -1311,20 +1436,19 @@ class PermissionService( methodName: String, packageName: String ) { - @Suppress("NAME_SHADOWING") - var flagMask = flagMask - @Suppress("NAME_SHADOWING") - var flagValues = flagValues + @Suppress("NAME_SHADOWING") var flagMask = flagMask + @Suppress("NAME_SHADOWING") var flagValues = flagValues // Only the system can change these flags and nothing else. if (!canUpdateSystemFlags) { // Different from the old implementation, which allowed non-system UIDs to remove (but // not add) permission restriction flags, we now consistently ignore them altogether. - val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or - PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or - PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or - PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION + val ignoredMask = + PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or + PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or + PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or + PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION flagMask = flagMask andInv ignoredMask flagValues = flagValues andInv ignoredMask } @@ -1340,7 +1464,8 @@ class PermissionService( val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!isPermissionRequested && oldFlags == 0) { Slog.w( - LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" + + LOG_TAG, + "$methodName: Permission $permissionName isn't requested by package" + " $packageName" ) return @@ -1365,21 +1490,29 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = false, enforceShellRestriction = false, + userId, + enforceFullPermission = false, + enforceShellRestriction = false, "getAllowlistedRestrictedPermissions" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return null + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return null val androidPackage = packageState.androidPackage ?: return null - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED - if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && - !isCallerPrivileged) { + if ( + allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && + !isCallerPrivileged + ) { throw SecurityException( "Querying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS @@ -1389,8 +1522,12 @@ class PermissionService( val isCallerInstallerOnRecord = packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid) - if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) { + if ( + allowlistedFlags.hasAnyBit( + PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + ) + ) { if (!isCallerPrivileged && !isCallerInstallerOnRecord) { throw SecurityException( "Querying upgrade or installer allowlist requires being installer on record" + @@ -1400,7 +1537,9 @@ class PermissionService( } return getAllowlistedRestrictedPermissionsUnchecked( - packageState.appId, allowlistedFlags, userId + packageState.appId, + allowlistedFlags, + userId ) } @@ -1414,8 +1553,11 @@ class PermissionService( with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " get the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " get the flags for default device." + ) return with(policy) { getPermissionFlags(appId, userId, permissionName) } } val virtualDeviceManagerInternal = virtualDeviceManagerInternal @@ -1423,8 +1565,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return 0 } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { getPermissionFlags(appId, persistentDeviceId, userId, permissionName) @@ -1444,13 +1585,14 @@ class PermissionService( flags: Int ): Boolean { return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { - with(policy) { - setPermissionFlags(appId, userId, permissionName, flags) - } + with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " set the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " set the flags for default device." + ) return with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -1459,8 +1601,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return false } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags) @@ -1473,17 +1614,17 @@ class PermissionService( } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun getAllowlistedRestrictedPermissionsUnchecked( appId: Int, allowlistedFlags: Int, userId: Int ): ArrayList<String>? { - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(appId, userId) } - } ?: return null + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(appId, userId) } } + ?: return null var queryFlags = 0 if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) { @@ -1512,14 +1653,18 @@ class PermissionService( return false } - val permissionNames = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: ArrayList(1) + val permissionNames = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: ArrayList(1) if (permissionName !in permissionNames) { permissionNames += permissionName return setAllowlistedRestrictedPermissions( - packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true + packageName, + permissionNames, + allowlistedFlags, + userId, + isAddingPermission = true ) } return false @@ -1531,14 +1676,22 @@ class PermissionService( permissionNames: List<String>, userId: Int ) { - val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId - )?.let { - ArraySet(permissionNames).apply { this += it }.toList() - } ?: permissionNames + val newPermissionNames = + getAllowlistedRestrictedPermissionsUnchecked( + appId, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) + ?.let { ArraySet(permissionNames).apply { this += it }.toList() } + ?: permissionNames - setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId) + setAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + appId, + newPermissionNames, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) } override fun removeAllowlistedRestrictedPermission( @@ -1552,13 +1705,17 @@ class PermissionService( return false } - val permissions = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: return false + val permissions = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: return false if (permissions.remove(permissionName)) { return setAllowlistedRestrictedPermissions( - packageName, permissions, allowlistedFlags, userId, isAddingPermission = false + packageName, + permissions, + allowlistedFlags, + userId, + isAddingPermission = false ) } @@ -1572,16 +1729,22 @@ class PermissionService( return false } - if (packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(permission.packageName) } == null) { + if ( + packageManagerLocal.withFilteredSnapshot().use { + it.getPackageState(permission.packageName) + } == null + ) { return false } val isImmutablyRestrictedPermission = permission.isHardOrSoftRestricted && permission.isImmutablyRestricted - if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) != PackageManager.PERMISSION_GRANTED) { + if ( + isImmutablyRestrictedPermission && + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) != PackageManager.PERMISSION_GRANTED + ) { throw SecurityException( "Cannot modify allowlist of an immutably restricted permission: ${permission.name}" ) @@ -1599,13 +1762,16 @@ class PermissionService( ): Boolean { Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1) - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { snapshot -> snapshot.packageStates[packageName] ?: return false } + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { snapshot -> + snapshot.packageStates[packageName] ?: return false + } val androidPackage = packageState.androidPackage ?: return false val isCallerInstallerOnRecord = @@ -1627,15 +1793,19 @@ class PermissionService( } setAllowlistedRestrictedPermissionsUnchecked( - androidPackage, packageState.appId, permissionNames, allowlistedFlags, userId + androidPackage, + packageState.appId, + permissionNames, + allowlistedFlags, + userId ) return true } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun setAllowlistedRestrictedPermissionsUnchecked( androidPackage: AndroidPackage, @@ -1712,22 +1882,24 @@ class PermissionService( } } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - mask = mask or PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SOFT_RESTRICTED + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } + mask = + mask or + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SOFT_RESTRICTED - updatePermissionFlags( - appId, userId, requestedPermission, mask, newFlags - ) + updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags) } } } @@ -1735,9 +1907,8 @@ class PermissionService( override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) { service.mutateState { - with(policy) { - resetRuntimePermissions(androidPackage.packageName, userId) - } + with(policy) { resetRuntimePermissions(androidPackage.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(androidPackage.packageName, userId) } } } @@ -1745,9 +1916,8 @@ class PermissionService( packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> service.mutateState { snapshot.packageStates.forEach { (_, packageState) -> - with(policy) { - resetRuntimePermissions(packageState.packageName, userId) - } + with(policy) { resetRuntimePermissions(packageState.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) } } } } @@ -1755,7 +1925,8 @@ class PermissionService( override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) { context.enforceCallingOrSelfPermission( - Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, "addOnPermissionsChangeListener" + Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, + "addOnPermissionsChangeListener" ) onPermissionsChangeListeners.addListener(listener) @@ -1780,9 +1951,7 @@ class PermissionService( requireNotNull(permissionName) { "permissionName cannot be null" } val packageNames = ArraySet<String>() - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } + val permission = service.getState { with(policy) { getPermissions()[permissionName] } } if (permission == null || !permission.isAppOp) { packageNames.toTypedArray() } @@ -1808,8 +1977,8 @@ class PermissionService( androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName -> val permission = permissions[permissionName] ?: return@requestedPermissions if (permission.isAppOp) { - val packageNames = appOpPermissionPackageNames - .getOrPut(permissionName) { ArraySet() } + val packageNames = + appOpPermissionPackageNames.getOrPut(permissionName) { ArraySet() } packageNames += androidPackage.packageName } } @@ -1822,14 +1991,18 @@ class PermissionService( Preconditions.checkArgumentNonnegative(userId, "userId cannot be null") val backup = CompletableFuture<ByteArray>() permissionControllerManager.getRuntimePermissionBackup( - UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete + UserHandle.of(userId), + PermissionThread.getExecutor(), + backup::complete ) return try { backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) } catch (e: Exception) { when (e) { - is TimeoutException, is InterruptedException, is ExecutionException -> { + is TimeoutException, + is InterruptedException, + is ExecutionException -> { Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e) null } @@ -1846,7 +2019,8 @@ class PermissionService( isDelayedPermissionBackupFinished -= userId } permissionControllerManager.stageAndApplyRuntimePermissionsBackup( - backup, UserHandle.of(userId) + backup, + UserHandle.of(userId) ) } @@ -1860,7 +2034,9 @@ class PermissionService( } } permissionControllerManager.applyStagedRuntimePermissionBackup( - packageName, UserHandle.of(userId), PermissionThread.getExecutor() + packageName, + UserHandle.of(userId), + PermissionThread.getExecutor() ) { hasMoreBackup -> if (hasMoreBackup) { return@applyStagedRuntimePermissionBackup @@ -1907,21 +2083,15 @@ class PermissionService( ): IndexedMap<Int, MutableIndexedSet<String>> { val appIds = MutableIndexedSet<Int>() - val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { - it.packageStates - } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } state.userStates.forEachIndexed { _, _, userState -> - userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.packageVersions.forEachIndexed packageVersions@ { _, packageName, _ -> + userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.packageVersions.forEachIndexed packageVersions@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageVersions appIds.add(appId) } - userState.packageAppOpModes.forEachIndexed packageAppOpModes@ { _, packageName, _ -> + userState.packageAppOpModes.forEachIndexed packageAppOpModes@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageAppOpModes appIds.add(appId) } @@ -1929,7 +2099,8 @@ class PermissionService( val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>() packageStates.forEach { (_, packageState) -> - appIdPackageNames.getOrPut(packageState.appId) { MutableIndexedSet() } + appIdPackageNames + .getOrPut(packageState.appId) { MutableIndexedSet() } .add(packageState.packageName) } // add non-package app IDs which might not be reported by package manager. @@ -1960,10 +2131,7 @@ class PermissionService( println("Permission groups:") withIndent { state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup -> - println( - "${permissionGroup.name}: " + - "packageName=${permissionGroup.packageName}" - ) + println("${permissionGroup.name}: " + "packageName=${permissionGroup.packageName}") } } @@ -1992,7 +2160,9 @@ class PermissionService( println("Permissions:") withIndent { userState.appIdPermissionFlags[appId]?.forEachIndexed { - _, permissionName, flags -> + _, + permissionName, + flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) println( "$permissionName: granted=$isGranted, flags=" + @@ -2002,7 +2172,9 @@ class PermissionService( } userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { - _, deviceId, devicePermissionFlags -> + _, + deviceId, + devicePermissionFlags -> println("Permissions (Device $deviceId):") withIndent { devicePermissionFlags.forEachIndexed { _, permissionName, flags -> @@ -2017,7 +2189,8 @@ class PermissionService( println("App ops:") withIndent { - userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode -> + userState.appIdAppOpModes[appId]?.forEachIndexed { _, appOpName, appOpMode + -> println("$appOpName: mode=${AppOpsManager.modeToName(appOpMode)}") } } @@ -2029,7 +2202,9 @@ class PermissionService( println("App ops:") withIndent { userState.packageAppOpModes[packageName]?.forEachIndexed { - _, appOpName, appOpMode -> + _, + appOpName, + appOpMode -> val modeName = AppOpsManager.modeToName(appOpMode) println("$appOpName: mode=$modeName") } @@ -2048,24 +2223,30 @@ class PermissionService( } override fun getPermissionTEMP(permissionName: String): LegacyPermission2? { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return null return LegacyPermission2( - permission.permissionInfo, permission.type, permission.isReconciled, permission.appId, - permission.gids, permission.areGidsPerUser + permission.permissionInfo, + permission.type, + permission.isReconciled, + permission.appId, + permission.gids, + permission.areGidsPerUser ) } override fun getLegacyPermissions(): List<LegacyPermission> = - service.getState { - with(policy) { getPermissions() } - }.mapIndexedTo(ArrayList()) { _, _, permission -> - LegacyPermission( - permission.permissionInfo, permission.type, permission.appId, permission.gids - ) - } + service + .getState { with(policy) { getPermissions() } } + .mapIndexedTo(ArrayList()) { _, _, permission -> + LegacyPermission( + permission.permissionInfo, + permission.type, + permission.appId, + permission.gids + ) + } override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) { // Package settings has been read when this method is called. @@ -2086,9 +2267,7 @@ class PermissionService( ): List<LegacyPermission> = permissions.mapIndexedTo(ArrayList()) { _, _, permission -> // We don't need to provide UID and GIDs, which are only retrieved when dumping. - LegacyPermission( - permission.permissionInfo, permission.type, 0, EmptyArray.INT - ) + LegacyPermission(permission.permissionInfo, permission.type, 0, EmptyArray.INT) } override fun getLegacyPermissionState(appId: Int): LegacyPermissionState { @@ -2097,17 +2276,18 @@ class PermissionService( service.getState { val permissions = with(policy) { getPermissions() } userIds.forEachIndexed { _, userId -> - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return@forEachIndexed + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return@forEachIndexed permissionFlags.forEachIndexed permissionFlags@{ _, permissionName, flags -> val permission = permissions[permissionName] ?: return@permissionFlags - val legacyPermissionState = LegacyPermissionState.PermissionState( - permissionName, - permission.isRuntime, - PermissionFlags.isPermissionGranted(flags), - PermissionFlags.toApiFlags(flags) - ) + val legacyPermissionState = + LegacyPermissionState.PermissionState( + permissionName, + permission.isRuntime, + PermissionFlags.isPermissionGranted(flags), + PermissionFlags.toApiFlags(flags) + ) legacyState.putPermissionState(legacyPermissionState, userId) } } @@ -2131,16 +2311,13 @@ class PermissionService( override fun onSystemReady() { service.onSystemReady() virtualDeviceManagerInternal = - LocalServices.getService(VirtualDeviceManagerInternal::class.java) - permissionControllerManager = PermissionControllerManager( - context, PermissionThread.getHandler() - ) + LocalServices.getService(VirtualDeviceManagerInternal::class.java) + permissionControllerManager = + PermissionControllerManager(context, PermissionThread.getHandler()) } override fun onUserCreated(userId: Int) { - withCorkedPackageInfoCache { - service.onUserAdded(userId) - } + withCorkedPackageInfoCache { service.onUserAdded(userId) } } override fun onUserRemoved(userId: Int) { @@ -2170,9 +2347,8 @@ class PermissionService( // of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the // packages to be iterated in onStorageVolumeAdded() in the same order so that the // ownership of permissions is consistent. - storageVolumePackageNames.getOrPut(packageState.volumeUuid) { - mutableListOf() - } += packageState.packageName + storageVolumePackageNames.getOrPut(packageState.volumeUuid) { mutableListOf() } += + packageState.packageName if (packageState.volumeUuid !in mountedStorageVolumes) { // Wait for the storage volume to be mounted and batch the state mutation there. return @@ -2212,23 +2388,26 @@ class PermissionService( return } } - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } @Suppress("NAME_SHADOWING") - userIds.forEach { userId -> - service.onPackageInstalled(androidPackage.packageName, userId) - } + userIds.forEach { userId -> service.onPackageInstalled(androidPackage.packageName, userId) } @Suppress("NAME_SHADOWING") userIds.forEach { userId -> // TODO: Remove when this callback receives packageState directly. val packageState = packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!! - addAllowlistedRestrictedPermissionsUnchecked(androidPackage, packageState.appId, - params.allowlistedRestrictedPermissions, userId) + addAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + packageState.appId, + params.allowlistedRestrictedPermissions, + userId + ) setRequestedPermissionStates(packageState, userId, params.permissionStates) } } @@ -2241,11 +2420,12 @@ class PermissionService( sharedUserPkgs: List<AndroidPackage>, userId: Int ) { - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } userIds.forEach { service.onPackageUninstalled(packageName, appId, it) } val packageState = packageManagerInternal.packageStates[packageName] if (packageState == null) { @@ -2262,31 +2442,26 @@ class PermissionService( } } - /** - * Check whether a UID is root or system UID. - */ + /** Check whether a UID is root or system UID. */ private fun isRootOrSystemUid(uid: Int) = when (UserHandle.getAppId(uid)) { - Process.ROOT_UID, Process.SYSTEM_UID -> true + Process.ROOT_UID, + Process.SYSTEM_UID -> true else -> false } - /** - * Check whether a UID is shell UID. - */ + /** Check whether a UID is shell UID. */ private fun isShellUid(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID - /** - * Check whether a UID is root, system or shell UID. - */ + /** Check whether a UID is root, system or shell UID. */ private fun isRootOrSystemOrShellUid(uid: Int) = isRootOrSystemUid(uid) || isShellUid(uid) /** * This method should typically only be used when granting or revoking permissions, since the * app may immediately restart after this call. * - * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against - * the app being relaunched. + * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against the + * app being relaunched. */ private fun killUid(uid: Int, reason: String) { val activityManager = ActivityManager.getService() @@ -2303,9 +2478,7 @@ class PermissionService( } } - /** - * @see PackageManagerLocal.withFilteredSnapshot - */ + /** @see PackageManagerLocal.withFilteredSnapshot */ private fun PackageManagerLocal.withFilteredSnapshot( callingUid: Int, userId: Int @@ -2323,35 +2496,27 @@ class PermissionService( packageName: String ): PackageState? = packageStates[packageName] - /** - * Check whether a UID belongs to an instant app. - */ + /** Check whether a UID belongs to an instant app. */ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean = // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the // API for getting the SharedUserApi object for an app ID yet, so for now we just keep // calling the old API. packageManagerInternal.getInstantAppPackageName(uid) != null - /** - * Check whether a package is visible to a UID within the same user as the UID. - */ + /** Check whether a package is visible to a UID within the same user as the UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, uid: Int ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid) - /** - * Check whether a package in a particular user is visible to a UID. - */ + /** Check whether a package in a particular user is visible to a UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, userId: Int, uid: Int ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null } - /** - * @see PackageManagerLocal.UnfilteredSnapshot.filtered - */ + /** @see PackageManagerLocal.UnfilteredSnapshot.filtered */ private fun PackageManagerLocal.UnfilteredSnapshot.filtered( callingUid: Int, userId: Int @@ -2374,13 +2539,16 @@ class PermissionService( val callingUid = Binder.getCallingUid() val callingUserId = UserHandle.getUserId(callingUid) if (userId != callingUserId) { - val permissionName = if (enforceFullPermission) { - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } else { - Manifest.permission.INTERACT_ACROSS_USERS - } - if (context.checkCallingOrSelfPermission(permissionName) != - PackageManager.PERMISSION_GRANTED) { + val permissionName = + if (enforceFullPermission) { + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } else { + Manifest.permission.INTERACT_ACROSS_USERS + } + if ( + context.checkCallingOrSelfPermission(permissionName) != + PackageManager.PERMISSION_GRANTED + ) { val exceptionMessage = buildString { if (message != null) { append(message) @@ -2397,9 +2565,11 @@ class PermissionService( } } if (enforceShellRestriction && isShellUid(callingUid)) { - val isShellRestricted = userManagerInternal.hasUserRestriction( - UserManager.DISALLOW_DEBUGGING_FEATURES, userId - ) + val isShellRestricted = + userManagerInternal.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, + userId + ) if (isShellRestricted) { val exceptionMessage = buildString { if (message != null) { @@ -2424,10 +2594,11 @@ class PermissionService( message: String?, vararg permissionNames: String ) { - val hasAnyPermission = permissionNames.any { permissionName -> - context.checkCallingOrSelfPermission(permissionName) == - PackageManager.PERMISSION_GRANTED - } + val hasAnyPermission = + permissionNames.any { permissionName -> + context.checkCallingOrSelfPermission(permissionName) == + PackageManager.PERMISSION_GRANTED + } if (!hasAnyPermission) { val exceptionMessage = buildString { if (message != null) { @@ -2443,9 +2614,7 @@ class PermissionService( } } - /** - * Callback invoked when interesting actions have been taken on a permission. - */ + /** Callback invoked when interesting actions have been taken on a permission. */ private inner class OnPermissionFlagsChangedListener : AppIdPermissionPolicy.OnPermissionFlagsChangedListener() { private var isPermissionFlagsChanged = false @@ -2476,9 +2645,8 @@ class PermissionService( isPermissionFlagsChanged = true val uid = UserHandle.getUid(userId, appId) - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags) val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags) @@ -2512,16 +2680,20 @@ class PermissionService( runtimePermissionChangedUids.clear() if (!isKillRuntimePermissionRevokedUidsSkipped) { - val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { - killRuntimePermissionRevokedUidsReasons.joinToString(", ") - } else { - PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED - } + val reason = + if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { + killRuntimePermissionRevokedUidsReasons.joinToString(", ") + } else { + PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED + } runtimePermissionRevokedUids.forEachIndexed { - _, uid, areOnlyNotificationsPermissionsRevoked -> + _, + uid, + areOnlyNotificationsPermissionsRevoked -> handler.post { - if (areOnlyNotificationsPermissionsRevoked && - isAppBackupAndRestoreRunning(uid) + if ( + areOnlyNotificationsPermissionsRevoked && + isAppBackupAndRestoreRunning(uid) ) { return@post } @@ -2541,19 +2713,27 @@ class PermissionService( } private fun isAppBackupAndRestoreRunning(uid: Int): Boolean { - if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != - PackageManager.PERMISSION_GRANTED) { + if ( + checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != + PackageManager.PERMISSION_GRANTED + ) { return false } return try { val contentResolver = context.contentResolver val userId = UserHandle.getUserId(uid) - val isInSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId - ) == 0 - val isInDeferredSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId - ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED + val isInSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_COMPLETE, + userId + ) == 0 + val isInDeferredSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, + userId + ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED isInSetup || isInDeferredSetup } catch (e: Settings.SettingNotFoundException) { Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e") @@ -2614,31 +2794,34 @@ class PermissionService( @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L - private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply { - this[Manifest.permission.ACCESS_COARSE_LOCATION] = - Manifest.permission.ACCESS_FINE_LOCATION - this[Manifest.permission.INTERACT_ACROSS_USERS] = - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } + private val FULLER_PERMISSIONS = + ArrayMap<String, String>().apply { + this[Manifest.permission.ACCESS_COARSE_LOCATION] = + Manifest.permission.ACCESS_FINE_LOCATION + this[Manifest.permission.INTERACT_ACROSS_USERS] = + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } - private val NOTIFICATIONS_PERMISSIONS = arraySetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NOTIFICATIONS_PERMISSIONS = arraySetOf(Manifest.permission.POST_NOTIFICATIONS) - private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or - PermissionFlags.IMPLICIT - private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or - PermissionFlags.USER_FIXED + private const val REVIEW_REQUIRED_FLAGS = + PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT + private const val UNREQUESTABLE_MASK = + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SYSTEM_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.USER_FIXED private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60) - /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ + /** + * Cap the size of permission trees that 3rd party apps can define; in characters of text + */ private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768 private const val PERMISSION_ALLOWLIST_MASK = PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER } } diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt index bd829351941c..6b20ef1d5519 100644 --- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -25,9 +25,7 @@ import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException -/** - * Read from an [AtomicFile], fallback to reserve file to read the data. - */ +/** Read from an [AtomicFile], fallback to reserve file to read the data. */ @Throws(Exception::class) inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { try { @@ -46,9 +44,7 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { } } -/** - * Write to actual file and reserve file. - */ +/** Write to actual file and reserve file. */ @Throws(IOException::class) inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") @@ -66,9 +62,7 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { } } -/** - * Write to an [AtomicFile] and close everything safely when done. - */ +/** Write to an [AtomicFile] and close everything safely when done. */ @Throws(IOException::class) // Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline. inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt index 1d27aef39a2c..6ab73c76007e 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt @@ -22,9 +22,7 @@ import java.io.InputStream import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException -/** - * Parse content from [InputStream] with [BinaryXmlPullParser]. - */ +/** Parse content from [InputStream] with [BinaryXmlPullParser]. */ @Throws(IOException::class, XmlPullParserException::class) inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { BinaryXmlPullParser().apply { @@ -35,6 +33,7 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { /** * Iterate through child tags of the current tag. + * * <p> * Attributes for the current tag needs to be accessed before this method is called because this * method will advance the parser past the start tag of the current tag. The code inspecting each @@ -50,7 +49,8 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) { when (val eventType = eventType) { // Document start or start tag of the parent tag. - XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd() + XmlPullParser.START_DOCUMENT, + XmlPullParser.START_TAG -> nextTagOrEnd() else -> throw XmlPullParserException("Unexpected event type $eventType") } while (true) { @@ -90,7 +90,8 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) nextTagOrEnd() } // End tag of the parent tag, or document end. - XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> break else -> throw XmlPullParserException("Unexpected event type $eventType") } } @@ -107,193 +108,146 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) inline fun BinaryXmlPullParser.nextTagOrEnd(): Int { while (true) { when (val eventType = next()) { - XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> - return eventType + XmlPullParser.START_TAG, + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> return eventType else -> continue } } } -/** - * @see BinaryXmlPullParser.getName - */ +/** @see BinaryXmlPullParser.getName */ inline val BinaryXmlPullParser.tagName: String get() = name -/** - * Check whether an attribute exists for the current tag. - */ +/** Check whether an attribute exists for the current tag. */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1 -/** - * @see BinaryXmlPullParser.getAttributeIndex - */ +/** @see BinaryXmlPullParser.getAttributeIndex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIndexOrThrow - */ +/** @see BinaryXmlPullParser.getAttributeIndexOrThrow */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int = getAttributeIndexOrThrow(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? = getAttributeValue(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String = getAttributeValue(getAttributeIndexOrThrow(name)) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? = getAttributeBytesHex(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray = getAttributeBytesHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? = getAttributeBytesBase64(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray = getAttributeBytesBase64(null, name) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int = getAttributeInt(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int = getAttributeInt(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int = getAttributeIntHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int = getAttributeIntHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long = getAttributeLong(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long = getAttributeLong(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault( name: String, defaultValue: Long ): Long = getAttributeLongHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long = getAttributeLongHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeFloatOrDefault( name: String, defaultValue: Float ): Float = getAttributeFloat(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float = getAttributeFloat(null, name) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault( name: String, defaultValue: Double ): Double = getAttributeDouble(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double = getAttributeDouble(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault( name: String, defaultValue: Boolean ): Boolean = getAttributeBoolean(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt index c8cd5866adbc..6500a7d2e0a2 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt @@ -20,9 +20,7 @@ import com.android.modules.utils.BinaryXmlSerializer import java.io.IOException import java.io.OutputStream -/** - * Serialize content into [OutputStream] with [BinaryXmlSerializer]. - */ +/** Serialize content into [OutputStream] with [BinaryXmlSerializer]. */ @Throws(IOException::class) inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) { BinaryXmlSerializer().apply { @@ -57,54 +55,42 @@ inline fun BinaryXmlSerializer.tag(name: String, block: BinaryXmlSerializer.() - endTag(null, name) } -/** - * @see BinaryXmlSerializer.attribute - */ +/** @see BinaryXmlSerializer.attribute */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attribute(name: String, value: String) { attribute(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInterned - */ +/** @see BinaryXmlSerializer.attributeInterned */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) { attributeInterned(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesHex - */ +/** @see BinaryXmlSerializer.attributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) { attributeBytesHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesBase64 - */ +/** @see BinaryXmlSerializer.attributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) { attributeBytesBase64(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) { attributeInt(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntWithDefault( @@ -117,18 +103,14 @@ inline fun BinaryXmlSerializer.attributeIntWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) { attributeIntHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHexWithDefault( @@ -141,18 +123,14 @@ inline fun BinaryXmlSerializer.attributeIntHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) { attributeLong(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongWithDefault( @@ -165,18 +143,14 @@ inline fun BinaryXmlSerializer.attributeLongWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) { attributeLongHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHexWithDefault( @@ -189,18 +163,14 @@ inline fun BinaryXmlSerializer.attributeLongHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) { attributeFloat(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloatWithDefault( @@ -213,18 +183,14 @@ inline fun BinaryXmlSerializer.attributeFloatWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) { attributeDouble(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDoubleWithDefault( @@ -237,18 +203,14 @@ inline fun BinaryXmlSerializer.attributeDoubleWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) { attributeBoolean(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBooleanWithDefault( diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt index a61489cd21c5..3ef284b05961 100644 --- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt @@ -22,13 +22,13 @@ import com.android.server.pm.permission.PermissionMigrationHelper object PackageVersionMigration { /** - * Maps existing permission and app-op version to a unified version during OTA upgrade. The - * new unified version is used in determining the upgrade steps for a package (for both - * permission and app-ops). + * Maps existing permission and app-op version to a unified version during OTA upgrade. The new + * unified version is used in determining the upgrade steps for a package (for both permission + * and app-ops). * * @return unified permission/app-op version * @throws IllegalStateException if the method is called when there is nothing to migrate i.e. - * permission and app-op file does not exist. + * permission and app-op file does not exist. */ internal fun getVersion(userId: Int): Int { val permissionMigrationHelper = diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt index e6b4e3e67b61..3d835e8c3cf3 100644 --- a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt +++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt @@ -23,15 +23,11 @@ import java.io.File object PermissionApex { private const val MODULE_NAME = "com.android.permission" - /** - * @see ApexEnvironment.getDeviceProtectedDataDir - */ + /** @see ApexEnvironment.getDeviceProtectedDataDir */ val systemDataDirectory: File get() = apexEnvironment.deviceProtectedDataDir - /** - * @see ApexEnvironment.getDeviceProtectedDataDirForUser - */ + /** @see ApexEnvironment.getDeviceProtectedDataDirForUser */ fun getUserDataDirectory(userId: Int): File = apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId)) diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index 0275c7df4648..6e4069fbe4bd 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -40,6 +40,7 @@ android_test { "services.core", "servicestests-utils", "testables", + "TestParameterInjector", ], defaults: [ 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 2396905aecbf..d021f1d5aaea 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -114,17 +114,18 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayManagerService.DeviceStateListener; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.truth.Expect; + import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; -import com.google.common.truth.Expect; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -201,9 +202,12 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter(syncRoot, context, handler, - displayAdapterListener, flags, new LocalDisplayAdapter.Injector() { + displayAdapterListener, flags, + mMockedDisplayNotificationManager, + new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; @@ -248,13 +252,15 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter( syncRoot, context, handler, displayAdapterListener, flags, + mMockedDisplayNotificationManager, new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { @@ -288,6 +294,7 @@ public class DisplayManagerServiceTest { private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); + @Mock DisplayNotificationManager mMockedDisplayNotificationManager; @Mock IMediaProjectionManager mMockProjectionService; @Mock IVirtualDeviceManager mIVirtualDeviceManager; @Mock InputManagerInternal mMockInputManagerInternal; diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 147e8f22aab6..9ac00624b343 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -60,6 +60,7 @@ import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -113,6 +114,8 @@ public class LocalDisplayAdapterTest { @Mock private LogicalLight mMockedBacklight; @Mock + private DisplayNotificationManager mMockedDisplayNotificationManager; + @Mock private DisplayManagerFlags mFlags; private Handler mHandler; @@ -148,7 +151,7 @@ public class LocalDisplayAdapterTest { mInjector = new Injector(); when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true); mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler, - mListener, mFlags, mInjector); + mListener, mFlags, mMockedDisplayNotificationManager, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java new file mode 100644 index 000000000000..5c50acb13f30 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java @@ -0,0 +1,85 @@ +/* + * 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; + +import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManager; +import android.testing.TestableContext; +import android.view.Display; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.display.RefreshRateSettingsUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RefreshRateSettingsUtilsTest { + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Mock + private DisplayManager mDisplayManagerMock; + @Mock + private Display mDisplayMock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock); + + Display.Mode[] modes = new Display.Mode[]{ + new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, + /* refreshRate= */ 60), + new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, + /* refreshRate= */ 120), + new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, + /* refreshRate= */ 90) + }; + + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); + when(mDisplayMock.getSupportedModes()).thenReturn(modes); + } + + @Test + public void testFindHighestRefreshRateForDefaultDisplay() { + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null); + assertEquals(DEFAULT_REFRESH_RATE, + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), + /* delta= */ 0); + + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); + assertEquals(120, + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), + /* delta= */ 0); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java index c280349a0559..79222c063989 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java @@ -23,6 +23,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -37,6 +38,7 @@ import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.R; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.After; import org.junit.Before; @@ -54,6 +56,8 @@ public class DisplayWhiteBalanceTintControllerTest { private Resources mMockedResources; @Mock private DisplayManagerInternal mDisplayManagerInternal; + @Mock + private DisplayManagerFlags mDisplayManagerFlagsMock; private MockitoSession mSession; private Resources mResources; @@ -63,10 +67,6 @@ public class DisplayWhiteBalanceTintControllerTest { @Before public void setUp() { DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class); - mDisplayWhiteBalanceTintController = - new DisplayWhiteBalanceTintController(displayManagerInternal); - mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true); - mDisplayWhiteBalanceTintController.setActivated(true); mSession = ExtendedMockito.mockitoSession() .initMocks(this) @@ -74,6 +74,13 @@ public class DisplayWhiteBalanceTintControllerTest { .strictness(Strictness.LENIENT) .startMocking(); + mDisplayWhiteBalanceTintController = + new DisplayWhiteBalanceTintController(displayManagerInternal, + mDisplayManagerFlagsMock); + mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true); + mDisplayWhiteBalanceTintController.setActivated(true); + + mResources = InstrumentationRegistry.getContext().getResources(); // These Resources are common to all tests. doReturn(4000) @@ -360,9 +367,47 @@ public class DisplayWhiteBalanceTintControllerTest { 1e-6f /* tolerance */); } + @Test + public void testDisplayWhiteBalance_TransitionTimes() { + when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(false); + setUpTransitionTimes(); + setUpTintController(); + + assertEquals(30L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true)); + assertEquals(30L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false)); + } + + @Test + public void testDisplayWhiteBalance_TransitionTimesDirectional() { + when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(true); + setUpTransitionTimes(); + setUpTintController(); + + assertEquals(400L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true)); + assertEquals(5000L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false)); + } + + + private void setUpTransitionTimes() { + doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries)) + .when(mMockedResources) + .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries); + when(mMockedResources.getInteger( + R.integer.config_displayWhiteBalanceTransitionTime)).thenReturn(30); + when(mMockedResources.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeIncrease)).thenReturn(400); + when(mMockedResources.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeDecrease)).thenReturn(5000); + + } + private void setUpTintController() { mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController( - mDisplayManagerInternal); + mDisplayManagerInternal, mDisplayManagerFlagsMock); mDisplayWhiteBalanceTintController.setUp(mMockedContext, true); mDisplayWhiteBalanceTintController.setActivated(true); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index b8c18e070397..c4f72b307eb7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -27,6 +27,7 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_R import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.server.display.mode.Vote.INVALID_SIZE; import static com.google.common.truth.Truth.assertThat; @@ -85,10 +86,12 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; +import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.Preconditions; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.TestUtils; import com.android.server.display.feature.DisplayManagerFlags; @@ -106,7 +109,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.ArrayList; @@ -252,9 +255,15 @@ public class DisplayModeDirectorTest { @Mock private DisplayManagerFlags mDisplayManagerFlags; + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = + new ExtendedMockitoRule.Builder(this) + .setStrictness(Strictness.LENIENT) + .spyStatic(RefreshRateSettingsUtils.class) + .build(); + @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); when(mContext.getContentResolver()).thenReturn(resolver); @@ -1470,6 +1479,94 @@ public class DisplayModeDirectorTest { } @Test + public void testPeakRefreshRate_FlagEnabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + float highestRefreshRate = 130; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setPeakRefreshRate(Float.POSITIVE_INFINITY); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ + highestRefreshRate); + } + + @Test + public void testPeakRefreshRate_FlagDisabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(false); + float peakRefreshRate = 130; + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setPeakRefreshRate(peakRefreshRate); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ + peakRefreshRate); + } + + @Test + public void testMinRefreshRate_FlagEnabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + float highestRefreshRate = 130; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setMinRefreshRate(Float.POSITIVE_INFINITY); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + } + + @Test + public void testMinRefreshRate_FlagDisabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(false); + float minRefreshRate = 130; + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setMinRefreshRate(minRefreshRate); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + } + + @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. final FakeDeviceConfig config = mInjector.getDeviceConfig(); @@ -3167,6 +3264,13 @@ public class DisplayModeDirectorTest { waitForIdleSync(); } + private void setMinRefreshRate(float fps) { + Settings.System.putFloat(mContext.getContentResolver(), Settings.System.MIN_REFRESH_RATE, + fps); + mInjector.notifyMinRefreshRateChanged(); + waitForIdleSync(); + } + private static SensorManager createMockSensorManager(Sensor... sensors) { SensorManager sensorManager = mock(SensorManager.class); when(sensorManager.getSensorList(anyInt())).then((invocation) -> { @@ -3216,6 +3320,7 @@ public class DisplayModeDirectorTest { private final SensorManagerInternal mSensorManagerInternal; private ContentObserver mPeakRefreshRateObserver; + private ContentObserver mMinRefreshRateObserver; FakesInjector() { this(null, null, null); @@ -3247,6 +3352,12 @@ public class DisplayModeDirectorTest { } @Override + public void registerMinRefreshRateObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + mMinRefreshRateObserver = observer; + } + + @Override public void registerDisplayListener(DisplayListener listener, Handler handler) {} @Override @@ -3318,5 +3429,12 @@ public class DisplayModeDirectorTest { PEAK_REFRESH_RATE_URI); } } + + void notifyMinRefreshRateChanged() { + if (mMinRefreshRateObserver != null) { + mMinRefreshRateObserver.dispatchChange(false /*selfChange*/, + MIN_REFRESH_RATE_URI); + } + } } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java new file mode 100644 index 000000000000..d5a92cbc927f --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java @@ -0,0 +1,130 @@ +/* + * 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.notifications; + +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED; +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED; +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE; +import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE; + +import static org.junit.Assume.assumeFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.usb.DisplayPortAltModeInfo; +import android.hardware.usb.UsbManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.Injector; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link ConnectedDisplayUsbErrorsDetector} + * Run: atest ConnectedDisplayUsbErrorsDetectorTest + */ +@SmallTest +@RunWith(TestParameterInjector.class) +public class ConnectedDisplayUsbErrorsDetectorTest { + @Mock + private Injector mMockedInjector; + @Mock + private UsbManager mMockedUsbManager; + @Mock + private DisplayManagerFlags mMockedFlags; + @Mock + private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener; + + /** Setup tests. */ + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNoErrorTypes( + @TestParameter final boolean isUsbManagerAvailable, + @TestParameter final boolean isUsbErrorsNotificationEnabled) { + // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure + assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled); + var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled); + // None of these should trigger an error now. + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp()); + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure()); + verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any()); + verify(mMockedListener, never()).onCableNotCapableDisplayPort(); + verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure(); + } + + @Test + public void testErrorOnUsbCableNotCapableDp() { + var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true, + /*isUsbErrorsNotificationEnabled=*/ true); + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp()); + verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any()); + verify(mMockedListener).onCableNotCapableDisplayPort(); + verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure(); + } + + @Test + public void testErrorOnDpLinkTrainingFailure() { + var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true, + /*isUsbErrorsNotificationEnabled=*/ true); + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure()); + verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any()); + verify(mMockedListener, never()).onCableNotCapableDisplayPort(); + verify(mMockedListener).onDisplayPortLinkTrainingFailure(); + } + + private ConnectedDisplayUsbErrorsDetector createErrorsDetector( + final boolean isUsbManagerAvailable, + final boolean isConnectedDisplayUsbErrorsNotificationEnabled) { + when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()) + .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled); + when(mMockedInjector.getUsbManager()).thenReturn( + (isUsbManagerAvailable) ? mMockedUsbManager : null); + var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags, + ApplicationProvider.getApplicationContext(), mMockedInjector); + detector.registerListener(mMockedListener); + return detector; + } + + private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() { + return new DisplayPortAltModeInfo( + DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED, + DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0); + } + + private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() { + return new DisplayPortAltModeInfo( + DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED, + DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false, + LINK_TRAINING_STATUS_FAILURE); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java new file mode 100644 index 000000000000..1d2034be4acb --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java @@ -0,0 +1,148 @@ +/* + * 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.notifications; + +import static android.app.Notification.FLAG_ONGOING_EVENT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.DisplayNotificationManager.Injector; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link DisplayNotificationManager} + * Run: atest DisplayNotificationManagerTest + */ +@SmallTest +@RunWith(TestParameterInjector.class) +public class DisplayNotificationManagerTest { + @Mock + private Injector mMockedInjector; + @Mock + private NotificationManager mMockedNotificationManager; + @Mock + private DisplayManagerFlags mMockedFlags; + @Captor + private ArgumentCaptor<String> mNotifyTagCaptor; + @Captor + private ArgumentCaptor<Integer> mNotifyNoteIdCaptor; + @Captor + private ArgumentCaptor<Notification> mNotifyAsUserNotificationCaptor; + + /** Setup tests. */ + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNotificationOnHotplugConnectionError() { + var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, + /*isErrorHandlingEnabled=*/ true); + dnm.onHotplugConnectionError(); + assertExpectedNotification(); + } + + @Test + public void testNotificationOnDisplayPortLinkTrainingFailure() { + var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, + /*isErrorHandlingEnabled=*/ true); + dnm.onDisplayPortLinkTrainingFailure(); + assertExpectedNotification(); + } + + @Test + public void testNotificationOnCableNotCapableDisplayPort() { + var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, + /*isErrorHandlingEnabled=*/ true); + dnm.onCableNotCapableDisplayPort(); + assertExpectedNotification(); + } + + @Test + public void testNoErrorNotification( + @TestParameter final boolean isNotificationManagerAvailable, + @TestParameter final boolean isErrorHandlingEnabled) { + /* This case is tested by #testNotificationOnHotplugConnectionError, + #testNotificationOnDisplayPortLinkTrainingFailure, + #testNotificationOnCableNotCapableDisplayPort */ + assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled); + var dnm = createDisplayNotificationManager(isNotificationManagerAvailable, + isErrorHandlingEnabled); + // None of these methods should trigger a notification now. + dnm.onHotplugConnectionError(); + dnm.onDisplayPortLinkTrainingFailure(); + dnm.onCableNotCapableDisplayPort(); + verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any()); + } + + private DisplayNotificationManager createDisplayNotificationManager( + final boolean isNotificationManagerAvailable, + final boolean isErrorHandlingEnabled) { + when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn( + isErrorHandlingEnabled); + when(mMockedInjector.getNotificationManager()).thenReturn( + (isNotificationManagerAvailable) ? mMockedNotificationManager : null); + // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest + when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ null); + final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags, + ApplicationProvider.getApplicationContext(), mMockedInjector); + displayNotificationManager.onBootCompleted(); + return displayNotificationManager; + } + + private void assertExpectedNotification() { + verify(mMockedNotificationManager).notify( + mNotifyTagCaptor.capture(), + mNotifyNoteIdCaptor.capture(), + mNotifyAsUserNotificationCaptor.capture()); + assertThat(mNotifyTagCaptor.getValue()).isEqualTo("DisplayNotificationManager"); + assertThat((int) mNotifyNoteIdCaptor.getValue()).isEqualTo(1); + final var notification = mNotifyAsUserNotificationCaptor.getValue(); + assertThat(notification.getChannelId()).isEqualTo("ALERTS"); + assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR); + assertThat(notification.visibility).isEqualTo(Notification.VISIBILITY_PUBLIC); + assertThat(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(0); + assertThat(notification.when).isEqualTo(0); + assertThat(notification.getTimeoutAfter()).isEqualTo(30000L); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index 7cc01e1b4292..4329b6fbc8e3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -160,6 +160,8 @@ public class PrefetchControllerTest { mPrefetchController = new PrefetchController(mJobSchedulerService); mPcConstants = mPrefetchController.getPcConstants(); + setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS); + setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT); verify(mUsageStatsManagerInternal) diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml index 0115db63e56b..ef19ba11fb6d 100644 --- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml +++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml @@ -40,6 +40,7 @@ mediaSharedWithParent='true' credentialShareableWithParent='false' showInSettings='23' + hideInSettingsInQuietMode='true' inheritDevicePolicy='450' deleteAppWithParent='false' alwaysVisible='true' diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java index 8a057df7e836..0988eeab5913 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java @@ -366,6 +366,7 @@ public class FlashNotificationsControllerTest { private void assumeCameraTorchAvailable() { assumeTrue(mCameraManager != null); assumeTrue(!mCameraInfoMap.isEmpty()); + assumeTrue(mCameraInfoMap.values().stream().anyMatch(info -> info.mIsValid)); } private void simulateCallSequence() throws InterruptedException { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 430f600a6288..caa9e7c3aecb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -1065,7 +1065,7 @@ public class FullScreenMagnificationGestureHandlerTest { mMgh.clearAndTransitionToStateDetecting(); break; case STATE_ACTIVATED: - if (mMgh.mDetectTripleTap) { + if (mMgh.mDetectSingleFingerTripleTap) { goFromStateIdleTo(STATE_2TAPS); tap(); } else { @@ -1147,7 +1147,7 @@ public class FullScreenMagnificationGestureHandlerTest { break; case STATE_ACTIVATED: case STATE_ZOOMED_OUT_FROM_SERVICE: - if (mMgh.mDetectTripleTap) { + if (mMgh.mDetectSingleFingerTripleTap) { tap(); tap(); returnToNormalFrom(STATE_ACTIVATED_2TAPS); diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java index 70527ce2ad32..44d676052352 100644 --- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java @@ -238,7 +238,7 @@ public class AnrTimerTest { } @Override - Handler getHandler(Handler.Callback callback) { + Handler newHandler(Handler.Callback callback) { if (mTestHandler == null) { mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate); } @@ -250,14 +250,18 @@ public class AnrTimerTest { return mTestHandler; } + /** + * This override returns the tracker supplied in the constructor. It does not create a + * new one. + */ @Override - AnrTimer.CpuTracker getTracker() { + AnrTimer.CpuTracker newTracker() { return mTracker; } /** For test purposes, always enable the feature. */ @Override - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return true; } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 0230d77e8e14..e3e708ec856d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -47,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -64,6 +65,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -1751,6 +1753,45 @@ public class BiometricServiceTest { verifyNoMoreInteractions(callback); } + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_authenticationAlreadyStarted() + throws Exception { + final IBiometricPromptStatusListener callback = + mock(IBiometricPromptStatusListener.class); + + setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + mBiometricService.mImpl.registerBiometricPromptStatusListener(callback); + + verify(callback).onBiometricPromptShowing(); + } + + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_startAuth_dismissDialog() + throws Exception { + final IBiometricPromptStatusListener listener = + mock(IBiometricPromptStatusListener.class); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + mBiometricService.mImpl.registerBiometricPromptStatusListener(listener); + waitForIdle(); + + verify(listener).onBiometricPromptIdle(); + + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + waitForIdle(); + + verify(listener).onBiometricPromptShowing(); + + final byte[] hat = generateRandomHAT(); + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, hat); + waitForIdle(); + + verify(listener, times(2)).onBiometricPromptIdle(); + } + // Helper methods private int invokeCanAuthenticate(BiometricService service, int authenticators) diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index 9e5a0479ea70..3a3dd6ea2746 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.content.ComponentName; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.common.AuthenticateReason; import android.hardware.biometrics.common.ICancellationSignal; @@ -59,6 +60,7 @@ import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.face.UsageStats; import org.junit.Before; @@ -103,7 +105,7 @@ public class FaceAuthenticationClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -112,6 +114,8 @@ public class FaceAuthenticationClientTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock private BiometricManager mBiometricManager; + @Mock + private LockoutTracker mLockoutTracker; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -236,27 +240,63 @@ public class FaceAuthenticationClientTest { verify(mCallback).onClientFinished(client, true); } + @Test + public void authWithNoLockout() throws RemoteException { + when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( + LockoutTracker.LOCKOUT_NONE); + + final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); + client.start(mCallback); + + verify(mHal).authenticate(OP_ID); + } + + @Test + public void authWithLockout() throws RemoteException { + when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( + LockoutTracker.LOCKOUT_PERMANENT); + + final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); + client.start(mCallback); + + verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), + eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt()); + verify(mHal, never()).authenticate(anyInt()); + } + private FaceAuthenticationClient createClient() throws RemoteException { return createClient(2 /* version */, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */); + false /* allowBackgroundAuthentication */, + null /* lockoutTracker */); } private FaceAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(2 /* version */, null /* listener */, - true /* allowBackgroundAuthentication */); + true /* allowBackgroundAuthentication */, + null /* lockoutTracker */); } private FaceAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, mClientMonitorCallbackConverter, - false /* allowBackgroundAuthentication */); + false /* allowBackgroundAuthentication */, + null /* lockoutTracker */); + } + + private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker) + throws RemoteException { + return createClient(0 /* version */, + mClientMonitorCallbackConverter, + true /* allowBackgroundAuthentication */, + lockoutTracker); } private FaceAuthenticationClient createClient(int version, ClientMonitorCallbackConverter listener, - boolean allowBackgroundAuthentication) throws RemoteException { + boolean allowBackgroundAuthentication, + LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder() .setOpPackageName("test-owner") .setUserId(USER_ID) @@ -270,7 +310,7 @@ public class FaceAuthenticationClientTest { false /* restricted */, options, 4 /* cookie */, false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, - mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication, + mUsageStats, lockoutTracker, allowBackgroundAuthentication, null /* sensorPrivacyManager */, 0 /* biometricStrength */) { @Override protected ActivityTaskManager getActivityTaskManager() { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java index ade3e8275157..fbf0e13c2ac9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -87,7 +87,7 @@ public class FaceDetectClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -170,7 +170,7 @@ public class FaceDetectClientTest { private FaceDetectClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceDetectClient(mContext, () -> aidl, mToken, 99 /* requestId */, mClientMonitorCallbackConverter, new FaceAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java index 54d116f07805..128f3149e6d4 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java @@ -83,7 +83,7 @@ public class FaceEnrollClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -150,7 +150,7 @@ public class FaceEnrollClientTest { private FaceEnrollClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter, USER_ID, HAT, "com.foo.bar", 44 /* requestId */, mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java new file mode 100644 index 000000000000..c8bfaa90d863 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java @@ -0,0 +1,102 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceGenerateChallengeClientTest { + private static final String TAG = "FaceGenerateChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FaceGenerateChallengeClient mClient; + + @Before + public void setUp() throws RemoteException { + when(mAidlSession.getSession()).thenReturn(mSession); + doAnswer(invocation -> { + mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).generateChallenge(); + } + + @Test + public void generateChallenge() throws RemoteException { + createClient(mListener); + mClient.start(mCallback); + + verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void generateChallenge_nullListener() { + createClient(null); + mClient.start(mCallback); + + verify(mCallback).onClientFinished(mClient, false); + } + + private void createClient(ClientMonitorCallbackConverter listener) { + mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java new file mode 100644 index 000000000000..9d0c84edb366 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java @@ -0,0 +1,105 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +@Presubmit +@SmallTest +public class FaceGetFeatureClientTest { + private static final String TAG = "FaceGetFeatureClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; + private FaceGetFeatureClient mClient; + + @Before + public void setUp() throws RemoteException { + mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature); + + when(mAidlSession.getSession()).thenReturn(mSession); + doAnswer(invocation -> { + mClient.onFeatureGet(true, new byte[]{ + AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)}); + return null; + }).when(mSession).getFeatures(); + } + + @Test + public void getFeature() throws RemoteException { + ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class); + mClient.start(mCallback); + + verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(), + featureState.capture()); + assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature)); + assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java new file mode 100644 index 000000000000..1b4c01723027 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java @@ -0,0 +1,187 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.annotation.NonNull; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Presubmit +@SmallTest +public class FaceInternalCleanupClientTest { + private static final String TAG = "FaceInternalCleanupClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Face> mBiometricUtils; + @Mock + private Map<Integer, Long> mAuthenticatorIds; + + private final List<Face> mEnrolledList = new ArrayList<>(); + private final int mBiometricId = 1; + private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); + private FaceInternalCleanupClient mClient; + private List<Integer> mAddedIds; + + @Before + public void setUp() throws RemoteException { + when(mAidlSession.getSession()).thenReturn(mSession); + + mEnrolledList.add(mFace); + mAddedIds = new ArrayList<>(); + mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils, + mAuthenticatorIds) { + @Override + protected void onAddUnknownTemplate(int userId, + @NonNull BiometricAuthenticator.Identifier identifier) { + mAddedIds.add(identifier.getBiometricId()); + } + }; + } + + @Test + public void removesUnknownTemplate() throws Exception { + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0); + } + + assertThat(mAddedIds).isEmpty(); + final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); + + verify(mSession, times(2)).removeEnrollments(captor.capture()); + assertThat(captor.getAllValues().stream() + .flatMap(x -> Arrays.stream(x).boxed()) + .collect(Collectors.toList())) + .containsExactly(1, 2); + verify(mCallback).onClientFinished(eq(mClient), eq(true)); + } + + @Test + public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception { + mClient.setFavorHalEnrollments(); + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + + assertThat(mAddedIds).containsExactly(1, 2); + verify(mSession, never()).removeEnrollments(any()); + verify(mCallback).onClientFinished(eq(mClient), eq(true)); + } + + @Test + public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { + final List<Face> templates = List.of( + new Face("one", 1, 1), + new Face("two", 2, 1), + new Face("three", 3, 1) + ); + mClient.start(mCallback); + for (int i = templates.size() - 1; i >= 0; i--) { + mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); + } + + // The first template is removed after enumeration + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); + + // Simulate finishing the removal of the first template. + // |remaining| is 0 because one FaceRemovalClient is associated with only one + // biometrics ID. + mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0); + + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + + // Simulate finishing the removal of the second template. + mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0); + + assertThat(mClient.getUnknownHALTemplates()).isEmpty(); + } + + @Test + public void noUnknownTemplates() throws RemoteException { + mClient.start(mCallback); + mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mSession, never()).removeEnrollments(any()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java new file mode 100644 index 000000000000..8d74fd1d56be --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java @@ -0,0 +1,169 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class FaceInternalEnumerateClientTest { + private static final String TAG = "FaceInternalEnumerateClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Face> mBiometricUtils; + + private final int mBiometricId = 1; + private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); + private FaceInternalEnumerateClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + final List<Face> enrolled = new ArrayList<>(); + enrolled.add(mFace); + mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID, + TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext); + } + + @Test + public void internalCleanupClient_noTemplatesRemaining() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(mFace, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(null, 1); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback, never()).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(null, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_templatesRemaining() throws RemoteException { + final Face identifier = new Face("face", 2, 1); + doAnswer(invocation -> { + mClient.onEnumerationResult(identifier, 1); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback, never()).onClientFinished(mClient, true); + } + + @Test + public void internalCleanupClient_differentIdentifier_noTemplatesRemaining() + throws RemoteException { + final Face identifier = new Face("face", 2, 1); + doAnswer(invocation -> { + mClient.onEnumerationResult(identifier, 0); + return null; + }).when(mSession).enumerateEnrollments(); + + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java index 76a5accc5fe9..1d9e9334f043 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java @@ -76,7 +76,7 @@ public class FaceRemovalClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private BiometricUtils<Face> mUtils; @Mock @@ -115,7 +115,7 @@ public class FaceRemovalClientTest { private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FaceRemovalClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter, biometricIds, USER_ID, "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java new file mode 100644 index 000000000000..dbbd69bdd3b5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java @@ -0,0 +1,121 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceResetLockoutClientTest { + private static final String TAG = "FaceResetLockoutClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + private final byte[] mHardwareAuthToken = new byte[69]; + @Mock + private LockoutCache mLockoutTracker; + @Mock + private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; + + private FaceResetLockoutClient mClient; + + @Before + public void setUp() { + mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID, + mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker, + mLockoutResetDispatcher, BIOMETRIC_STRONG); + + when(mAidlSession.getSession()).thenReturn(mSession); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + } + + @Test + public void testResetLockout_onLockoutCleared() throws RemoteException { + doAnswer(invocation -> { + mClient.onLockoutCleared(); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1); + verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); + verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void testResetLockout_onError() throws RemoteException { + doAnswer(invocation -> { + mClient.onError(0, 0); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID, + BIOMETRIC_STRONG, -1); + verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID, + LockoutTracker.LOCKOUT_NONE); + verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mCallback).onClientFinished(mClient, false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java new file mode 100644 index 000000000000..fb5502a1795d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java @@ -0,0 +1,88 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceRevokeChallengeClientTest { + private static final String TAG = "FaceRevokeChallengeClientTest"; + private static final long CHALLENGE = 200L; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FaceRevokeChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); + } + + @Test + public void revokeChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java new file mode 100644 index 000000000000..eb8cc9c63e75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java @@ -0,0 +1,120 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceSetFeatureClientTest { + private static final String TAG = "FaceSetFeatureClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; + private final boolean mEnabled = true; + private final byte[] mHardwareAuthToken = new byte[69]; + private FaceSetFeatureClient mClient; + TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID, + TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled, + mHardwareAuthToken); + } + + @Test + public void setFeature_onFeatureSet() throws RemoteException { + doAnswer(invocation -> { + mClient.onFeatureSet(true); + return null; + }).when(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled)); + mClient.start(mCallback); + + verify(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + verify(mListener).onFeatureSet(true, mFeature); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void setFeature_onError() throws RemoteException { + doAnswer(invocation -> { + mClient.onError(0, 0); + return null; + }).when(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + mClient.start(mCallback); + + verify(mSession).setFeature(any(), + eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), + eq(mEnabled)); + verify(mListener).onFeatureSet(false, mFeature); + verify(mCallback).onClientFinished(mClient, false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index be9f52e00b16..7a293e80c183 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -74,7 +74,7 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock - private Sensor.HalSessionCallback.Callback mHalSessionCallback; + private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @Mock @@ -94,7 +94,7 @@ public class SensorTest { private final LockoutCache mLockoutCache = new LockoutCache(); private UserAwareBiometricScheduler mScheduler; - private Sensor.HalSessionCallback mHalCallback; + private AidlResponseHandler mHalCallback; @Before public void setUp() { @@ -111,10 +111,9 @@ public class SensorTest { mBiometricService, () -> USER_ID, mUserSwitchCallback); - mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), - TAG, mScheduler, SENSOR_ID, - USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mHalSessionCallback); + mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + mHardwareUnavailableCallback); } @Test @@ -153,11 +152,11 @@ public class SensorTest { sensorProps.commonProps.sensorId = 1; final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, sensorProps.sensorType, sensorProps.supportsDetectInteraction, sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); - final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, - internalProp, mLockoutResetDispatcher, mBiometricContext); + final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, + null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext); mScheduler.reset(); @@ -181,7 +180,7 @@ public class SensorTest { sensorProps.commonProps.sensorId = 1; final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, sensorProps.sensorType, sensorProps.supportsDetectInteraction, sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..9a40e8a7201a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,342 @@ +/* + * 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.biometrics.sensors.face.hidl; + +import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC; +import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.face.EnrollmentType; +import android.hardware.biometrics.face.Feature; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.OptionalUint64; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricsFace mSession; + @Mock + FaceManager mFaceManager; + @Mock + private AidlResponseHandler mAidlResponseHandler; + @Mock + private HardwareAuthToken mHardwareAuthToken; + @Mock + private Clock mClock; + + private final long mChallenge = 100L; + private AidlToHidlAdapter mAidlToHidlAdapter; + private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */); + private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY; + private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION}; + + @Before + public void setUp() throws RemoteException { + TestableContext testableContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + testableContext.addMockSystemService(FaceManager.class, mFaceManager); + mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */, + mAidlResponseHandler, mClock); + mHardwareAuthToken.timestamp = new Timestamp(); + mHardwareAuthToken.mac = new byte[10]; + final OptionalUint64 result = new OptionalUint64(); + result.status = Status.OK; + result.value = mChallenge; + + when(mSession.generateChallenge(anyInt())).thenReturn(result); + when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace)); + } + + @Test + public void testGenerateChallengeCache() throws RemoteException { + verify(mSession).setCallback(any()); + + final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class); + + mAidlToHidlAdapter.generateChallenge(); + + verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC); + verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture()); + assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge); + + forwardTime(10 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + forwardTime(20 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + + //Confirms that the challenge is cached and the hal method is not called again + verifyNoMoreInteractions(mSession); + verify(mAidlResponseHandler, times(3)) + .onChallengeGenerated(mChallenge); + + forwardTime(60 /* seconds */); + mAidlToHidlAdapter.generateChallenge(); + + //HAL method called after challenge has timed out + verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC); + } + + @Test + public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException { + for (int i = 0; i < 3; i++) { + mAidlToHidlAdapter.generateChallenge(); + forwardTime(10 /* seconds */); + } + for (int i = 0; i < 3; i++) { + mAidlToHidlAdapter.revokeChallenge(0); + forwardTime((i + 1) * 10 /* seconds */); + } + + verify(mSession).revokeChallenge(); + } + + @Test + public void testRevokeChallenge_timeout() throws RemoteException { + mAidlToHidlAdapter.generateChallenge(); + mAidlToHidlAdapter.generateChallenge(); + forwardTime(700); + mAidlToHidlAdapter.generateChallenge(); + mAidlToHidlAdapter.revokeChallenge(0); + + verify(mSession).revokeChallenge(); + } + + @Test + public void testEnroll() throws RemoteException { + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken, + EnrollmentType.DEFAULT, mFeatures, + null /* previewSurface */); + ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class); + + verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture()); + + ArrayList<Integer> features = featureCaptor.getValue(); + + assertThat(features).containsExactly( + AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0])); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testAuthenticate() throws RemoteException { + final int operationId = 2; + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + + verify(mSession).authenticate(operationId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testDetectInteraction() throws RemoteException { + ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + + verify(mSession).authenticate(0); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testEnumerateEnrollments() throws RemoteException { + mAidlToHidlAdapter.enumerateEnrollments(); + + verify(mSession).enumerate(); + } + + @Test + public void testRemoveEnrollment() throws RemoteException { + final int[] enrollments = new int[]{1}; + mAidlToHidlAdapter.removeEnrollments(enrollments); + + verify(mSession).remove(enrollments[0]); + } + + @Test + public void testGetFeatures_onResultSuccess() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.OK; + result.value = true; + ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); + assertThat(featureRetrieved.getValue()[0]).isEqualTo( + AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)); + } + + @Test + public void testGetFeatures_onResultFailed() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.OK; + result.value = false; + ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); + assertThat(featureRetrieved.getValue().length).isEqualTo(0); + } + + @Test + public void testGetFeatures_onStatusFailed() throws RemoteException { + final OptionalBool result = new OptionalBool(); + result.status = Status.INTERNAL_ERROR; + result.value = false; + + when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + + mAidlToHidlAdapter.setFeature(mFeature); + mAidlToHidlAdapter.getFeatures(); + + verify(mSession).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); + verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0); + } + + @Test + public void testGetFeatures_featureNotSet() throws RemoteException { + mAidlToHidlAdapter.getFeatures(); + + verify(mSession, never()).getFeature(eq(mFeature), anyInt()); + verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); + } + + @Test + public void testSetFeatureSuccessful() throws RemoteException { + byte feature = Feature.REQUIRE_ATTENTION; + boolean enabled = true; + + when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK); + + mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + + verify(mAidlResponseHandler).onFeatureSet(feature); + } + + @Test + public void testSetFeatureFailed() throws RemoteException { + byte feature = Feature.REQUIRE_ATTENTION; + boolean enabled = true; + + when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())) + .thenReturn(Status.INTERNAL_ERROR); + + mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + + verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, + 0 /* vendorCode */); + } + + @Test + public void testGetAuthenticatorId() throws RemoteException { + final long authenticatorId = 2L; + final OptionalUint64 result = new OptionalUint64(); + result.status = Status.OK; + result.value = authenticatorId; + + when(mSession.getAuthenticatorId()).thenReturn(result); + + mAidlToHidlAdapter.getAuthenticatorId(); + + verify(mSession).getAuthenticatorId(); + verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId); + } + + @Test + public void testResetLockout() throws RemoteException { + mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + + ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class); + + verify(mSession).resetLockout(hatCaptor.capture()); + + assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken)); + } + + private ArrayList<Byte> processHAT(HardwareAuthToken hat) { + ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); + for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { + hardwareAuthToken.add(b); + } + return hardwareAuthToken; + } + + private void forwardTime(long seconds) { + when(mClock.millis()).thenReturn(seconds * 1000); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 8a11e31014d5..79a528c59f49 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -53,11 +53,15 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.test.TestLooper; 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.testing.TestableContext; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -66,6 +70,7 @@ import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker; import org.junit.Before; import org.junit.Rule; @@ -102,6 +107,9 @@ public class FingerprintAuthenticationClientTest { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Mock private ISession mHal; @@ -124,7 +132,7 @@ public class FingerprintAuthenticationClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -135,6 +143,8 @@ public class FingerprintAuthenticationClientTest { private AuthSessionCoordinator mAuthSessionCoordinator; @Mock private Clock mClock; + @Mock + private LockoutTracker mLockoutTracker; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -425,37 +435,65 @@ public class FingerprintAuthenticationClientTest { verify(mCallback).onClientFinished(client, true); } + @Test + public void testLockoutTracker_authSuccess() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, + mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), true /* authenticated */, new ArrayList<>()); + + verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); + verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testLockoutTracker_authFailed() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1 /* version */, + true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, + mLockoutTracker); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), false /* authenticated */, new ArrayList<>()); + + verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt()); + verify(mLockoutTracker).addFailedAttemptForUser(USER_ID); + } + private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithoutBackgroundAuth() throws RemoteException { return createClient(100 /* version */, false /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClient(int version) throws RemoteException { return createClient(version, true /* allowBackgroundAuthentication */, - mClientMonitorCallbackConverter); + mClientMonitorCallbackConverter, null); } private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */, - null /* listener */); + null, /* listener */null); } private FingerprintAuthenticationClient createClient(int version, - boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener) + boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener, + LockoutTracker lockoutTracker) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder() .setOpPackageName("test-owner") - .setUserId(5) - .setSensorId(9) + .setUserId(USER_ID) + .setSensorId(SENSOR_ID) .build(); return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken, REQUEST_ID, listener, OP_ID, @@ -463,10 +501,11 @@ public class FingerprintAuthenticationClientTest { false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, - null /* taskStackListener */, null /* lockoutCache */, + null /* taskStackListener */, mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps, - new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) { + new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock, + lockoutTracker) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java index 78d3a9dd9f9e..a467c84845cd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -83,7 +83,7 @@ public class FingerprintDetectClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -150,7 +150,7 @@ public class FingerprintDetectClientTest { @Test public void testWhenListenerIsNull() { - final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler); final FingerprintDetectClient client = new FingerprintDetectClient(mContext, () -> aidl, mToken, 6 /* requestId */, null /* listener */, new FingerprintAuthenticateOptions.Builder() @@ -173,7 +173,7 @@ public class FingerprintDetectClientTest { private FingerprintDetectClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FingerprintDetectClient(mContext, () -> aidl, mToken, 6 /* requestId */, mClientMonitorCallbackConverter, new FingerprintAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index ef253801b118..c7eb1db3e74b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -102,7 +102,7 @@ public class FingerprintEnrollClientTest { @Mock private ClientMonitorCallback mCallback; @Mock - private Sensor.HalSessionCallback mHalSessionCallback; + private AidlResponseHandler mAidlResponseHandler; @Mock private Probe mLuxProbe; @Captor @@ -291,7 +291,7 @@ public class FingerprintEnrollClientTest { private FingerprintEnrollClient createClient(int version) throws RemoteException { when(mHal.getInterfaceVersion()).thenReturn(version); - final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler); return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID, mClientMonitorCallbackConverter, 0 /* userId */, HAT, "owner", mBiometricUtils, 8 /* sensorId */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java new file mode 100644 index 000000000000..840961938cb2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java @@ -0,0 +1,92 @@ +/* + * 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.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintGenerateChallengeClientTest { + private static final String TAG = "FingerprintGenerateChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FingerprintGenerateChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken, + mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); + } + + @Test + public void generateChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + return null; + }).when(mSession).generateChallenge(); + mClient.start(mCallback); + + verify(mSession).generateChallenge(); + verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java index 580644347c84..c9482ceb00f5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.Fingerprint; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.testing.TestableContext; @@ -41,7 +42,6 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -70,9 +70,9 @@ public class FingerprintInternalCleanupClientTest { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Mock - private AidlSession mAidlSession; + ISession mSession; @Mock - private ISession mSession; + private AidlSession mAidlSession; @Mock private BiometricLogger mLogger; @Mock @@ -87,15 +87,16 @@ public class FingerprintInternalCleanupClientTest { @Before public void setup() { - when(mAidlSession.getSession()).thenReturn(mSession); mAddedIds = new ArrayList<>(); + + when(mAidlSession.getSession()).thenReturn(mSession); } - @Ignore("TODO(b/229015801): verify cleanup behavior") @Test public void removesUnknownTemplate() throws Exception { mClient = createClient(); + final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); final List<Fingerprint> templates = List.of( new Fingerprint("one", 1, 1), new Fingerprint("two", 2, 1) @@ -108,8 +109,8 @@ public class FingerprintInternalCleanupClientTest { mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0); } + verify(mSession).enumerateEnrollments(); assertThat(mAddedIds).isEmpty(); - final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); verify(mSession, times(2)).removeEnrollments(captor.capture()); assertThat(captor.getAllValues().stream() .flatMap(x -> Arrays.stream(x).boxed()) @@ -132,13 +133,15 @@ public class FingerprintInternalCleanupClientTest { mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); } + verify(mSession).enumerateEnrollments(); assertThat(mAddedIds).containsExactly(1, 2); verify(mSession, never()).removeEnrollments(any()); verify(mCallback).onClientFinished(eq(mClient), eq(true)); } @Test - public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { + public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() + throws RemoteException { mClient = createClient(); final List<Fingerprint> templates = List.of( @@ -150,6 +153,8 @@ public class FingerprintInternalCleanupClientTest { for (int i = templates.size() - 1; i >= 0; i--) { mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); } + + verify(mSession).enumerateEnrollments(); // The first template is removed after enumeration assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java new file mode 100644 index 000000000000..723f916f99c8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java @@ -0,0 +1,123 @@ +/* + * 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.biometrics.sensors.fingerprint.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Presubmit +@SmallTest +public class FingerprintInternalEnumerateClientTest { + private static final String TAG = "FingerprintInternalEnumerateClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Fingerprint> mBiometricUtils; + + private FingerprintInternalEnumerateClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + List<Fingerprint> enrolled = new ArrayList<>(); + enrolled.add(new Fingerprint("one", 1, 1)); + mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken, + USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, + mBiometricContext); + } + + @Test + public void internalEnumerate_unknownTemplates() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1); + mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0); + return null; + }).when(mSession).enumerateEnrollments(); + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().stream() + .flatMap(x -> Stream.of(x.getBiometricId())) + .collect(Collectors.toList())).containsExactly(2, 3); + verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void internalEnumerate_noUnknownTemplates() throws RemoteException { + doAnswer(invocation -> { + mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0); + return null; + }).when(mSession).enumerateEnrollments(); + mClient.start(mCallback); + + verify(mSession).enumerateEnrollments(); + assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java new file mode 100644 index 000000000000..64f07e20e958 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java @@ -0,0 +1,147 @@ +/* + * 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.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Presubmit +@SmallTest +public class FingerprintRemovalClientTest { + private static final String TAG = "FingerprintRemovalClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mListener; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Fingerprint> mBiometricUtils; + @Mock + private Map<Integer, Long> mAuthenticatorIds; + + private FingerprintRemovalClient mClient; + private int[] mBiometricIds = new int[]{1, 2}; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener, + mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID, + mBiometricLogger, mBiometricContext, mAuthenticatorIds); + } + + @Test + public void removalMultipleFingerprints() throws RemoteException { + when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn( + List.of(new Fingerprint("three", 3, 1))); + doAnswer(invocation -> { + mClient.onRemoved(new Fingerprint("one", 1, 1), 1); + mClient.onRemoved(new Fingerprint("two", 2, 1), 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), + eq(USER_ID), anyInt()); + verifyNoMoreInteractions(mAuthenticatorIds); + verify(mListener, times(2)).onRemoved(any(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void removeFingerprint_nullIdentifier() throws RemoteException { + doAnswer(invocation -> { + mClient.onRemoved(null, 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt()); + verify(mCallback).onClientFinished(mClient, false); + } + + @Test + public void removeFingerprints_noFingerprintEnrolled() throws RemoteException { + doAnswer(invocation -> { + mClient.onRemoved(new Fingerprint("one", 1, 1), 1); + mClient.onRemoved(new Fingerprint("two", 2, 1), 0); + return null; + }).when(mSession).removeEnrollments(mBiometricIds); + when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>()); + + mClient.start(mCallback); + + verify(mSession).removeEnrollments(mBiometricIds); + verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), + eq(USER_ID), anyInt()); + verify(mAuthenticatorIds).put(USER_ID, 0L); + verify(mListener, times(2)).onRemoved(any(), anyInt()); + verify(mCallback).onClientFinished(mClient, true); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java new file mode 100644 index 000000000000..a4746dea7ac5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java @@ -0,0 +1,105 @@ +/* + * 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.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintResetLockoutClientTest { + private static final String TAG = "FingerprintResetLockoutClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private LockoutTracker mLockoutTracker; + @Mock + private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; + + private FingerprintResetLockoutClient mClient; + + @Before + public void setUp() { + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, + SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69], + mLockoutTracker, mLockoutResetDispatcher, + BiometricManager.Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void resetLockout_onLockoutCleared() throws RemoteException { + doAnswer(invocation -> { + mClient.onLockoutCleared(); + return null; + }).when(mSession).resetLockout(any()); + mClient.start(mCallback); + + verify(mSession).resetLockout(any()); + verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); + verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); + verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); + verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), + eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java new file mode 100644 index 000000000000..f19b2f7da8a5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java @@ -0,0 +1,101 @@ +/* + * 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.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintRevokeChallengeClientTest { + private static final String TAG = "FingerprintRevokeChallengeClientTest"; + private static final int USER_ID = 2; + private static final int SENSOR_ID = 4; + private static final long CHALLENGE = 200; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AidlSession mAidlSession; + @Mock + private ISession mSession; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Context mContext; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + + private FingerprintRevokeChallengeClient mClient; + + @Before + public void setUp() { + when(mAidlSession.getSession()).thenReturn(mSession); + + mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken, + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); + } + + @Test + public void revokeChallenge_sameChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(CHALLENGE); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, true); + } + + @Test + public void revokeChallenge_differentChallenge() throws RemoteException { + doAnswer(invocation -> { + mClient.onChallengeRevoked(CHALLENGE + 1); + return null; + }).when(mSession).revokeChallenge(CHALLENGE); + mClient.start(mCallback); + + verify(mSession).revokeChallenge(CHALLENGE); + verify(mCallback).onClientFinished(mClient, false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 15d7601dde34..410260074fbd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -75,7 +75,7 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock - private Sensor.HalSessionCallback.Callback mHalSessionCallback; + private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @Mock @@ -97,7 +97,7 @@ public class SensorTest { private final LockoutCache mLockoutCache = new LockoutCache(); private UserAwareBiometricScheduler mScheduler; - private Sensor.HalSessionCallback mHalCallback; + private AidlResponseHandler mHalCallback; @Before public void setUp() { @@ -113,10 +113,9 @@ public class SensorTest { mBiometricService, () -> USER_ID, mUserSwitchCallback); - mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), - TAG, mScheduler, SENSOR_ID, - USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mHalSessionCallback); + mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, + mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, + mHardwareUnavailableCallback); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..b78ba82bd8fe --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,148 @@ +/* + * 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.biometrics.sensors.fingerprint.hidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IBiometricsFingerprint mSession; + @Mock + private AidlResponseHandler mAidlResponseHandler; + @Mock + private HardwareAuthToken mHardwareAuthToken; + + private final long mChallenge = 100L; + private final int mUserId = 0; + private AidlToHidlAdapter mAidlToHidlAdapter; + + @Before + public void setUp() { + mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId, + mAidlResponseHandler); + mHardwareAuthToken.timestamp = new Timestamp(); + mHardwareAuthToken.mac = new byte[10]; + } + + @Test + public void testGenerateChallenge() throws RemoteException { + when(mSession.preEnroll()).thenReturn(mChallenge); + mAidlToHidlAdapter.generateChallenge(); + + verify(mSession).preEnroll(); + verify(mAidlResponseHandler).onChallengeGenerated(mChallenge); + } + + @Test + public void testRevokeChallenge() throws RemoteException { + mAidlToHidlAdapter.revokeChallenge(mChallenge); + + verify(mSession).postEnroll(); + verify(mAidlResponseHandler).onChallengeRevoked(0L); + } + + @Test + public void testEnroll() throws RemoteException { + final ICancellationSignal cancellationSignal = + mAidlToHidlAdapter.enroll(mHardwareAuthToken); + + verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC)); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testAuthenticate() throws RemoteException { + final int operationId = 2; + final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + + verify(mSession).authenticate(operationId, mUserId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testDetectInteraction() throws RemoteException { + final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + + verify(mSession).authenticate(0 /* operationId */, mUserId); + + cancellationSignal.cancel(); + + verify(mSession).cancel(); + } + + @Test + public void testEnumerateEnrollments() throws RemoteException { + mAidlToHidlAdapter.enumerateEnrollments(); + + verify(mSession).enumerate(); + } + + @Test + public void testRemoveEnrollment() throws RemoteException { + final int[] enrollmentIds = new int[]{1}; + mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + + verify(mSession).remove(mUserId, enrollmentIds[0]); + } + + @Test + public void testRemoveMultipleEnrollments() throws RemoteException { + final int[] enrollmentIds = new int[]{1, 2}; + mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + + verify(mSession).remove(mUserId, 0); + } + + @Test + public void testResetLockout() throws RemoteException { + mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + + verify(mAidlResponseHandler).onLockoutCleared(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 61b30a024478..e8cbcf9a6874 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -233,6 +233,7 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceManagerService mVdms; private VirtualDeviceManagerInternal mLocalService; private VirtualDeviceManagerService.VirtualDeviceManagerImpl mVdm; + private VirtualDeviceManagerService.VirtualDeviceManagerNativeImpl mVdmNative; private VirtualDeviceLog mVirtualDeviceLog; @Mock private InputController.NativeWrapper mNativeWrapperMock; @@ -340,6 +341,7 @@ public class VirtualDeviceManagerServiceTest { mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY); mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS); mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME); + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM); doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); @@ -384,6 +386,7 @@ public class VirtualDeviceManagerServiceTest { mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); mVdm = mVdms.new VirtualDeviceManagerImpl(); + mVdmNative = mVdms.new VirtualDeviceManagerNativeImpl(); mVirtualDeviceLog = new VirtualDeviceLog(mContext); mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1); mSensorController = mDeviceImpl.getSensorControllerForTest(); @@ -440,24 +443,32 @@ public class VirtualDeviceManagerServiceTest { public void getDevicePolicy_invalidDeviceId_returnsDefault() { assertThat(mVdm.getDevicePolicy(DEVICE_ID_INVALID, POLICY_TYPE_SENSORS)) .isEqualTo(DEVICE_POLICY_DEFAULT); + assertThat(mVdmNative.getDevicePolicy(DEVICE_ID_INVALID, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); } @Test public void getDevicePolicy_defaultDeviceId_returnsDefault() { assertThat(mVdm.getDevicePolicy(DEVICE_ID_DEFAULT, POLICY_TYPE_SENSORS)) .isEqualTo(DEVICE_POLICY_DEFAULT); + assertThat(mVdmNative.getDevicePolicy(DEVICE_ID_DEFAULT, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); } @Test public void getDevicePolicy_nonExistentDeviceId_returnsDefault() { assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS)) .isEqualTo(DEVICE_POLICY_DEFAULT); + assertThat(mVdmNative.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); } @Test public void getDevicePolicy_unspecifiedPolicy_returnsDefault() { assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) .isEqualTo(DEVICE_POLICY_DEFAULT); + assertThat(mVdmNative.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); } @Test @@ -472,6 +483,8 @@ public class VirtualDeviceManagerServiceTest { assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) .isEqualTo(DEVICE_POLICY_CUSTOM); + assertThat(mVdmNative.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_CUSTOM); } @Test @@ -567,8 +580,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void getDeviceIdsForUid_noRunningApps_returnsNull() { - Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); - assertThat(deviceIds).isEmpty(); + assertThat(mLocalService.getDeviceIdsForUid(UID_1)).isEmpty(); + assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).isEmpty(); } @Test @@ -577,8 +590,8 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged( Sets.newArraySet(UID_2)); - Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); - assertThat(deviceIds).isEmpty(); + assertThat(mLocalService.getDeviceIdsForUid(UID_1)).isEmpty(); + assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).isEmpty(); } @Test @@ -587,8 +600,9 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged( Sets.newArraySet(UID_1)); - Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); - assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId()); + int deviceId = mDeviceImpl.getDeviceId(); + assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly(deviceId); + assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly(deviceId); } @Test @@ -598,8 +612,9 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged( Sets.newArraySet(UID_1, UID_2)); - Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); - assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId()); + int deviceId = mDeviceImpl.getDeviceId(); + assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly(deviceId); + assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly(deviceId); } @Test @@ -611,8 +626,9 @@ public class VirtualDeviceManagerServiceTest { secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged( Sets.newArraySet(UID_1)); - Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); - assertThat(deviceIds).containsExactly(secondDevice.getDeviceId()); + int deviceId = secondDevice.getDeviceId(); + assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly(deviceId); + assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly(deviceId); } @Test @@ -628,8 +644,9 @@ public class VirtualDeviceManagerServiceTest { secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged( Sets.newArraySet(UID_1, UID_2)); - Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); - assertThat(deviceIds).containsExactly( + assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly( + mDeviceImpl.getDeviceId(), secondDevice.getDeviceId()); + assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly( mDeviceImpl.getDeviceId(), secondDevice.getDeviceId()); } diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index e7777f75b6df..67b70684eede 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -436,6 +436,24 @@ public class ContentCaptureManagerServiceTest { verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS); } + @Test + public void parseContentProtectionGroupsConfig_null() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_empty() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_notEmpty() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty(); + } + private class TestContentCaptureManagerService extends ContentCaptureManagerService { TestContentCaptureManagerService() { diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 7d735632b675..7dcfc88e998c 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -284,6 +284,7 @@ public final class DeviceStateManagerServiceTest { assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier()); } + @FlakyTest(bugId = 297949293) @Test public void getDeviceStateInfo_baseStateAndCommittedStateNotSet() throws RemoteException { // Create a provider and a service without an initial base state. 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 184c976b86bf..3de167e72ba0 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 @@ -336,7 +336,8 @@ public final class UpdatableFontDirTest { return new FontConfig(Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( - Collections.singletonList(family), "sans-serif")), 0, 1); + Collections.singletonList(family), "sans-serif")), + Collections.emptyList(), 0, 1); }; UpdatableFontDir dirForPreparation = new UpdatableFontDir( @@ -499,7 +500,8 @@ public final class UpdatableFontDirTest { Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( - Collections.singletonList(family), "sans-serif")), 0, 1); + Collections.singletonList(family), "sans-serif")), + Collections.emptyList(), 0, 1); }); dir.loadFontFileMap(); @@ -651,7 +653,8 @@ public final class UpdatableFontDirTest { FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); return new FontConfig(Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( - Collections.singletonList(family), "sans-serif")), 0, 1); + Collections.singletonList(family), "sans-serif")), + Collections.emptyList(), 0, 1); }); dir.loadFontFileMap(); 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 d85768dd7588..f94aff706a67 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 @@ -26,6 +26,7 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -66,6 +67,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.testutils.OffsettableClock; import com.android.server.wm.WindowManagerInternal; @@ -128,6 +130,14 @@ public class MediaProjectionManagerServiceTest { } }; + private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector = + new MediaProjectionManagerService.Injector() { + @Override + MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { + return mMediaProjectionMetricsLogger; + } + }; + private Context mContext; private MediaProjectionManagerService mService; private OffsettableClock mClock; @@ -142,6 +152,8 @@ public class MediaProjectionManagerServiceTest { private PackageManager mPackageManager; @Mock private IMediaProjectionWatcherCallback mWatcherCallback; + @Mock + private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @Captor private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; @@ -734,6 +746,25 @@ public class MediaProjectionManagerServiceTest { } @Test + public void setContentRecordingSession_success_logsCaptureInProgress() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + + service.setContentRecordingSession(DISPLAY_SESSION); + + verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange( + projection.uid, + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN + ); + } + + @Test public void setContentRecordingSession_notifiesListenersOnCallbackLooper() throws Exception { mService = new MediaProjectionManagerService(mContext, mTestLooperInjector); diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java new file mode 100644 index 000000000000..949f8e7a6ab0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java @@ -0,0 +1,335 @@ +/* + * 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.net; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.ContextWrapper; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.HandlerThread; +import android.text.TextUtils; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.R; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.server.connectivity.Vpn; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LockdownVpnTrackerTest { + private static final NetworkCapabilities TEST_CELL_NC = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + .build(); + private static final LinkProperties TEST_CELL_LP = new LinkProperties(); + + static { + TEST_CELL_LP.setInterfaceName("rmnet0"); + TEST_CELL_LP.addLinkAddress(new LinkAddress("192.0.2.2/25")); + } + + // Use a context wrapper instead of a mock since LockdownVpnTracker builds notifications which + // is tedious and currently unnecessary to mock. + private final Context mContext = new ContextWrapper(InstrumentationRegistry.getContext()) { + @Override + public Object getSystemService(String name) { + if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; + if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager; + + return super.getSystemService(name); + } + }; + @Mock private ConnectivityManager mCm; + @Mock private Vpn mVpn; + @Mock private NotificationManager mNotificationManager; + @Mock private NetworkInfo mVpnNetworkInfo; + @Mock private VpnConfig mVpnConfig; + @Mock private Network mNetwork; + @Mock private Network mNetwork2; + @Mock private Network mVpnNetwork; + + private HandlerThread mHandlerThread; + private Handler mHandler; + private VpnProfile mProfile; + + private VpnProfile createTestVpnProfile() { + final String profileName = "testVpnProfile"; + final VpnProfile profile = new VpnProfile(profileName); + profile.name = "My VPN"; + profile.server = "192.0.2.1"; + profile.dnsServers = "8.8.8.8"; + profile.ipsecIdentifier = "My ipsecIdentifier"; + profile.ipsecSecret = "My PSK"; + profile.type = VpnProfile.TYPE_IKEV2_IPSEC_PSK; + + return profile; + } + + private NetworkCallback getDefaultNetworkCallback() { + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mCm).registerSystemDefaultNetworkCallback(callbackCaptor.capture(), eq(mHandler)); + return callbackCaptor.getValue(); + } + + private NetworkCallback getVpnNetworkCallback() { + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mCm).registerNetworkCallback(any(), callbackCaptor.capture(), eq(mHandler)); + return callbackCaptor.getValue(); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread("LockdownVpnTrackerTest"); + mHandlerThread.start(); + mHandler = mHandlerThread.getThreadHandler(); + + doReturn(mVpnNetworkInfo).when(mVpn).getNetworkInfo(); + doReturn(false).when(mVpnNetworkInfo).isConnectedOrConnecting(); + doReturn(mVpnConfig).when(mVpn).getLegacyVpnConfig(); + // mVpnConfig is a mock but the production code will try to add addresses in this array + // assuming it's non-null, so it needs to be initialized. + mVpnConfig.addresses = new ArrayList<>(); + + mProfile = createTestVpnProfile(); + } + + @After + public void tearDown() throws Exception { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread.join(); + } + } + + private LockdownVpnTracker initAndVerifyLockdownVpnTracker() { + final LockdownVpnTracker lockdownVpnTracker = + new LockdownVpnTracker(mContext, mHandler, mVpn, mProfile); + lockdownVpnTracker.init(); + verify(mVpn).setEnableTeardown(false); + verify(mVpn).setLockdown(true); + verify(mCm).setLegacyLockdownVpnEnabled(true); + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + + return lockdownVpnTracker; + } + + private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network, + NetworkCapabilities nc, LinkProperties lp, boolean blocked) { + callback.onAvailable(network); + callback.onCapabilitiesChanged(network, nc); + callback.onLinkPropertiesChanged(network, lp); + callback.onBlockedStatusChanged(network, blocked); + } + + private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network) { + callCallbacksForNetworkConnect( + callback, network, TEST_CELL_NC, TEST_CELL_LP, true /* blocked */); + } + + private boolean isExpectedNotification(Notification notification, int titleRes, int iconRes) { + if (!NOTIFICATION_CHANNEL_VPN.equals(notification.getChannelId())) { + return false; + } + final CharSequence expectedTitle = mContext.getString(titleRes); + final CharSequence actualTitle = notification.extras.getCharSequence( + Notification.EXTRA_TITLE); + if (!TextUtils.equals(expectedTitle, actualTitle)) { + return false; + } + return notification.getSmallIcon().getResId() == iconRes; + } + + @Test + public void testShutdown() { + final LockdownVpnTracker lockdownVpnTracker = initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + final NetworkCallback vpnCallback = getVpnNetworkCallback(); + clearInvocations(mVpn, mCm, mNotificationManager); + + lockdownVpnTracker.shutdown(); + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mVpn).setLockdown(false); + verify(mCm).setLegacyLockdownVpnEnabled(false); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + verify(mVpn).setEnableTeardown(true); + verify(mCm).unregisterNetworkCallback(defaultCallback); + verify(mCm).unregisterNetworkCallback(vpnCallback); + } + + @Test + public void testDefaultNetworkConnected() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + clearInvocations(mVpn, mCm, mNotificationManager); + + // mNetwork connected and available. + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + + // Vpn is starting + verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork, TEST_CELL_LP); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected))); + } + + private void doTestDefaultLpChanged(LinkProperties startingLp, LinkProperties newLp) { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + callCallbacksForNetworkConnect( + defaultCallback, mNetwork, TEST_CELL_NC, startingLp, true /* blocked */); + clearInvocations(mVpn, mCm, mNotificationManager); + + // LockdownVpnTracker#handleStateChangedLocked() is not called on the same network even if + // the LinkProperties change. + defaultCallback.onLinkPropertiesChanged(mNetwork, newLp); + + // Ideally the VPN should start if it hasn't already, but it doesn't because nothing calls + // LockdownVpnTracker#handleStateChangedLocked. This is a bug. + // TODO: consider fixing this. + verify(mVpn, never()).stopVpnRunnerPrivileged(); + verify(mVpn, never()).startLegacyVpnPrivileged(any(), any(), any()); + verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + } + + @Test + public void testDefaultLPChanged_V4AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(TEST_CELL_LP); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + doTestDefaultLpChanged(TEST_CELL_LP, lp); + } + + @Test + public void testDefaultLPChanged_V4AddLinkAddressV6() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + final LinkProperties newLp = new LinkProperties(lp); + newLp.addLinkAddress(new LinkAddress("2001:db8::1/64")); + doTestDefaultLpChanged(lp, newLp); + } + + @Test + public void testDefaultLPChanged_V6AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("2001:db8::1/64")); + final LinkProperties newLp = new LinkProperties(lp); + newLp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + doTestDefaultLpChanged(lp, newLp); + } + + @Test + public void testDefaultLPChanged_AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + doTestDefaultLpChanged(lp, TEST_CELL_LP); + } + + @Test + public void testDefaultNetworkChanged() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + final NetworkCallback vpnCallback = getVpnNetworkCallback(); + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + clearInvocations(mVpn, mCm, mNotificationManager); + + // New network and LinkProperties received + final NetworkCapabilities wifiNc = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + .build(); + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName("wlan0"); + callCallbacksForNetworkConnect( + defaultCallback, mNetwork2, wifiNc, wifiLp, true /* blocked */); + + // Vpn is restarted. + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork2, wifiLp); + verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected))); + + // Vpn is Connected + doReturn(true).when(mVpnNetworkInfo).isConnectedOrConnecting(); + doReturn(true).when(mVpnNetworkInfo).isConnected(); + vpnCallback.onAvailable(mVpnNetwork); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connected, R.drawable.vpn_connected))); + + } + + @Test + public void testSystemDefaultLost() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + // mNetwork connected + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + clearInvocations(mVpn, mCm, mNotificationManager); + + defaultCallback.onLost(mNetwork); + + // Vpn is stopped + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index 9f75cf8d552e..253592c9a07d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -43,6 +43,7 @@ import android.annotation.UserIdInt; import android.app.PropertyInvalidatedCache; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; +import android.multiuser.Flags; import android.os.Looper; import android.os.Parcel; import android.os.UserHandle; @@ -124,18 +125,34 @@ public class UserManagerServiceUserInfoTest { mUserManagerService.putUserInfo(data.info); - // Set a global and user restriction so they get written out to the user file. + //Local restrictions are written to the user specific files and global restrictions + // are written to the SYSTEM user file. setUserRestrictions(data.info.id, globalRestriction, localRestriction, true); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos); mUserManagerService.writeUserLP(data, out); - byte[] bytes = baos.toByteArray(); + byte[] secondaryUserBytes = baos.toByteArray(); + baos.reset(); + + byte[] systemUserBytes = new byte[0]; + if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) { + UserData systemUserData = new UserData(); + systemUserData.info = mUserManagerService.getUserInfo(UserHandle.USER_SYSTEM); + mUserManagerService.writeUserLP(systemUserData, baos); + systemUserBytes = baos.toByteArray(); + } // Clear the restrictions to see if they are properly read in from the user file. setUserRestrictions(data.info.id, globalRestriction, localRestriction, false); - mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes)); + //read the secondary and SYSTEM user file to fetch local/global device policy restrictions. + mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes)); + if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) { + mUserManagerService.readUserLP(UserHandle.USER_SYSTEM, + new ByteArrayInputStream(systemUserBytes)); + } + assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction)); assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction)); } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java index a54bc914f686..c684a7bbf884 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java @@ -60,6 +60,7 @@ public class UserManagerServiceUserPropertiesTest { .setShowInLauncher(21) .setStartWithParent(false) .setShowInSettings(45) + .setHideInSettingsInQuietMode(false) .setInheritDevicePolicy(67) .setUseParentsContacts(false) .setCrossProfileIntentFilterAccessControl(10) @@ -72,6 +73,7 @@ public class UserManagerServiceUserPropertiesTest { final UserProperties actualProps = new UserProperties(defaultProps); actualProps.setShowInLauncher(14); actualProps.setShowInSettings(32); + actualProps.setHideInSettingsInQuietMode(true); actualProps.setInheritDevicePolicy(51); actualProps.setUseParentsContacts(true); actualProps.setCrossProfileIntentFilterAccessControl(20); @@ -228,6 +230,8 @@ public class UserManagerServiceUserPropertiesTest { assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher()); assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent()); assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings()); + assertThat(expected.getHideInSettingsInQuietMode()) + .isEqualTo(actual.getHideInSettingsInQuietMode()); assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy()); assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts()); assertThat(expected.getCrossProfileIntentFilterAccessControl()) diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index e3579b4b9ef6..20270a817831 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -90,6 +90,7 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(true) .setCredentialShareableWithParent(false) .setShowInSettings(900) + .setHideInSettingsInQuietMode(true) .setInheritDevicePolicy(340) .setDeleteAppWithParent(true) .setAlwaysVisible(true); @@ -160,6 +161,7 @@ public class UserManagerServiceUserTypeTest { assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent()); assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent()); assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings()); + assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(340, type.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent()); @@ -217,6 +219,7 @@ public class UserManagerServiceUserTypeTest { assertFalse(props.isCredentialShareableWithParent()); assertFalse(props.getDeleteAppWithParent()); assertFalse(props.getAlwaysVisible()); + assertFalse(props.getHideInSettingsInQuietMode()); assertFalse(type.hasBadge()); } @@ -304,6 +307,7 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) .setShowInSettings(20) + .setHideInSettingsInQuietMode(false) .setInheritDevicePolicy(21) .setDeleteAppWithParent(true) .setAlwaysVisible(false); @@ -344,6 +348,7 @@ public class UserManagerServiceUserTypeTest { assertTrue(aospType.getDefaultUserPropertiesReference() .isCredentialShareableWithParent()); assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings()); + assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(21, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); @@ -390,6 +395,7 @@ public class UserManagerServiceUserTypeTest { assertFalse(aospType.getDefaultUserPropertiesReference() .isCredentialShareableWithParent()); assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings()); + assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(450, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index d1f4961ab7e5..8891413dd964 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -43,6 +43,8 @@ android_test { // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", + "flag-junit", + "notification_flags_lib", ], libs: [ diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 974238427587..42ad73a23f0e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -32,6 +32,7 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -147,6 +148,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { private static final int CUSTOM_LIGHT_ON = 10000; private static final int CUSTOM_LIGHT_OFF = 10000; private static final int MAX_VIBRATION_DELAY = 1000; + private static final float DEFAULT_VOLUME = 1.0f; @Before public void setUp() throws Exception { @@ -397,19 +399,22 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // private void verifyNeverBeep() throws RemoteException { - verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat()); } private void verifyBeepUnlooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeepLooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeep(int times) throws RemoteException { - verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(), + eq(DEFAULT_VOLUME)); } private void verifyNeverStopAudio() throws RemoteException { @@ -905,7 +910,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyDelayedVibrate( mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false)); verify(mRingtonePlayer, never()).playAsync - (anyObject(), anyObject(), anyBoolean(), anyObject()); + (anyObject(), anyObject(), anyBoolean(), anyObject(), anyFloat()); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 81867df74abd..9bd938f2e0a7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -57,6 +57,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.AudioAttributes; @@ -66,9 +67,11 @@ import android.os.Handler; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -87,8 +90,11 @@ import com.android.server.UiServiceTestCase; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.pm.PackageManagerService; + +import java.util.List; import java.util.Objects; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -102,6 +108,8 @@ import org.mockito.verification.VerificationMode; @RunWith(AndroidJUnit4.class) @SuppressLint("GuardedBy") public class NotificationAttentionHelperTest extends UiServiceTestCase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock AudioManager mAudioManager; @Mock Vibrator mVibrator; @@ -115,6 +123,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { IAccessibilityManager mAccessibilityService; @Mock KeyguardManager mKeyguardManager; + @Mock + private UserManager mUserManager; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -134,6 +144,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private AccessibilityManager mAccessibilityManager; private static final NotificationAttentionHelper.Signals DEFAULT_SIGNALS = new NotificationAttentionHelper.Signals(false, 0); + private static final NotificationAttentionHelper.Signals WORK_PROFILE_SIGNALS = + new NotificationAttentionHelper.Signals(true, 0); private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1); private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0); @@ -151,6 +163,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private static final int CUSTOM_LIGHT_ON = 10000; private static final int CUSTOM_LIGHT_OFF = 10000; private static final int MAX_VIBRATION_DELAY = 1000; + private static final float DEFAULT_VOLUME = 1.0f; @Before public void setUp() throws Exception { @@ -178,6 +191,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // TODO (b/291907312): remove feature flag mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true); + // Disable feature flags by default. Tests should enable as needed. + mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS); mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, mNotificationInstanceIdSequence)); @@ -189,9 +204,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private void initAttentionHelper(TestableFlagResolver flagResolver) { mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class), - mAccessibilityManager, getContext().getPackageManager(), mUsageStats, + mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats, mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver); - mAttentionHelper.setVibratorHelper(new VibratorHelper(getContext())); + mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext()))); mAttentionHelper.setAudioManager(mAudioManager); mAttentionHelper.setSystemReady(true); mAttentionHelper.setLights(mLight); @@ -282,6 +297,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { true /* noisy */, true /* buzzy*/, false /* lights */); } + private NotificationRecord getBuzzyBeepyNotification(UserHandle userHandle) { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, true /* buzzy*/, false /* lights */, userHandle); + } + private NotificationRecord getLightsNotification() { return getNotificationRecord(mId, false /* insistent */, false /* once */, false /* noisy */, false /* buzzy*/, true /* lights */); @@ -312,7 +332,13 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, boolean noisy, boolean buzzy, boolean lights) { return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy, - lights, null, Notification.GROUP_ALERT_ALL, false); + lights, null, Notification.GROUP_ALERT_ALL, false, mUser); + } + + private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, + boolean noisy, boolean buzzy, boolean lights, UserHandle userHandle) { + return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy, + lights, null, Notification.GROUP_ALERT_ALL, false, userHandle); } private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, @@ -320,25 +346,25 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { boolean noisy, boolean buzzy, boolean lights) { return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true, - null, Notification.GROUP_ALERT_ALL, true); + null, Notification.GROUP_ALERT_ALL, true, mUser); } private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) { return getNotificationRecord(mId, false, false, true, false, false, true, true, true, - groupKey, groupAlertBehavior, false); + groupKey, groupAlertBehavior, false, mUser); } private NotificationRecord getLightsNotificationRecord(String groupKey, int groupAlertBehavior) { return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true, - true, true, groupKey, groupAlertBehavior, false); + true, true, groupKey, groupAlertBehavior, false, mUser); } private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration, boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior, - boolean isLeanback) { + boolean isLeanback, UserHandle userHandle) { final Builder builder = new Builder(getContext()) .setContentTitle("foo") @@ -399,7 +425,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { .thenReturn(isLeanback); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, - mPid, n, mUser, null, System.currentTimeMillis()); + mPid, n, userHandle, null, System.currentTimeMillis()); NotificationRecord r = new NotificationRecord(context, sbn, mChannel); mService.addNotification(r); return r; @@ -410,19 +436,26 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // private void verifyNeverBeep() throws RemoteException { - verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat()); } private void verifyBeepUnlooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeepLooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeep(int times) throws RemoteException { - verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(), + eq(DEFAULT_VOLUME)); + } + + private void verifyBeepVolume(float volume) throws RemoteException { + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), anyBoolean(), any(), eq(volume)); } private void verifyNeverStopAudio() throws RemoteException { @@ -921,7 +954,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { mAttentionHelper.getVibratorHelper().createFallbackVibration( /* insistent= */ false)); verify(mRingtonePlayer, never()).playAsync(anyObject(), anyObject(), anyBoolean(), - anyObject()); + anyObject(), anyFloat()); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @@ -1948,6 +1981,259 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { assertEquals(-1, r.getLastAudiblyAlertedMs()); } + // TODO b/270456865: Only one of the two strategies will be released. + // The other one need to be removed + @Test + public void testBeepVolume_politeNotif() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 50% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.5f); + + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + // TODO b/270456865: Only one of the two strategies will be released. + // The other one need to be removed + @Test + public void testBeepVolume_politeNotif_Strategy2() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 0% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + + // 2nd update should beep at 50% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.5f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testVibrationIntensity_politeNotif() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBuzzyBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper(); + Mockito.reset(vibratorHelper); + + // update should buzz at 50% intensity + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verify(vibratorHelper, times(1)).scale(any(), eq(0.5f)); + Mockito.reset(vibratorHelper); + + // 2nd update should buzz at 0% intensity + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verify(vibratorHelper, times(1)).scale(any(), eq(0.0f)); + } + + @Test + public void testVibrationIntensity_politeNotif_Strategy2() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBuzzyBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper(); + Mockito.reset(vibratorHelper); + + // update should buzz at 0% intensity + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verify(vibratorHelper, times(1)).scale(any(), eq(0.0f)); + Mockito.reset(vibratorHelper); + + // 2nd update should buzz at 50% intensity + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verify(vibratorHelper, times(1)).scale(any(), eq(0.5f)); + } + + @Test + public void testBuzzOnlyOnScreenUnlock_politeNotif() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + + // When NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED setting is enabled + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, 1); + + initAttentionHelper(flagResolver); + // And screen is unlocked + mAttentionHelper.setUserPresent(true); + + NotificationRecord r = getBuzzyBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + // The notification attention should only buzz + verifyNeverBeep(); + verifyVibrate(); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_disabled() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + + // When NOTIFICATION_COOLDOWN_ENABLED setting is disabled + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0); + + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 100% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_workProfile() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + + final int workProfileUserId = mUser.getIdentifier() + 1; + + // Enable notifications cooldown for work profile + Settings.System.putIntForUser(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 1, workProfileUserId); + + when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn( + List.of(new UserInfo(workProfileUserId, "work_profile", null, + UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE, + UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE)))); + + initAttentionHelper(flagResolver); + + final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId)); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 50% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(0.5f); + + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(0.0f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + + final int workProfileUserId = mUser.getIdentifier() + 1; + + // Disable notifications cooldown for work profile + Settings.System.putIntForUser(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0, workProfileUserId); + + when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn( + List.of(new UserInfo(workProfileUserId, "work_profile", null, + UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE, + UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE)))); + + initAttentionHelper(flagResolver); + + final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId)); + + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 100% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(1.0f); + + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> { private final int mRepeatIndex; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index c05f81497e57..53ca704b6d86 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -23,9 +23,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -49,12 +47,10 @@ import android.graphics.drawable.Icon; import android.os.Binder; import android.os.Build; import android.os.IBinder; -import android.os.Parcel; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; -import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.NotificationRankingUpdate; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; @@ -158,81 +154,6 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { } } - // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking. - @Test - public void testRankingUpdate_parcel() { - NotificationRankingUpdate nru = generateUpdate(); - Parcel parcel = Parcel.obtain(); - nru.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel); - assertEquals(nru, nru1); - } - - // Tests parceling of RankingMap and RankingMap.equals - @Test - public void testRankingMap_parcel() { - RankingMap rmap = generateUpdate().getRankingMap(); - Parcel parcel = Parcel.obtain(); - rmap.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - RankingMap rmap1 = RankingMap.CREATOR.createFromParcel(parcel); - - detailedAssertEquals(rmap, rmap1); - assertEquals(rmap, rmap1); - } - - // Tests parceling of Ranking and Ranking.equals - @Test - public void testRanking_parcel() { - Ranking ranking = generateUpdate().getRankingMap().getRawRankingObject(mKeys[0]); - Parcel parcel = Parcel.obtain(); - ranking.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - Ranking ranking1 = new Ranking(parcel); - detailedAssertEquals("rankings differ: ", ranking, ranking1); - assertEquals(ranking, ranking1); - } - - // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking. - @Test - public void testRankingUpdate_equals() { - NotificationRankingUpdate nru = generateUpdate(); - NotificationRankingUpdate nru2 = generateUpdate(); - detailedAssertEquals(nru, nru2); - assertEquals(nru, nru2); - Ranking tweak = nru2.getRankingMap().getRawRankingObject(mKeys[0]); - tweak.populate( - tweak.getKey(), - tweak.getRank(), - !tweak.matchesInterruptionFilter(), // note the inversion here! - tweak.getLockscreenVisibilityOverride(), - tweak.getSuppressedVisualEffects(), - tweak.getImportance(), - tweak.getImportanceExplanation(), - tweak.getOverrideGroupKey(), - tweak.getChannel(), - (ArrayList) tweak.getAdditionalPeople(), - (ArrayList) tweak.getSnoozeCriteria(), - tweak.canShowBadge(), - tweak.getUserSentiment(), - tweak.isSuspended(), - tweak.getLastAudiblyAlertedMillis(), - tweak.isNoisy(), - (ArrayList) tweak.getSmartActions(), - (ArrayList) tweak.getSmartReplies(), - tweak.canBubble(), - tweak.isTextChanged(), - tweak.isConversation(), - tweak.getConversationShortcutInfo(), - tweak.getRankingAdjustment(), - tweak.isBubble(), - tweak.getProposedImportance(), - tweak.hasSensitiveContent() - ); - assertNotEquals(nru, nru2); - } - @Test public void testLegacyIcons_preM() { TestListenerService service = new TestListenerService(); @@ -275,7 +196,6 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { assertNull(n.largeIcon); } - // Test data private String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"}; @@ -461,48 +381,6 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { } } - private void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) { - detailedAssertEquals(a.getRankingMap(), b.getRankingMap()); - } - - private void detailedAssertEquals(String comment, Ranking a, Ranking b) { - assertEquals(comment, a.getKey(), b.getKey()); - assertEquals(comment, a.getRank(), b.getRank()); - assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter()); - assertEquals(comment, a.getLockscreenVisibilityOverride(), b.getLockscreenVisibilityOverride()); - assertEquals(comment, a.getSuppressedVisualEffects(), b.getSuppressedVisualEffects()); - assertEquals(comment, a.getImportance(), b.getImportance()); - assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation()); - assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey()); - assertEquals(comment, a.getChannel(), b.getChannel()); - assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople()); - assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria()); - assertEquals(comment, a.canShowBadge(), b.canShowBadge()); - assertEquals(comment, a.getUserSentiment(), b.getUserSentiment()); - assertEquals(comment, a.isSuspended(), b.isSuspended()); - assertEquals(comment, a.getLastAudiblyAlertedMillis(), b.getLastAudiblyAlertedMillis()); - assertEquals(comment, a.isNoisy(), b.isNoisy()); - assertEquals(comment, a.getSmartReplies(), b.getSmartReplies()); - assertEquals(comment, a.canBubble(), b.canBubble()); - assertEquals(comment, a.isConversation(), b.isConversation()); - assertEquals(comment, a.getConversationShortcutInfo().getId(), - b.getConversationShortcutInfo().getId()); - assertActionsEqual(a.getSmartActions(), b.getSmartActions()); - assertEquals(a.getProposedImportance(), b.getProposedImportance()); - assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent()); - } - - private void detailedAssertEquals(RankingMap a, RankingMap b) { - Ranking arank = new Ranking(); - Ranking brank = new Ranking(); - assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys()); - for (String key : a.getOrderedKeys()) { - a.getRanking(key, arank); - b.getRanking(key, brank); - detailedAssertEquals("ranking for key <" + key + ">", arank, brank); - } - } - public static class TestListenerService extends NotificationListenerService { private final IBinder binder = new LocalBinder(); public int targetSdk = 0; 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 91129a14ecab..3d4b4a62e5ac 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -687,7 +687,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver = broadcastReceivers.get(i); } if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) { - mUserSwitchIntentReceiver = broadcastReceivers.get(i); + // There may be multiple receivers, get the NMS one + if (broadcastReceivers.get(i).toString().contains( + NotificationManagerService.class.getName())) { + mUserSwitchIntentReceiver = broadcastReceivers.get(i); + } } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp index ca5cfa5b60f5..95441060f0e5 100644 --- a/services/tests/vibrator/Android.bp +++ b/services/tests/vibrator/Android.bp @@ -27,6 +27,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "flag-junit", "frameworks-base-testutils", "frameworks-services-vibrator-testutils", "junit", diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java index bc826a3cf4a6..04158c4d4f93 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java @@ -31,6 +31,8 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.os.VibrationEffect; import android.os.VibratorInfo; +import android.os.vibrator.Flags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.AtomicFile; import android.util.SparseArray; @@ -49,6 +51,8 @@ import java.io.File; import java.io.FileOutputStream; public class HapticFeedbackCustomizationTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public MockitoRule rule = MockitoJUnit.rule(); // Pairs of valid vibration XML along with their equivalent VibrationEffect. @@ -77,6 +81,7 @@ public class HapticFeedbackCustomizationTest { @Before public void setUp() { when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true); + mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); } @Test @@ -87,6 +92,21 @@ public class HapticFeedbackCustomizationTest { } @Test + public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); + // Valid customization XML. + String xml = "<haptic-feedback-constants>" + + "<constant id=\"10\">" + + COMPOSITION_VIBRATION_XML + + "</constant>" + + "</haptic-feedback-constants>"; + setupCustomizationFile(xml); + + assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)) + .isNull(); + } + + @Test public void testParseCustomizations_oneVibrationCustomization_success() throws Exception { String xml = "<haptic-feedback-constants>" + "<constant id=\"10\">" diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index cd0389dce416..ba31944f412a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -46,10 +46,13 @@ import static com.android.server.wm.testing.Assert.assertThrows; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -583,6 +586,7 @@ public class DisplayAreaTest extends WindowTestsBase { final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class); doReturn(mock(IBinder.class)).when(mockDisplayAreaOrganizer).asBinder(); displayArea.mOrganizer = mockDisplayAreaOrganizer; + displayArea.mDisplayAreaAppearedSent = true; spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController); mDisplayContent.addChild(displayArea, 0); @@ -687,6 +691,56 @@ public class DisplayAreaTest extends WindowTestsBase { assertEquals(parent.getChildAt(0), child); } + @Test + public void testSetOrganizer() { + final TaskDisplayArea displayArea = createTaskDisplayArea( + mDisplayContent, mWm, "NewArea", FEATURE_VENDOR_FIRST); + + assertNull(displayArea.mOrganizer); + assertFalse(displayArea.mDisplayAreaAppearedSent); + + final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class); + final DisplayAreaOrganizerController controller = + mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController; + spyOn(controller); + doNothing().when(controller).onDisplayAreaVanished(any(), any()); + + displayArea.setOrganizer(organizer); + + assertEquals(organizer, displayArea.mOrganizer); + assertTrue(displayArea.mDisplayAreaAppearedSent); + verify(controller).onDisplayAreaAppeared(organizer, displayArea); + + // No duplicated appeared sent. + clearInvocations(controller); + displayArea.sendDisplayAreaAppeared(); + + verify(controller, never()).onDisplayAreaAppeared(any(), any()); + + // Sent info changed after appeared. + displayArea.sendDisplayAreaInfoChanged(); + + verify(controller).onDisplayAreaInfoChanged(organizer, displayArea); + + // Sent info vanished after appeared. + displayArea.setOrganizer(null); + + verify(controller).onDisplayAreaVanished(organizer, displayArea); + assertNull(displayArea.mOrganizer); + assertFalse(displayArea.mDisplayAreaAppearedSent); + + // No callback until appeared sent. + clearInvocations(controller); + + displayArea.sendDisplayAreaAppeared(); + displayArea.sendDisplayAreaInfoChanged(); + displayArea.sendDisplayAreaVanished(organizer); + + verify(controller, never()).onDisplayAreaAppeared(any(), any()); + verify(controller, never()).onDisplayAreaInfoChanged(any(), any()); + verify(controller, never()).onDisplayAreaVanished(any(), any()); + } + private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> { private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) { super(wms, ANY, name); diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java index 6c48a6961bc2..9f43a1785266 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -94,6 +94,16 @@ public class SafeActivityOptionsTest { } @Test + public void test_selectiveCloneLunchRemoteTransition() { + final RemoteTransition transition = mock(RemoteTransition.class); + final SafeActivityOptions clone = new SafeActivityOptions( + ActivityOptions.makeRemoteTransition(transition)) + .selectiveCloneLaunchOptions(); + + assertSame(clone.getOriginalOptions().getRemoteTransition(), transition); + } + + @Test public void test_getOptions() { // Mock everything necessary MockitoSession mockingSession = mockitoSession() diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 474720f68731..c8546c613995 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -48,6 +48,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -60,6 +61,7 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -84,6 +86,7 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.ITransitionPlayer; import android.window.RemoteTransition; +import android.window.SystemPerformanceHinter; import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; @@ -2433,6 +2436,45 @@ public class TransitionTests extends WindowTestsBase { assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0); } + @Test + public void testTransitionsTriggerPerformanceHints() { + assumeTrue(explicitRefreshRateHints()); + SystemPerformanceHinter systemPerformanceHinter = mock(SystemPerformanceHinter.class); + final TransitionController controller = new TestTransitionController(mAtm); + final TestTransitionPlayer player = registerTestTransitionPlayer(); + + mSyncEngine = createTestBLASTSyncEngine(); + controller.setSyncEngine(mSyncEngine); + controller.setSystemPerformanceHinter(systemPerformanceHinter); + SystemPerformanceHinter.HighPerfSession session = mock( + SystemPerformanceHinter.HighPerfSession.class); + doReturn(session).when(systemPerformanceHinter).startSession(anyInt(), anyInt(), + anyString()); + + final Transition transitA = createTestTransition(TRANSIT_OPEN, controller); + final Task task = createTask(mDisplayContent, + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + final ActivityRecord act = createActivityRecord(task); + act.setVisibleRequested(true); + act.setVisible(true); + + controller.startCollectOrQueue(transitA, (deferred) -> { + }); + transitA.collect(act); + + verify(systemPerformanceHinter).startSession( + eq(SystemPerformanceHinter.HINT_SF), anyInt(), eq("Transition collected")); + + transitA.start(); + transitA.setAllReady(); + + // Aborting here doesn't abort the transition, it aborts the sync allowing the transition to + // finish successfully. + mSyncEngine.abort(transitA.getSyncId()); + controller.finishTransition(transitA); + verify(session).close(); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 7d9b3790c1b5..def52a517913 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -942,6 +942,7 @@ public final class Call { * @return the Telecom identifier associated with this {@link Call} . This is not a stable * identifier and is not guaranteed to be unique across device reboots. */ + @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES) public @NonNull String getId() { return mTelecomCallId; } /** {@hide} */ @@ -1894,7 +1895,7 @@ public final class Call { * Tones are both played locally for the user to hear and sent to the network to be relayed * to the remote device. * <p> - * You must ensure that any call to {@link #playDtmfTone(char}) is followed by a matching + * You must ensure that any call to {@link #playDtmfTone(char)} is followed by a matching * call to {@link #stopDtmfTone()} and that each tone is stopped before a new one is started. * The play and stop commands are relayed to the underlying * {@link android.telecom.ConnectionService} as executed; implementations may not correctly diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java index 50f2ad4561cc..24d39182add6 100644 --- a/telecomm/java/android/telecom/CallControl.java +++ b/telecomm/java/android/telecom/CallControl.java @@ -225,7 +225,7 @@ public final class CallControl { /** * Request start a call streaming session. On receiving valid request, telecom will bind to - * the {@link CallStreamingService} implemented by a general call streaming sender. So that the + * the {@code CallStreamingService} implemented by a general call streaming sender. So that the * call streaming sender can perform streaming local device audio to another remote device and * control the call during streaming. * diff --git a/telecomm/java/android/telecom/CallControlCallback.java b/telecomm/java/android/telecom/CallControlCallback.java index eac2e64aa2ab..0166022abeb8 100644 --- a/telecomm/java/android/telecom/CallControlCallback.java +++ b/telecomm/java/android/telecom/CallControlCallback.java @@ -69,7 +69,7 @@ public interface CallControlCallback { /** * Telecom is informing the client to answer an incoming call and set it to active. * - * @param videoState see {@link android.telecom.CallAttributes.CallType} for valid states + * @param videoState the video state * @param wasCompleted The {@link Consumer} to be completed. If the client can answer the call * on their end, {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}. diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java index d9f89d544f40..c83804fde8fe 100644 --- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java +++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java @@ -68,7 +68,7 @@ public final class PhoneAccountSuggestion implements Parcelable { /** * Creates a new instance of {@link PhoneAccountSuggestion}. This constructor is intended for - * use by apps implementing a {@link PhoneAccountSuggestionService}, and generally should not be + * use by apps implementing a {@code PhoneAccountSuggestionService}, and generally should not be * used by dialer apps other than for testing purposes. * * @param handle The {@link PhoneAccountHandle} for this suggestion. diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java index 29f436d9f459..ad1b6f9e7665 100644 --- a/telecomm/java/android/telecom/StreamingCall.java +++ b/telecomm/java/android/telecom/StreamingCall.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -25,6 +26,8 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import com.android.server.telecom.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -57,6 +60,7 @@ public final class StreamingCall implements Parcelable { /** * The ID associated with this call. This is the same value as {@link CallControl#getCallId()}. */ + @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES) public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID"; /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 98bbb40282c9..7a0bf9038f7c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5073,7 +5073,6 @@ public class CarrierConfigManager { * MMTEL and RCS. * <p> * The default value for this configuration is {@code false}. - * @see android.telephony.ims.SipDelegateManager */ public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL = KEY_PREFIX + "ims_single_registration_required_bool"; @@ -5648,8 +5647,8 @@ public class CarrierConfigManager { * <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li> * <li>{@link #KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY}</li> * </ul> - * <p> The values are defined in - * {@link ImsRegistrationImplBase.ImsRegistrationTech} + * <p> The values are defined as {@code REGISTRATION_TECH_*} constants in + * {@link android.telephony.ims.stub.ImsRegistrationImplBase}. * * changing mmtel_requires_provisioning_bundle requires changes to * carrier_volte_provisioning_required_bool and vice versa @@ -5661,12 +5660,12 @@ public class CarrierConfigManager { /** * List of different RAT technologies on which Provisioning for Voice calling (IR.92) * is supported. - * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE * <p>Possible values are, - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR} + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE */ public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = KEY_PREFIX + "capability_type_voice_int_array"; @@ -5674,12 +5673,12 @@ public class CarrierConfigManager { /** * List of different RAT technologies on which Provisioning for Video Telephony (IR.94) * is supported. - * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO * <p>Possible values are, - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR} + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO */ public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = KEY_PREFIX + "capability_type_video_int_array"; @@ -5687,24 +5686,24 @@ public class CarrierConfigManager { /** * List of different RAT technologies on which Provisioning for XCAP over Ut for * supplementary services. (IR.92) is supported. - * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT * <p>Possible values are, - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR} + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT */ public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = KEY_PREFIX + "capability_type_ut_int_array"; /** * List of different RAT technologies on which Provisioning for SMS (IR.92) is supported. - * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS * <p>Possible values are, - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR} + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS */ public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = KEY_PREFIX + "capability_type_sms_int_array"; @@ -5712,12 +5711,12 @@ public class CarrierConfigManager { /** * List of different RAT technologies on which Provisioning for Call Composer * (section 2.4 of RCC.20) is supported. - * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER * <p>Possible values are, - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR} + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER */ public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY = KEY_PREFIX + "capability_type_call_composer_int_array"; @@ -5732,8 +5731,8 @@ public class CarrierConfigManager { * <li>{@link #KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY}</li> * <li>{@link #KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY}</li> * </ul> - * <p> The values are defined in - * {@link ImsRegistrationImplBase.ImsRegistrationTech} + * <p> The values are defined as {@code REGISTRATION_TECH_*} constants in + * {@link android.telephony.ims.stub.ImsRegistrationImplBase}. */ public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE = KEY_PREFIX + "rcs_requires_provisioning_bundle"; @@ -5742,12 +5741,11 @@ public class CarrierConfigManager { * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. * If not set, this RcsFeature should not service capability requests. - * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE * <p>Possible values are, - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = KEY_PREFIX + "capability_type_options_uce_int_array"; @@ -5757,12 +5755,11 @@ public class CarrierConfigManager { * framework. If set, the RcsFeature should support capability exchange using a presence * server. If not set, this RcsFeature should not publish capabilities or service capability * requests using presence. - * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE * <p>Possible values are, - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} - * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = KEY_PREFIX + "capability_type_presence_uce_int_array"; @@ -9640,7 +9637,7 @@ public class CarrierConfigManager { /** * A list of premium capabilities the carrier supports. Applications can prompt users to * purchase these premium capabilities from their carrier for a performance boost. - * Valid values are any of {@link TelephonyManager.PremiumCapability}. + * Valid values are any of {@link TelephonyManager}'s {@code PREMIUM_CAPABILITY_*} constants. * * This is empty by default, indicating that no premium capabilities are supported. * @@ -10526,6 +10523,8 @@ public class CarrierConfigManager { auto_data_switch_rat_signal_score_string_bundle.putIntArray( "LTE", new int[]{3731, 5965, 8618, 11179, 13384}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( + "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484}); + auto_data_switch_rat_signal_score_string_bundle.putIntArray( "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428}); diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index f9844bcc677d..a8c077d24ed9 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -16,9 +16,12 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; +import com.android.internal.telephony.flags.Flags; + /** * Describes the cause of a disconnected call. Those disconnect causes can be converted into a more * generic {@link android.telecom.DisconnectCause} object. @@ -363,6 +366,7 @@ public final class DisconnectCause { /** * Indicates that the call was unable to be made because the satellite modem is enabled. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_ENABLED = 82; //********************************************************************************************* diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 631013fc485e..7ccc27e2f94e 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +31,8 @@ import android.telephony.AccessNetworkConstants.TransportType; import android.telephony.Annotation.NetworkType; import android.text.TextUtils; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -206,6 +209,7 @@ public final class NetworkRegistrationInfo implements Parcelable { /** * MMS service */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int SERVICE_TYPE_MMS = 6; /** @hide */ @@ -625,7 +629,7 @@ public final class NetworkRegistrationInfo implements Parcelable { } /** - * @return The access network technology {@link NetworkType}. + * @return The access network technology network type.. */ public @NetworkType int getAccessNetworkTechnology() { return mAccessNetworkTechnology; @@ -702,6 +706,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * * @return {@code true} if network is a non-terrestrial network else {@code false}. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public boolean isNonTerrestrialNetwork() { return mIsNonTerrestrialNetwork; } @@ -1186,6 +1191,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * else {@code false}. * @return The builder. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) { mIsNonTerrestrialNetwork = isNonTerrestrialNetwork; return this; diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 5b8848c72041..85a85c6dfadb 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +36,7 @@ import android.telephony.NetworkRegistrationInfo.Domain; import android.telephony.NetworkRegistrationInfo.NRState; import android.text.TextUtils; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -2256,6 +2258,7 @@ public class ServiceState implements Parcelable { * * @return {@code true} if device is connected to a non-terrestrial network else {@code false}. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public boolean isUsingNonTerrestrialNetwork() { synchronized (mNetworkRegistrationInfos) { for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) { diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index fa5fd875a024..8e90fe7ea975 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1364,7 +1364,7 @@ public class SubscriptionManager { public static class OnSubscriptionsChangedListener { /** - * After {@link Build.VERSION_CODES.Q}, it is no longer necessary to instantiate a + * After {@link Build.VERSION_CODES#Q}, it is no longer necessary to instantiate a * Handler inside of the OnSubscriptionsChangedListener in all cases, so it will only * be done for callers that do not supply an Executor. */ @@ -1388,13 +1388,14 @@ public class SubscriptionManager { /** * Create an OnSubscriptionsChangedListener. * - * For callers targeting {@link Build.VERSION_CODES.P} or earlier, this can only be called + * For callers targeting {@link Build.VERSION_CODES#P} or earlier, this can only be called * on a thread that already has a prepared Looper. Callers targeting Q or later should * subsequently use {@link SubscriptionManager#addOnSubscriptionsChangedListener( * Executor, OnSubscriptionsChangedListener)}. * - * On OS versions prior to {@link Build.VERSION_CODES.V} callers should assume that this - * call will fail if invoked on a thread that does not already have a prepared looper. + * On OS versions prior to {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} callers should + * assume that this call will fail if invoked on a thread that does not already have a + * prepared looper. */ public OnSubscriptionsChangedListener() { mCreatorLooper = Looper.myLooper(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 234ca918a144..90fa69ff8a15 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -25,6 +25,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest; import android.annotation.BytesLong; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.NonNull; @@ -128,6 +129,7 @@ import com.android.internal.telephony.IccLogicalChannelRequest; import com.android.internal.telephony.OperatorInfo; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.RILConstants; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.io.IOException; @@ -1195,6 +1197,7 @@ public class TelephonyManager { * The dialer app receives this event via * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; @@ -13132,8 +13135,8 @@ public class TelephonyManager { * </ul> * * @param executor The executor on which the result listener will be called. - * @param resultListener {@link Consumer} that will be called with the result fetched - * from the radio of type {@link CarrierRestrictionStatus} + * @param resultListener {@link Consumer} that will be called with the carrier restriction + * status result fetched from the radio * @throws SecurityException if the caller does not have the required permission/privileges or * if the caller is not pre-registered. */ @@ -13465,7 +13468,7 @@ public class TelephonyManager { public static final int DATA_ENABLED_REASON_THERMAL = 3; /** - * To indicate data was enabled or disabled due to {@link MobileDataPolicy} overrides. + * To indicate data was enabled or disabled due to mobile data policy overrides. * Note that this is not a valid reason for {@link #setDataEnabledForReason(int, boolean)} and * is only used to indicate that data enabled was changed due to an override. */ @@ -14571,7 +14574,7 @@ public class TelephonyManager { * @param needValidation whether validation is needed before switch happens. * @param executor The executor of where the callback will execute. * @param callback Callback will be triggered once it succeeds or failed. - * See {@link TelephonyManager.SetOpportunisticSubscriptionResult} + * See the {@code SET_OPPORTUNISTIC_SUB_*} constants * for more details. Pass null if don't care about the result. */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA) @@ -15036,6 +15039,7 @@ public class TelephonyManager { * @hide */ @TestApi + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int HAL_SERVICE_SATELLITE = 8; /** @hide */ @@ -17516,7 +17520,7 @@ public class TelephonyManager { public @interface PurchasePremiumCapabilityResult {} /** - * Returns the purchase result {@link PurchasePremiumCapabilityResult} as a String. + * Returns the purchase result as a String. * * @param result The purchase premium capability result. * @return The purchase result as a String. @@ -17570,7 +17574,6 @@ public class TelephonyManager { * @param capability The premium capability to purchase. * @param executor The callback executor for the response. * @param callback The result of the purchase request. - * One of {@link PurchasePremiumCapabilityResult}. * @throws SecurityException if the caller does not hold permissions * READ_BASIC_PHONE_STATE or INTERNET. * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid. diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 26c17a461cf2..e9af486219f7 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -532,7 +532,7 @@ public class ApnSetting implements Parcelable { /** * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought * up by this APN setting. Note this value will only be used when MTU size is not provided - * in {@link DataCallResponse#getMtuV4()} during network bring up. + * in {@code DataCallResponse#getMtuV4()} during network bring up. * * @return the MTU size in bytes of the route. */ @@ -542,7 +542,7 @@ public class ApnSetting implements Parcelable { /** * Returns the MTU size of the IPv6 mobile interface to which the APN connected. Note this value - * will only be used when MTU size is not provided in {@link DataCallResponse#getMtuV6()} + * will only be used when MTU size is not provided in {@code DataCallResponse#getMtuV6()} * during network bring up. * * @return the MTU size in bytes of the route. @@ -1787,7 +1787,7 @@ public class ApnSetting implements Parcelable { /** * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought * up by this APN setting. Note this value will only be used when MTU size is not provided - * in {@link DataCallResponse#getMtuV4()} during network bring up. + * in {@code DataCallResponse#getMtuV4()} during network bring up. * * @param mtuV4 the MTU size in bytes of the route. */ @@ -1799,7 +1799,7 @@ public class ApnSetting implements Parcelable { /** * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv6 routes brought * up by this APN setting. Note this value will only be used when MTU size is not provided - * in {@link DataCallResponse#getMtuV6()} during network bring up. + * in {@code DataCallResponse#getMtuV6()} during network bring up. * * @param mtuV6 the MTU size in bytes of the route. */ diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index ed4627631dd5..b9a7d439114a 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -249,7 +249,7 @@ public class EuiccManager { * * <p>{@link #EXTRA_USE_QR_SCANNER} not set or set to false: The LPA should try to get an * activation code from the carrier app by binding to the carrier app service implementing - * {@link android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE}. + * {@code android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE}. * <p>{@link #EXTRA_USE_QR_SCANNER} set to true: The LPA should launch a QR scanner for the user * to scan an eSIM profile QR code. * diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index caee4e2ca277..2172d7de0dd7 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -561,7 +561,7 @@ public class ImsMmTelManager implements RegistrationManager { * @param c The MmTel {@link CapabilityCallback} to be registered. * @see #unregisterMmTelCapabilityCallback(CapabilityCallback) * @throws ImsException if the subscription associated with this callback is valid, but - * the {@link ImsService} associated with the subscription is not available. This can happen if + * the {@code ImsService} associated with the subscription is not available. This can happen if * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed * reason. */ diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index 4439e5c35d83..2b49bcd4e928 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -247,7 +247,7 @@ public class ImsRcsManager { * @param c The {@link RegistrationManager.RegistrationCallback} to be added. * @see #unregisterImsRegistrationCallback(RegistrationManager.RegistrationCallback) * @throws ImsException if the subscription associated with this callback is valid, but - * the {@link ImsService} associated with the subscription is not available. This can happen if + * the {@code ImsService} associated with the subscription is not available. This can happen if * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed * reason. */ diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 37a6a7ebf953..1c5d1e940030 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -1469,9 +1469,8 @@ public class ProvisioningManager { /** * Get the provisioning status for the IMS MmTel capability specified. * - * If provisioning is not required for the queried - * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and - * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will + * If provisioning is not required for the queried {@code capability} and + * {@code tech} combination specified, this method will * always return {@code true}. * * <p> Requires Permission: @@ -1503,7 +1502,7 @@ public class ProvisioningManager { * Get the provisioning status for the IMS RCS capability specified. * * If provisioning is not required for the queried - * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS + * {@code capability} or if the device does not support IMS * this method will always return {@code true}. * * @see CarrierConfigManager.Ims#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL @@ -1533,7 +1532,7 @@ public class ProvisioningManager { * Get the provisioning status for the IMS RCS capability specified. * * If provisioning is not required for the queried - * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS + * {@code capability} or if the device does not support IMS * this method will always return {@code true}. * * <p> Requires Permission: diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java index 873ce6064cca..b528866371cb 100644 --- a/telephony/java/android/telephony/ims/RegistrationManager.java +++ b/telephony/java/android/telephony/ims/RegistrationManager.java @@ -31,7 +31,6 @@ import android.os.Bundle; import android.telephony.AccessNetworkConstants; import android.telephony.NetworkRegistrationInfo; import android.telephony.ims.aidl.IImsRegistrationCallback; -import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; @@ -42,7 +41,7 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; /** - * Manages IMS Service registration state for associated {@link ImsFeature}s. + * Manages IMS Service registration state for associated {@code ImsFeature}s. */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS) public interface RegistrationManager { @@ -394,7 +393,7 @@ public interface RegistrationManager { * @param c The {@link RegistrationCallback} to be added. * @see #unregisterImsRegistrationCallback(RegistrationCallback) * @throws ImsException if the subscription associated with this callback is valid, but - * the {@link ImsService} associated with the subscription is not available. This can happen if + * the {@code ImsService} associated with the subscription is not available. This can happen if * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed * reason. */ diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java index 22412e6efadf..c690f9852bd0 100644 --- a/telephony/java/android/telephony/satellite/AntennaDirection.java +++ b/telephony/java/android/telephony/satellite/AntennaDirection.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -38,6 +41,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class AntennaDirection implements Parcelable { /** Antenna x axis direction. */ private float mX; @@ -62,11 +66,13 @@ public final class AntennaDirection implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeFloat(mX); out.writeFloat(mY); @@ -74,6 +80,7 @@ public final class AntennaDirection implements Parcelable { } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final Creator<AntennaDirection> CREATOR = new Creator<>() { @Override @@ -118,14 +125,17 @@ public final class AntennaDirection implements Parcelable { return Objects.hash(mX, mY, mZ); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getX() { return mX; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getY() { return mY; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getZ() { return mZ; } diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java index 588be6cae773..8842886d3a1c 100644 --- a/telephony/java/android/telephony/satellite/AntennaPosition.java +++ b/telephony/java/android/telephony/satellite/AntennaPosition.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -29,6 +32,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class AntennaPosition implements Parcelable { /** Antenna direction used for satellite communication. */ @NonNull AntennaDirection mAntennaDirection; @@ -49,17 +53,20 @@ public final class AntennaPosition implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mAntennaDirection, flags); out.writeInt(mSuggestedHoldPosition); } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final Creator<AntennaPosition> CREATOR = new Creator<>() { @Override @@ -100,11 +107,13 @@ public final class AntennaPosition implements Parcelable { } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public AntennaDirection getAntennaDirection() { return mAntennaDirection; } @SatelliteManager.DeviceHoldPosition + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int getSuggestedHoldPosition() { return mSuggestedHoldPosition; } diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java index dc4d38b02f57..022a856e48dd 100644 --- a/telephony/java/android/telephony/satellite/PointingInfo.java +++ b/telephony/java/android/telephony/satellite/PointingInfo.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -30,6 +33,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class PointingInfo implements Parcelable { /** Satellite azimuth in degrees */ private float mSatelliteAzimuthDegrees; @@ -50,16 +54,19 @@ public final class PointingInfo implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeFloat(mSatelliteAzimuthDegrees); out.writeFloat(mSatelliteElevationDegrees); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR = new Creator<PointingInfo>() { @Override @@ -101,10 +108,12 @@ public final class PointingInfo implements Parcelable { return sb.toString(); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getSatelliteAzimuthDegrees() { return mSatelliteAzimuthDegrees; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getSatelliteElevationDegrees() { return mSatelliteElevationDegrees; } diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java index bc45be110ace..0d8f10178971 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -16,12 +16,15 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -34,6 +37,7 @@ import java.util.Set; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteCapabilities implements Parcelable { /** * List of technologies supported by the satellite modem. @@ -76,11 +80,13 @@ public final class SatelliteCapabilities implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) { out.writeInt(mSupportedRadioTechnologies.size()); @@ -106,6 +112,7 @@ public final class SatelliteCapabilities implements Parcelable { } } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() { @Override public SatelliteCapabilities createFromParcel(Parcel in) { @@ -165,6 +172,7 @@ public final class SatelliteCapabilities implements Parcelable { /** * @return The list of technologies supported by the satellite modem. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull @SatelliteManager.NTRadioTechnology public Set<Integer> getSupportedRadioTechnologies() { return mSupportedRadioTechnologies; @@ -176,6 +184,7 @@ public final class SatelliteCapabilities implements Parcelable { * @return {@code true} if UE needs to point to a satellite to send and receive data and * {@code false} otherwise. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public boolean isPointingRequired() { return mIsPointingRequired; } @@ -185,6 +194,7 @@ public final class SatelliteCapabilities implements Parcelable { * * @return The maximum number of bytes per datagram that can be sent over satellite. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int getMaxBytesPerOutgoingDatagram() { return mMaxBytesPerOutgoingDatagram; } @@ -195,6 +205,7 @@ public final class SatelliteCapabilities implements Parcelable { * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition */ @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public Map<Integer, AntennaPosition> getAntennaPositionMap() { return mAntennaPositionMap; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java index 9037f0c4078d..4d67f80241f9 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java @@ -16,17 +16,21 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + /** * SatelliteDatagram is used to store data that is to be sent or received over satellite. * Data is stored in byte array format. * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteDatagram implements Parcelable { /** * Datagram to be sent or received over satellite. @@ -45,15 +49,18 @@ public final class SatelliteDatagram implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeByteArray(mData); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public static final Creator<SatelliteDatagram> CREATOR = new Creator<>() { @Override @@ -73,6 +80,7 @@ public final class SatelliteDatagram implements Parcelable { * satellite provider. Client application should be aware of how to encode the datagram based * upon the satellite provider. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public byte[] getSatelliteDatagram() { return mData; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java index cb2920f9e6ee..b5763c38e69c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java @@ -16,9 +16,12 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + import java.util.function.Consumer; /** @@ -27,6 +30,7 @@ import java.util.function.Consumer; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteDatagramCallback { /** * Called when there is an incoming datagram to be received. @@ -38,6 +42,7 @@ public interface SatelliteDatagramCallback { * that they received the datagram. If the callback is not received within * five minutes, Telephony will resend the datagram. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram, int pendingCount, @NonNull Consumer<Void> callback); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 8dc2de8178c4..7322aeb8bfca 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -63,6 +63,7 @@ import java.util.stream.Collectors; */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE) @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteManager { private static final String TAG = "SatelliteManager"; @@ -108,6 +109,7 @@ public final class SatelliteManager { /** * Exception from the satellite service containing the {@link SatelliteResult} error code. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static class SatelliteException extends Exception { @SatelliteResult private final int mErrorCode; @@ -116,6 +118,7 @@ public final class SatelliteManager { * * @param errorCode The {@link SatelliteResult}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public SatelliteException(@SatelliteResult int errorCode) { mErrorCode = errorCode; } @@ -125,6 +128,7 @@ public final class SatelliteManager { * * @return The {@link SatelliteResult}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int getErrorCode() { return mErrorCode; } @@ -190,104 +194,127 @@ public final class SatelliteManager { /** * The request was successfully processed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SUCCESS = 0; /** * A generic error which should be used only when other specific errors cannot be used. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_ERROR = 1; /** * Error received from the satellite server. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVER_ERROR = 2; /** * Error received from the vendor service. This generic error code should be used * only when the error cannot be mapped to other specific service error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; /** * Error received from satellite modem. This generic error code should be used only when * the error cannot be mapped to other specific modem error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_ERROR = 4; /** * Error received from the satellite network. This generic error code should be used only when * the error cannot be mapped to other specific network error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; /** * Telephony is not in a valid state to receive requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; /** * Satellite modem is not in a valid state to receive requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; /** * Either vendor service, or modem, or Telephony framework has received a request with * invalid arguments from its clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; /** * Telephony framework failed to send a request or receive a response from the vendor service * or satellite modem due to internal error. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; /** * Radio did not start or is resetting. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; /** * The request is not supported by either the satellite modem or the network. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; /** * Satellite modem or network has no resources available to handle requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NO_RESOURCES = 12; /** * Satellite service is not provisioned yet. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; /** * Satellite service provision is already in progress. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; /** * The ongoing request was aborted by either the satellite modem or the network. * This error is also returned when framework decides to abort current send request as one * of the previous send request failed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; /** * The device/subscriber is barred from accessing the satellite service. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; /** * Satellite modem timeout to receive ACK or response from the satellite network after * sending a request to the network. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; /** * Satellite network is not reachable from the modem. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; /** * The device/subscriber is not authorized to register with the satellite service provider. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; /** * The device does not support satellite. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; /** * The current request is already in-progress. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; /** * Satellite modem is currently busy due to which current request cannot be processed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_BUSY = 22; /** @hide */ @@ -323,22 +350,27 @@ public final class SatelliteManager { * Unknown Non-Terrestrial radio technology. This generic radio technology should be used * only when the radio technology cannot be mapped to other specific radio technologies. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; /** * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; /** * 3GPP 5G NR over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; /** * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; /** * Proprietary technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; /** @hide */ @@ -353,12 +385,16 @@ public final class SatelliteManager { public @interface NTRadioTechnology {} /** Suggested device hold position is unknown. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; /** User is suggested to hold the device in portrait mode. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; /** User is suggested to hold the device in landscape mode with left hand. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; /** User is suggested to hold the device in landscape mode with right hand. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; /** @hide */ @@ -372,14 +408,18 @@ public final class SatelliteManager { public @interface DeviceHoldPosition {} /** Display mode is unknown. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_UNKNOWN = 0; /** Display mode of the device used for satellite communication for non-foldable phones. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_FIXED = 1; /** Display mode of the device used for satellite communication for foldabale phones when the * device is opened. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_OPENED = 2; /** Display mode of the device used for satellite communication for foldabable phones when the * device is closed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_CLOSED = 3; /** @hide */ @@ -414,7 +454,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { @@ -457,7 +497,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -512,7 +552,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -565,7 +605,7 @@ public final class SatelliteManager { * * @throws IllegalStateException if the Telephony process is not currently available. */ - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -619,7 +659,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -664,16 +704,19 @@ public final class SatelliteManager { * The default state indicating that datagram transfer is idle. * This should be sent if there are no message transfer activity happening. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; /** * A transition state indicating that a datagram is being sent. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; /** * An end state indicating that datagram sending completed successfully. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; /** * An end state indicating that datagram sending completed with a failure. @@ -681,16 +724,19 @@ public final class SatelliteManager { * must be sent before reporting any additional datagram transfer state changes. All pending * messages will be reported as failed, to the corresponding applications. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; /** * A transition state indicating that a datagram is being received. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; /** * An end state indicating that datagram receiving completed successfully. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; /** * An end state indicating that datagram receive operation found that there are no @@ -698,12 +744,14 @@ public final class SatelliteManager { * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; /** * An end state indicating that datagram receive completed with a failure. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; /** * A transition state indicating that Telephony is waiting for satellite modem to connect to a @@ -721,6 +769,7 @@ public final class SatelliteManager { * only when the datagram transfer state cannot be mapped to other specific datagram transfer * states. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; /** @hide */ @@ -743,26 +792,32 @@ public final class SatelliteManager { /** * Satellite modem is in idle state. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_IDLE = 0; /** * Satellite modem is listening for incoming datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_LISTENING = 1; /** * Satellite modem is sending and/or receiving datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; /** * Satellite modem is retrying to send and/or receive datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; /** * Satellite modem is powered off. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_OFF = 4; /** * Satellite modem is unavailable. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; /** * The satellite modem is powered on but the device is not registered to a satellite cell. @@ -778,6 +833,7 @@ public final class SatelliteManager { * Satellite modem state is unknown. This generic modem state should be used only when the * modem state cannot be mapped to other specific modem states. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; /** @hide */ @@ -799,15 +855,18 @@ public final class SatelliteManager { * Datagram type is unknown. This generic datagram type should be used only when the * datagram type cannot be mapped to other specific datagram types. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_UNKNOWN = 0; /** * Datagram type indicating that the datagram to be sent or received is of type SOS message. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; /** * Datagram type indicating that the datagram to be sent or received is of type * location sharing. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; /** @hide */ @@ -857,7 +916,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener, @NonNull SatelliteTransmissionUpdateCallback callback) { @@ -927,7 +986,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void stopSatelliteTransmissionUpdates( @NonNull SatelliteTransmissionUpdateCallback callback, @NonNull @CallbackExecutor Executor executor, @@ -983,7 +1042,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor executor, @@ -1036,7 +1095,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void deprovisionSatelliteService(@NonNull String token, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { @@ -1076,7 +1135,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int registerForSatelliteProvisionStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteProvisionStateCallback callback) { @@ -1119,7 +1178,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteProvisionStateChanged( @NonNull SatelliteProvisionStateCallback callback) { Objects.requireNonNull(callback); @@ -1158,7 +1217,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1210,7 +1269,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int registerForSatelliteModemStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteStateCallback callback) { @@ -1250,7 +1309,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) { Objects.requireNonNull(callback); ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback); @@ -1288,7 +1347,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int registerForSatelliteDatagram( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteDatagramCallback callback) { @@ -1344,7 +1403,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) { Objects.requireNonNull(callback); ISatelliteDatagramCallback internalCallback = @@ -1383,7 +1442,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { Objects.requireNonNull(executor); @@ -1436,7 +1495,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void sendSatelliteDatagram(@DatagramType int datagramType, @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, @NonNull @CallbackExecutor Executor executor, @@ -1482,7 +1541,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteCommunicationAllowedForCurrentLocation( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { @@ -1540,7 +1599,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Duration, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1594,7 +1653,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void setDeviceAlignedWithSatellite(boolean isAligned) { try { ITelephony telephony = getITelephony(); @@ -1628,7 +1687,7 @@ public final class SatelliteManager { * @param subId The subscription ID of the carrier. * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable. * @param executor The executor on which the error code listener will be called. - * @param resultListener Listener for the {@link SatelliteError} result of the operation. + * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. @@ -1662,7 +1721,7 @@ public final class SatelliteManager { * will return a {@code boolean} with value {@code true} if the satellite * is enabled and {@code false} otherwise. * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} - * will return a {@link SatelliteException} with the {@link SatelliteError}. + * will return a {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. @@ -1688,7 +1747,7 @@ public final class SatelliteManager { * @param subId The subscription ID of the carrier. * @param reason Reason for disallowing satellite communication. * @param executor The executor on which the error code listener will be called. - * @param resultListener Listener for the {@link SatelliteError} result of the operation. + * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. @@ -1731,7 +1790,7 @@ public final class SatelliteManager { * @param subId The subscription ID of the carrier. * @param reason Reason for disallowing satellite communication. * @param executor The executor on which the error code listener will be called. - * @param resultListener Listener for the {@link SatelliteError} result of the operation. + * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java index 71168762cca1..a12952be7620 100644 --- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java @@ -16,14 +16,18 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite provision state change events. * * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteProvisionStateCallback { /** * Called when satellite provision state changes. @@ -33,5 +37,6 @@ public interface SatelliteProvisionStateCallback { * It is generally expected that the provisioning app retries if * provisioning fails. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteProvisionStateChanged(boolean provisioned); } diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java index 812ff2decf5d..bfe6e101ffb4 100644 --- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java @@ -16,18 +16,23 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite modem state change events. * * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteStateCallback { /** * Called when satellite modem state changes. * @param state The new satellite modem state. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state); } diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index 7ac06b04a8eb..e02097019c4c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -16,9 +16,12 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite position update and datagram transfer state change * events. @@ -26,12 +29,14 @@ import android.annotation.SystemApi; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteTransmissionUpdateCallback { /** * Called when the satellite position changed. * * @param pointingInfo The pointing info containing the satellite location. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo); /** @@ -41,6 +46,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param sendPendingCount The number of datagrams that are currently being sent. * @param errorCode If datagram transfer failed, the reason for failure. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSendDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount, @SatelliteManager.SatelliteResult int errorCode); @@ -52,6 +58,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param receivePendingCount The number of datagrams that are currently pending to be received. * @param errorCode If datagram transfer failed, the reason for failure. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onReceiveDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount, @SatelliteManager.SatelliteResult int errorCode); diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 0fcd0d622d36..7fda550c599c 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -32,15 +32,15 @@ oneway interface ISatellite { * * @param listener The callback interface to handle satellite service indications. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void setSatelliteListener(in ISatelliteListener listener); @@ -53,15 +53,15 @@ oneway interface ISatellite { * disabling listening mode. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteListeningEnabled(in boolean enable, in int timeout, in IIntegerConsumer resultCallback); @@ -84,15 +84,15 @@ oneway interface ISatellite { * @param enableDemoMode True to enable demo mode and false to disable. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode, in IIntegerConsumer resultCallback); @@ -101,39 +101,42 @@ oneway interface ISatellite { * Request to get whether the satellite modem is enabled. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite modem is enabled. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite modem is enabled. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback); + void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, + in IBooleanConsumer callback); /** * Request to get whether the satellite service is supported on the device. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite service is supported on the device. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite service is supported on the device. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestIsSatelliteSupported(in IIntegerConsumer resultCallback, in IBooleanConsumer callback); @@ -142,19 +145,20 @@ oneway interface ISatellite { * Request to get the SatelliteCapabilities of the satellite service. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the SatelliteCapabilities of the satellite service. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the SatelliteCapabilities of the satellite service. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteCapabilities(in IIntegerConsumer resultCallback, in ISatelliteCapabilitiesConsumer callback); @@ -166,15 +170,15 @@ oneway interface ISatellite { * * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback); @@ -184,15 +188,15 @@ oneway interface ISatellite { * * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback); @@ -206,18 +210,18 @@ oneway interface ISatellite { * @param provisionData Data from the provisioning app that can be used by provisioning server * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ void provisionSatelliteService(in String token, in byte[] provisionData, in IIntegerConsumer resultCallback); @@ -230,18 +234,18 @@ oneway interface ISatellite { * @param token The token of the device/subscription to be deprovisioned. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback); @@ -249,19 +253,20 @@ oneway interface ISatellite { * Request to get whether this device is provisioned with a satellite provider. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether this device is provisioned with a satellite provider. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether this device is provisioned with a satellite provider. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback, in IBooleanConsumer callback); @@ -273,20 +278,20 @@ oneway interface ISatellite { * * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback); @@ -297,21 +302,21 @@ oneway interface ISatellite { * @param isEmergency Whether this is an emergency datagram. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency, in IIntegerConsumer resultCallback); @@ -322,19 +327,20 @@ oneway interface ISatellite { * ISatelliteListener#onSatelliteModemStateChanged. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the current satellite modem state. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the current satellite modem state. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteModemState(in IIntegerConsumer resultCallback, in IIntegerConsumer callback); @@ -343,19 +349,20 @@ oneway interface ISatellite { * Request to get whether satellite communication is allowed for the current location. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether satellite communication is allowed for the current location. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether satellite communication is allowed for the current location. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestIsSatelliteCommunicationAllowedForCurrentLocation( in IIntegerConsumer resultCallback, in IBooleanConsumer callback); @@ -366,19 +373,20 @@ oneway interface ISatellite { * This will return 0 if the satellite is currently visible. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the time after which the satellite will be visible. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the time after which the satellite will be visible. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback, in IIntegerConsumer callback); @@ -399,14 +407,14 @@ oneway interface ISatellite { * attach to them. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:NONE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:NO_RESOURCES - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:NONE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:MODEM_ERR + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList, in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback); @@ -420,12 +428,12 @@ oneway interface ISatellite { * @param serial Serial number of request. * @param enable {@code true} to enable satellite, {@code false} to disable satellite. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled, in IIntegerConsumer callback); @@ -437,12 +445,12 @@ oneway interface ISatellite { * this information to determine the relevant carrier. * @param serial Serial number of request. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback, in IBooleanConsumer callback); diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index a9c09c94d57c..4cee01e8bd39 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -72,172 +72,172 @@ public class SatelliteImplBase extends SatelliteService { @Override public void requestSatelliteListeningEnabled(boolean enable, int timeout, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestSatelliteListeningEnabled(enable, timeout, errorCallback), + .requestSatelliteListeningEnabled(enable, timeout, resultCallback), "requestSatelliteListeningEnabled"); } @Override public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .enableCellularModemWhileSatelliteModeIsOn(enabled, errorCallback), + .enableCellularModemWhileSatelliteModeIsOn(enabled, resultCallback), "enableCellularModemWhileSatelliteModeIsOn"); } @Override public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this .requestSatelliteEnabled( - enableSatellite, enableDemoMode, errorCallback), + enableSatellite, enableDemoMode, resultCallback), "requestSatelliteEnabled"); } @Override - public void requestIsSatelliteEnabled(IIntegerConsumer errorCallback, + public void requestIsSatelliteEnabled(IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteEnabled(errorCallback, callback), + .requestIsSatelliteEnabled(resultCallback, callback), "requestIsSatelliteEnabled"); } @Override - public void requestIsSatelliteSupported(IIntegerConsumer errorCallback, + public void requestIsSatelliteSupported(IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteSupported(errorCallback, callback), + .requestIsSatelliteSupported(resultCallback, callback), "requestIsSatelliteSupported"); } @Override - public void requestSatelliteCapabilities(IIntegerConsumer errorCallback, + public void requestSatelliteCapabilities(IIntegerConsumer resultCallback, ISatelliteCapabilitiesConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestSatelliteCapabilities(errorCallback, callback), + .requestSatelliteCapabilities(resultCallback, callback), "requestSatelliteCapabilities"); } @Override - public void startSendingSatellitePointingInfo(IIntegerConsumer errorCallback) + public void startSendingSatellitePointingInfo(IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(errorCallback), + () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(resultCallback), "startSendingSatellitePointingInfo"); } @Override - public void stopSendingSatellitePointingInfo(IIntegerConsumer errorCallback) + public void stopSendingSatellitePointingInfo(IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(errorCallback), + () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(resultCallback), "stopSendingSatellitePointingInfo"); } @Override public void provisionSatelliteService(String token, byte[] provisionData, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .provisionSatelliteService(token, provisionData, errorCallback), + .provisionSatelliteService(token, provisionData, resultCallback), "provisionSatelliteService"); } @Override - public void deprovisionSatelliteService(String token, IIntegerConsumer errorCallback) + public void deprovisionSatelliteService(String token, IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.deprovisionSatelliteService(token, errorCallback), + () -> SatelliteImplBase.this.deprovisionSatelliteService(token, resultCallback), "deprovisionSatelliteService"); } @Override - public void requestIsSatelliteProvisioned(IIntegerConsumer errorCallback, + public void requestIsSatelliteProvisioned(IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteProvisioned(errorCallback, callback), + .requestIsSatelliteProvisioned(resultCallback, callback), "requestIsSatelliteProvisioned"); } @Override - public void pollPendingSatelliteDatagrams(IIntegerConsumer errorCallback) + public void pollPendingSatelliteDatagrams(IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(errorCallback), + () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(resultCallback), "pollPendingSatelliteDatagrams"); } @Override public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .sendSatelliteDatagram(datagram, isEmergency, errorCallback), + .sendSatelliteDatagram(datagram, isEmergency, resultCallback), "sendSatelliteDatagram"); } @Override - public void requestSatelliteModemState(IIntegerConsumer errorCallback, + public void requestSatelliteModemState(IIntegerConsumer resultCallback, IIntegerConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestSatelliteModemState(errorCallback, callback), + .requestSatelliteModemState(resultCallback, callback), "requestSatelliteModemState"); } @Override public void requestIsSatelliteCommunicationAllowedForCurrentLocation( - IIntegerConsumer errorCallback, IBooleanConsumer callback) + IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this .requestIsSatelliteCommunicationAllowedForCurrentLocation( - errorCallback, callback), + resultCallback, callback), "requestIsSatelliteCommunicationAllowedForCurrentLocation"); } @Override - public void requestTimeForNextSatelliteVisibility(IIntegerConsumer errorCallback, + public void requestTimeForNextSatelliteVisibility(IIntegerConsumer resultCallback, IIntegerConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestTimeForNextSatelliteVisibility(errorCallback, callback), + .requestTimeForNextSatelliteVisibility(resultCallback, callback), "requestTimeForNextSatelliteVisibility"); } @Override public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList, - List<String> devicePlmnList, IIntegerConsumer errorCallback) + List<String> devicePlmnList, IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this.setSatellitePlmn( - simSlot, carrierPlmnList, devicePlmnList, errorCallback), + simSlot, carrierPlmnList, devicePlmnList, resultCallback), "setSatellitePlmn"); } @Override public void setSatelliteEnabledForCarrier(int simSlot, boolean enableSatellite, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this - .setSatelliteEnabledForCarrier(simSlot, enableSatellite, errorCallback), + () -> SatelliteImplBase.this.setSatelliteEnabledForCarrier( + simSlot, enableSatellite, resultCallback), "setSatelliteEnabledForCarrier"); } @Override - public void requestIsSatelliteEnabledForCarrier(int simSlot, IIntegerConsumer errorCallback, - IBooleanConsumer callback) throws RemoteException { + public void requestIsSatelliteEnabledForCarrier(int simSlot, + IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteEnabledForCarrier(simSlot, errorCallback, callback), + .requestIsSatelliteEnabledForCarrier(simSlot, resultCallback, callback), "requestIsSatelliteEnabledForCarrier"); } @@ -260,15 +260,15 @@ public class SatelliteImplBase extends SatelliteService { * * @param listener The callback interface to handle satellite service indications. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void setSatelliteListener(@NonNull ISatelliteListener listener) { // stub implementation @@ -281,20 +281,20 @@ public class SatelliteImplBase extends SatelliteService { * @param enable True to enable satellite listening mode and false to disable. * @param timeout How long the satellite modem should wait for the next incoming page before * disabling listening mode. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void requestSatelliteListeningEnabled(boolean enable, int timeout, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -302,10 +302,10 @@ public class SatelliteImplBase extends SatelliteService { * Allow cellular modem scanning while satellite mode is on. * @param enabled {@code true} to enable cellular modem while satellite mode is on * and {@code false} to disable - * @param errorCallback The callback to receive the error code result of the operation. + * @param resultCallback The callback to receive the error code result of the operation. */ public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -316,42 +316,43 @@ public class SatelliteImplBase extends SatelliteService { * * @param enableSatellite True to enable the satellite modem and false to disable. * @param enableDemoMode True to enable demo mode and false to disable. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } /** * Request to get whether the satellite modem is enabled. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite modem is enabled. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite modem is enabled. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer errorCallback, + public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -359,22 +360,23 @@ public class SatelliteImplBase extends SatelliteService { /** * Request to get whether the satellite service is supported on the device. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite service is supported on the device. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite service is supported on the device. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestIsSatelliteSupported(@NonNull IIntegerConsumer errorCallback, + public void requestIsSatelliteSupported(@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -382,22 +384,23 @@ public class SatelliteImplBase extends SatelliteService { /** * Request to get the SatelliteCapabilities of the satellite service. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the SatelliteCapabilities of the satellite service. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the SatelliteCapabilities of the satellite service. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestSatelliteCapabilities(@NonNull IIntegerConsumer errorCallback, + public void requestSatelliteCapabilities(@NonNull IIntegerConsumer resultCallback, @NonNull ISatelliteCapabilitiesConsumer callback) { // stub implementation } @@ -407,19 +410,19 @@ public class SatelliteImplBase extends SatelliteService { * The satellite service should report the satellite pointing info via * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves. * - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) { + public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -427,19 +430,19 @@ public class SatelliteImplBase extends SatelliteService { * User stopped pointing to the satellite. * The satellite service should stop reporting satellite pointing info to the framework. * - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) { + public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -452,23 +455,23 @@ public class SatelliteImplBase extends SatelliteService { * gateway. * @param provisionData Data from the provisioning app that can be used by provisioning * server - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -478,45 +481,46 @@ public class SatelliteImplBase extends SatelliteService { * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false. * * @param token The token of the device/subscription to be deprovisioned. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ public void deprovisionSatelliteService(@NonNull String token, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } /** * Request to get whether this device is provisioned with a satellite provider. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether this device is provisioned with a satellite provider. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether this device is provisioned with a satellite provider. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer errorCallback, + public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -526,24 +530,24 @@ public class SatelliteImplBase extends SatelliteService { * The satellite service should check if there are any pending datagrams to be received over * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived. * - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ - public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer errorCallback) { + public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -552,26 +556,26 @@ public class SatelliteImplBase extends SatelliteService { * * @param datagram Datagram to send in byte format. * @param isEmergency Whether this is an emergency datagram. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * @param resultCallback The callback to receive the error code result of the operation. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -580,22 +584,23 @@ public class SatelliteImplBase extends SatelliteService { * The satellite service should report the current satellite modem state via * ISatelliteListener#onSatelliteModemStateChanged. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the current satellite modem state. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the current satellite modem state. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestSatelliteModemState(@NonNull IIntegerConsumer errorCallback, + public void requestSatelliteModemState(@NonNull IIntegerConsumer resultCallback, @NonNull IIntegerConsumer callback) { // stub implementation } @@ -603,23 +608,24 @@ public class SatelliteImplBase extends SatelliteService { /** * Request to get whether satellite communication is allowed for the current location. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether satellite communication is allowed for the current location. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether satellite communication is allowed for the current location. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void requestIsSatelliteCommunicationAllowedForCurrentLocation( - @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) { + @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -628,22 +634,23 @@ public class SatelliteImplBase extends SatelliteService { * representing the duration in seconds after which the satellite will be visible. * This will return 0 if the satellite is currently visible. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the time after which the satellite will be visible. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the time after which the satellite will be visible. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer errorCallback, + public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer resultCallback, @NonNull IIntegerConsumer callback) { // stub implementation } @@ -658,25 +665,25 @@ public class SatelliteImplBase extends SatelliteService { * * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be * applied. The modem will use this information to determine the relevant carrier. - * @param errorCallback The callback to receive the error code result of the operation. + * @param resultCallback The callback to receive the error code result of the operation. * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks * supported by user subscription. * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite * PLMNs that are not supported by the carrier and make sure not to * attach to them. * - * Valid error codes returned: - * SatelliteError:NONE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:NO_RESOURCES - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:NONE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:MODEM_ERR + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -689,12 +696,12 @@ public class SatelliteImplBase extends SatelliteService { * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite. * @param callback {@code true} to enable satellite, {@code false} to disable satellite. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex, @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) { @@ -707,18 +714,18 @@ public class SatelliteImplBase extends SatelliteService { * * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be * applied. The modem will use this information to determine the relevant carrier. - * @param errorCallback The callback to receive the error code result of the operation. + * @param resultCallback The callback to receive the error code result of the operation. * @param callback {@code true} to satellite enabled, {@code false} to satellite disabled. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex, - @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) { + @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } } diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index d1a68d4e9cb2..241e69106718 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { @Deprecated public class MockAccountManager { diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt index 1496c356da08..fa2fbd276e9a 100644 --- a/test-mock/api/removed.txt +++ b/test-mock/api/removed.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt index 9e022f0cb0ef..2b5132ecd14f 100644 --- a/test-mock/api/system-current.txt +++ b/test-mock/api/system-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt index d802177e249b..14191ebcb080 100644 --- a/test-mock/api/system-removed.txt +++ b/test-mock/api/system-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index 35f076fe3f00..1752edcd469e 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt index d802177e249b..14191ebcb080 100644 --- a/test-mock/api/test-removed.txt +++ b/test-mock/api/test-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java index 79348d104745..ae7c2a99b808 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java @@ -247,7 +247,7 @@ public class GraphicsActivity extends Activity { int rc = 0; try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { - transaction.setFrameRateCategory(mSurfaceControl, category); + transaction.setFrameRateCategory(mSurfaceControl, category, false); transaction.apply(); } return rc; diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 63acddf25e02..0f4798090da8 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -61,9 +61,7 @@ <option name="shell-timeout" value="6600s"/> <option name="test-timeout" value="6600s"/> <option name="hidden-api-checks" value="false"/> - <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> - --> <!-- PerfettoListener related arguments --> <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> <option name="instrumentation-arg" diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index fa86e9c4ec0a..44de6a6ecbc3 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -100,6 +100,7 @@ class KeyboardLayoutManagerTests { const val RECEIVER_NAME = "DummyReceiver" private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us" private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk" + private const val GERMAN_LAYOUT_NAME = "keyboard_layout_german" private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1" const val LAYOUT_TYPE_QWERTZ = 2 const val LAYOUT_TYPE_QWERTY = 1 @@ -108,6 +109,7 @@ class KeyboardLayoutManagerTests { private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME) private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME) + private val GERMAN_LAYOUT_DESCRIPTOR = createLayoutDescriptor(GERMAN_LAYOUT_NAME) private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR = createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME) @@ -710,7 +712,7 @@ class KeyboardLayoutManagerTests { assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTag("de"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) assertCorrectLayout( keyboardDevice, @@ -775,13 +777,13 @@ class KeyboardLayoutManagerTests { assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) // Wrong layout type should match with language if provided layout type not available assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) assertCorrectLayout( keyboardDevice, @@ -856,7 +858,7 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.eq(createByteArray( KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, LAYOUT_TYPE_DEFAULT, - "German", + GERMAN_LAYOUT_NAME, KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, "de-Latn", LAYOUT_TYPE_QWERTZ)) @@ -882,7 +884,7 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.eq(createByteArray( "en", LAYOUT_TYPE_QWERTY, - "English (US)", + ENGLISH_US_LAYOUT_NAME, KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, "de-Latn", LAYOUT_TYPE_QWERTZ)) diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt index b39c93244ad6..33ff09b55534 100644 --- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt @@ -113,7 +113,7 @@ class KeyboardMetricsCollectorTests { ) val event = builder.addLayoutSelection( createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"), - KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0), + "English(US)(Qwerty)", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD ).addLayoutSelection( createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"), @@ -121,7 +121,7 @@ class KeyboardMetricsCollectorTests { KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER ).addLayoutSelection( createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"), - KeyboardLayout(null, "German", null, 0, null, 0, 0, 0), + "German", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE ).setIsFirstTimeConfiguration(true).build() @@ -184,7 +184,7 @@ class KeyboardMetricsCollectorTests { ) val event = builder.addLayoutSelection( createImeSubtype(4, null, "qwerty"), // Default language tag - KeyboardLayout(null, "German", null, 0, null, 0, 0, 0), + "German", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE ).build() diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt index 56ab755af47b..7e43566d56f8 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt @@ -37,6 +37,8 @@ class ColorModeControls : LinearLayout, WindowObserver { private var window: Window? = null private var currentModeDisplay: TextView? = null + private var desiredRatio = 0.0f + override fun onFinishInflate() { super.onFinishInflate() val window = window ?: throw IllegalStateException("Failed to attach window") @@ -67,6 +69,7 @@ class ColorModeControls : LinearLayout, WindowObserver { override fun onAttachedToWindow() { super.onAttachedToWindow() + desiredRatio = window?.desiredHdrHeadroom ?: 0.0f val hdrVis = if (display.isHdrSdrRatioAvailable) { display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener) View.VISIBLE @@ -83,6 +86,11 @@ class ColorModeControls : LinearLayout, WindowObserver { } private fun setColorMode(newMode: Int) { + if (newMode == ActivityInfo.COLOR_MODE_HDR && + window!!.colorMode == ActivityInfo.COLOR_MODE_HDR) { + desiredRatio = (desiredRatio + 1) % 5.0f + window!!.desiredHdrHeadroom = desiredRatio + } window!!.colorMode = newMode updateModeInfoDisplay() } diff --git a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java index 6a6ab00e33ff..a43e1b091d41 100644 --- a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java +++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java @@ -21,15 +21,39 @@ import java.util.Map; public class TestableFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver { private Map<String, Boolean> mOverrides = new HashMap<>(); + private Map<String, Integer> mOverridesInt = new HashMap<>(); + private Map<String, String> mOverridesString = new HashMap<>(); @Override public boolean isEnabled(SystemUiSystemPropertiesFlags.Flag flag) { return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue); } + @Override + public int getIntValue(SystemUiSystemPropertiesFlags.Flag flag) { + return mOverridesInt.getOrDefault(flag.mSysPropKey, flag.mDefaultIntValue); + } + + @Override + public String getStringValue(SystemUiSystemPropertiesFlags.Flag flag) { + return mOverridesString.getOrDefault(flag.mSysPropKey, flag.mDefaultStringValue); + } + public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, boolean isEnabled) { mOverrides.put(flag.mSysPropKey, isEnabled); return this; } + + public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, + int value) { + mOverridesInt.put(flag.mSysPropKey, value); + return this; + } + + public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, + String value) { + mOverridesString.put(flag.mSysPropKey, value); + return this; + } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt index 33010baaf894..678e6eae0be6 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt @@ -46,8 +46,11 @@ class ConstantFilter( } methodPolicy = policy - // TODO: Need to think about the realistic default behavior. - classPolicy = if (policy != FilterPolicy.Throw) policy else FilterPolicy.Remove + // If the default policy is "throw", we convert it to "keep" for classes and fields. + classPolicy = when (policy) { + FilterPolicy.Throw -> FilterPolicy.Keep + else -> policy + } fieldPolicy = classPolicy } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt index 9c372ff68e37..c6334c40e8f4 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt @@ -17,6 +17,8 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.HostStubGenErrors import com.android.hoststubgen.HostStubGenInternalException +import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC +import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME import com.android.hoststubgen.asm.isAnonymousInnerClass import com.android.hoststubgen.log import com.android.hoststubgen.asm.ClassNodes @@ -81,6 +83,16 @@ class ImplicitOutputFilter( } } + // If we throw from the static initializer, the class would be useless, so we convert it + // "keep" instead. + if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC && + fallback.policy == FilterPolicy.Throw) { + // TODO Maybe show a warning?? But that'd be too noisy with --default-throw. + return FilterPolicy.Keep.withReason( + "'throw' on static initializer is handled as 'keep'" + + " [original throw reason: ${fallback.reason}]") + } + return fallback } }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index 57b668954f98..ce72a8e79882 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -79,7 +79,7 @@ class ImplGeneratingAdapter( // StaticInitMerger will merge it with the existing one, if any. visitMethod( Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC, - "<clinit>", + CLASS_INITIALIZER_NAME, "()V", null, null diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index 43ceec42660d..0761edc34916 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -372,7 +372,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 2, attributes: 3 + interfaces: 0, fields: 1, methods: 1, attributes: 3 public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -387,17 +387,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow - - static {}; - descriptor: ()V - flags: (0x0008) ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - x: new #x // class java/lang/RuntimeException - x: dup - x: ldc #x // String Stub! - x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - x: athrow } SourceFile: "TinyFrameworkClassWithInitializer.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index 43ceec42660d..0761edc34916 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -372,7 +372,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 2, attributes: 3 + interfaces: 0, fields: 1, methods: 1, attributes: 3 public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -387,17 +387,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow - - static {}; - descriptor: ()V - flags: (0x0008) ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - x: new #x // class java/lang/RuntimeException - x: dup - x: ldc #x // String Stub! - x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - x: athrow } SourceFile: "TinyFrameworkClassWithInitializer.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt index 079d2a84e498..8fcd2fbe7c84 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt @@ -15,3 +15,8 @@ class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy stub # Class load hook class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy ~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + + +class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer stubclass + # Testing 'throw' on a static initializer. This should be handled as 'keep'. + method <clinit> ()V throw diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh index fd486468a1a7..722905f15b41 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh @@ -16,6 +16,10 @@ source "${0%/*}"/../../common.sh +#********************************************************************************************** +#This script is broken because it relies on soong intermediate files, which seem to have moved. +#********************************************************************************************** + # This scripts run the "tiny-framework" test, but does most stuff from the command line, using # the native java and javac commands. # This is useful to @@ -57,7 +61,7 @@ framework_compile_classpaths=( test_compile_classpaths=( $SOONG_INT/external/junit/junit/android_common/combined/junit.jar - $SOONG_INT/prebuilts/tools/common/m2/truth-prebuilt/android_common/combined/truth-prebuilt.jar + $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar ) test_runtime_classpaths=( diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java index 53cfdf6cd412..01a690b73ba0 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java @@ -18,10 +18,14 @@ package com.android.hoststubgen.test.tinyframework; import android.hosttest.annotation.HostSideTestClassLoadHook; import android.hosttest.annotation.HostSideTestWholeClassStub; + +// Note, policy-override-tiny-framework.txt hss an override on this class. @HostSideTestClassLoadHook( "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded") @HostSideTestWholeClassStub public class TinyFrameworkClassWithInitializer { + // Note, this method has a 'throw' in the policy file, which is handled as a 'keep' (because + // it's a static initializer), so this won't show up in the stub jar. static { sInitialized = true; } diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 7600942c99e6..2e9cf428354a 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -33,7 +33,9 @@ run m run-ravenwood-test ${READY_TEST_MODULES[*]} ${NOT_READY_TEST_MODULES[*]} run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh run ./hoststubgen/test-framework/run-test-without-atest.sh -run ./hoststubgen/test-tiny-framework/run-test-manually.sh + +#This script is broken because it relies on soong intermediate files, which seem to have moved. +#run ./hoststubgen/test-tiny-framework/run-test-manually.sh run atest tiny-framework-dump-test run ./scripts/build-framework-hostside-jars-and-extract.sh |